Merge "[placement] Add cache headers to placement api requests"
This commit is contained in:
commit
2ca91b8ce5
@ -13,6 +13,7 @@
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
from nova.api.openstack.placement import util
|
||||
@ -30,11 +31,20 @@ PUT_AGGREGATES_SCHEMA = {
|
||||
}
|
||||
|
||||
|
||||
def _send_aggregates(response, aggregate_uuids):
|
||||
def _send_aggregates(req, aggregate_uuids):
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
response = req.response
|
||||
response.status = 200
|
||||
response.body = encodeutils.to_utf8(
|
||||
jsonutils.dumps(_serialize_aggregates(aggregate_uuids)))
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.cache_control = 'no-cache'
|
||||
# We never get an aggregate itself, we get the list of aggregates
|
||||
# that are associated with a resource provider. We don't record the
|
||||
# time when that association was made and the time when an aggregate
|
||||
# uuid was created is not relevant, so here we punt and use utcnow.
|
||||
req.response.last_modified = timeutils.utcnow(with_timezone=True)
|
||||
return response
|
||||
|
||||
|
||||
@ -59,7 +69,7 @@ def get_aggregates(req):
|
||||
context, uuid)
|
||||
aggregate_uuids = resource_provider.get_aggregates()
|
||||
|
||||
return _send_aggregates(req.response, aggregate_uuids)
|
||||
return _send_aggregates(req, aggregate_uuids)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -73,4 +83,4 @@ def set_aggregates(req):
|
||||
aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA)
|
||||
resource_provider.set_aggregates(aggregate_uuids)
|
||||
|
||||
return _send_aggregates(req.response, aggregate_uuids)
|
||||
return _send_aggregates(req, aggregate_uuids)
|
||||
|
@ -17,6 +17,7 @@ import copy
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -161,7 +162,16 @@ def _allocations_dict(allocations, key_fetcher, resource_provider=None,
|
||||
"""Turn allocations into a dict of resources keyed by key_fetcher."""
|
||||
allocation_data = collections.defaultdict(dict)
|
||||
|
||||
# NOTE(cdent): The last_modified for an allocation will always be
|
||||
# based off the created_at column because allocations are only
|
||||
# ever inserted, never updated.
|
||||
last_modified = None
|
||||
# Only calculate last-modified if we are using a microversion that
|
||||
# supports it.
|
||||
get_last_modified = want_version and want_version.matches((1, 15))
|
||||
for allocation in allocations:
|
||||
if get_last_modified:
|
||||
last_modified = util.pick_last_modified(last_modified, allocation)
|
||||
key = key_fetcher(allocation)
|
||||
if 'resources' not in allocation_data[key]:
|
||||
allocation_data[key]['resources'] = {}
|
||||
@ -183,7 +193,8 @@ def _allocations_dict(allocations, key_fetcher, resource_provider=None,
|
||||
result['project_id'] = allocations[0].project_id
|
||||
result['user_id'] = allocations[0].user_id
|
||||
|
||||
return result
|
||||
last_modified = last_modified or timeutils.utcnow(with_timezone=True)
|
||||
return result, last_modified
|
||||
|
||||
|
||||
def _serialize_allocations_for_consumer(allocations, want_version=None):
|
||||
@ -245,6 +256,7 @@ def _serialize_allocations_for_resource_provider(allocations,
|
||||
def list_for_consumer(req):
|
||||
"""List allocations associated with a consumer."""
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
consumer_id = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
|
||||
@ -254,13 +266,18 @@ def list_for_consumer(req):
|
||||
allocations = rp_obj.AllocationList.get_all_by_consumer_id(
|
||||
context, consumer_id)
|
||||
|
||||
allocations_json = jsonutils.dumps(
|
||||
_serialize_allocations_for_consumer(allocations, want_version))
|
||||
output, last_modified = _serialize_allocations_for_consumer(
|
||||
allocations, want_version)
|
||||
allocations_json = jsonutils.dumps(output)
|
||||
|
||||
req.response.status = 200
|
||||
req.response.body = encodeutils.to_utf8(allocations_json)
|
||||
req.response.content_type = 'application/json'
|
||||
return req.response
|
||||
response = req.response
|
||||
response.status = 200
|
||||
response.body = encodeutils.to_utf8(allocations_json)
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = last_modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -273,6 +290,7 @@ def list_for_resource_provider(req):
|
||||
# using a dict of dicts for the output we are potentially limiting
|
||||
# ourselves in terms of sorting and filtering.
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
|
||||
# confirm existence of resource provider so we get a reasonable
|
||||
@ -286,13 +304,18 @@ def list_for_resource_provider(req):
|
||||
|
||||
allocs = rp_obj.AllocationList.get_all_by_resource_provider(context, rp)
|
||||
|
||||
allocations_json = jsonutils.dumps(
|
||||
_serialize_allocations_for_resource_provider(allocs, rp))
|
||||
output, last_modified = _serialize_allocations_for_resource_provider(
|
||||
allocs, rp)
|
||||
allocations_json = jsonutils.dumps(output)
|
||||
|
||||
req.response.status = 200
|
||||
req.response.body = encodeutils.to_utf8(allocations_json)
|
||||
req.response.content_type = 'application/json'
|
||||
return req.response
|
||||
response = req.response
|
||||
response.status = 200
|
||||
response.body = encodeutils.to_utf8(allocations_json)
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = last_modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
def _new_allocations(context, resource_provider_uuid, consumer_uuid,
|
||||
|
@ -17,6 +17,7 @@ import collections
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -224,4 +225,7 @@ def list_allocation_candidates(req):
|
||||
json_data = jsonutils.dumps(trx_cands)
|
||||
response.body = encodeutils.to_utf8(json_data)
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.cache_control = 'no-cache'
|
||||
response.last_modified = timeutils.utcnow(with_timezone=True)
|
||||
return response
|
||||
|
@ -161,21 +161,33 @@ def _make_inventory_object(resource_provider, resource_class, **data):
|
||||
return inventory
|
||||
|
||||
|
||||
def _send_inventories(response, resource_provider, inventories):
|
||||
def _send_inventories(req, resource_provider, inventories):
|
||||
"""Send a JSON representation of a list of inventories."""
|
||||
response = req.response
|
||||
response.status = 200
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_inventories(inventories, resource_provider.generation)))
|
||||
output, last_modified = _serialize_inventories(
|
||||
inventories, resource_provider.generation)
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||
response.content_type = 'application/json'
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = last_modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
def _send_inventory(response, resource_provider, inventory, status=200):
|
||||
def _send_inventory(req, resource_provider, inventory, status=200):
|
||||
"""Send a JSON representation of one single inventory."""
|
||||
response = req.response
|
||||
response.status = status
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(_serialize_inventory(
|
||||
inventory, generation=resource_provider.generation)))
|
||||
response.content_type = 'application/json'
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
if want_version.matches((1, 15)):
|
||||
modified = util.pick_last_modified(None, inventory)
|
||||
response.last_modified = modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
@ -195,11 +207,13 @@ def _serialize_inventories(inventories, generation):
|
||||
inventories_by_class = {inventory.resource_class: inventory
|
||||
for inventory in inventories}
|
||||
inventories_dict = {}
|
||||
last_modified = None
|
||||
for resource_class, inventory in inventories_by_class.items():
|
||||
last_modified = util.pick_last_modified(last_modified, inventory)
|
||||
inventories_dict[resource_class] = _serialize_inventory(
|
||||
inventory, generation=None)
|
||||
return {'resource_provider_generation': generation,
|
||||
'inventories': inventories_dict}
|
||||
return ({'resource_provider_generation': generation,
|
||||
'inventories': inventories_dict}, last_modified)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -238,7 +252,7 @@ def create_inventory(req):
|
||||
response = req.response
|
||||
response.location = util.inventory_url(
|
||||
req.environ, resource_provider, resource_class)
|
||||
return _send_inventory(response, resource_provider, inventory,
|
||||
return _send_inventory(req, resource_provider, inventory,
|
||||
status=201)
|
||||
|
||||
|
||||
@ -294,7 +308,7 @@ def get_inventories(req):
|
||||
|
||||
inv_list = rp_obj.InventoryList.get_all_by_resource_provider(context, rp)
|
||||
|
||||
return _send_inventories(req.response, rp, inv_list)
|
||||
return _send_inventories(req, rp, inv_list)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -323,7 +337,7 @@ def get_inventory(req):
|
||||
_('No inventory of class %(class)s for %(rp_uuid)s') %
|
||||
{'class': resource_class, 'rp_uuid': uuid})
|
||||
|
||||
return _send_inventory(req.response, rp, inventory)
|
||||
return _send_inventory(req, rp, inventory)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -383,7 +397,7 @@ def set_inventories(req):
|
||||
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
||||
'error': exc})
|
||||
|
||||
return _send_inventories(req.response, resource_provider, inventories)
|
||||
return _send_inventories(req, resource_provider, inventories)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -468,4 +482,4 @@ def update_inventory(req):
|
||||
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
||||
'error': exc})
|
||||
|
||||
return _send_inventory(req.response, resource_provider, inventory)
|
||||
return _send_inventory(req, resource_provider, inventory)
|
||||
|
@ -15,6 +15,7 @@ import copy
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -56,12 +57,17 @@ def _serialize_resource_class(environ, rc):
|
||||
return data
|
||||
|
||||
|
||||
def _serialize_resource_classes(environ, rcs):
|
||||
def _serialize_resource_classes(environ, rcs, want_version):
|
||||
output = []
|
||||
last_modified = None
|
||||
get_last_modified = want_version.matches((1, 15))
|
||||
for rc in rcs:
|
||||
if get_last_modified:
|
||||
last_modified = util.pick_last_modified(last_modified, rc)
|
||||
data = _serialize_resource_class(environ, rc)
|
||||
output.append(data)
|
||||
return {"resource_classes": output}
|
||||
last_modified = last_modified or timeutils.utcnow(with_timezone=True)
|
||||
return ({"resource_classes": output}, last_modified)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -131,6 +137,7 @@ def get_resource_class(req):
|
||||
"""
|
||||
name = util.wsgi_path_item(req.environ, 'name')
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
# The containing application will catch a not found here.
|
||||
rc = rp_obj.ResourceClass.get_by_name(context, name)
|
||||
|
||||
@ -138,6 +145,13 @@ def get_resource_class(req):
|
||||
_serialize_resource_class(req.environ, rc))
|
||||
)
|
||||
req.response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.cache_control = 'no-cache'
|
||||
# Non-custom resource classes will return None from pick_last_modified,
|
||||
# so the 'or' causes utcnow to be used.
|
||||
last_modified = util.pick_last_modified(None, rc) or timeutils.utcnow(
|
||||
with_timezone=True)
|
||||
req.response.last_modified = last_modified
|
||||
return req.response
|
||||
|
||||
|
||||
@ -151,13 +165,17 @@ def list_resource_classes(req):
|
||||
a collection of resource classes.
|
||||
"""
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
rcs = rp_obj.ResourceClassList.get_all(context)
|
||||
|
||||
response = req.response
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_resource_classes(req.environ, rcs))
|
||||
)
|
||||
output, last_modified = _serialize_resource_classes(
|
||||
req.environ, rcs, want_version)
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = last_modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ import copy
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import webob
|
||||
|
||||
@ -140,10 +141,15 @@ def _serialize_provider(environ, resource_provider, want_version):
|
||||
|
||||
def _serialize_providers(environ, resource_providers, want_version):
|
||||
output = []
|
||||
last_modified = None
|
||||
get_last_modified = want_version.matches((1, 15))
|
||||
for provider in resource_providers:
|
||||
if get_last_modified:
|
||||
last_modified = util.pick_last_modified(last_modified, provider)
|
||||
provider_data = _serialize_provider(environ, provider, want_version)
|
||||
output.append(provider_data)
|
||||
return {"resource_providers": output}
|
||||
last_modified = last_modified or timeutils.utcnow(with_timezone=True)
|
||||
return ({"resource_providers": output}, last_modified)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -219,6 +225,7 @@ def get_resource_provider(req):
|
||||
On success return a 200 with an application/json body representing
|
||||
the resource provider.
|
||||
"""
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
# The containing application will catch a not found here.
|
||||
context = req.environ['placement.context']
|
||||
@ -226,11 +233,15 @@ def get_resource_provider(req):
|
||||
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
|
||||
context, uuid)
|
||||
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
response = req.response
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_provider(req.environ, resource_provider, want_version)))
|
||||
req.response.content_type = 'application/json'
|
||||
return req.response
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
modified = util.pick_last_modified(None, resource_provider)
|
||||
response.last_modified = modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@ -287,9 +298,13 @@ def list_resource_providers(req):
|
||||
{'error': exc})
|
||||
|
||||
response = req.response
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_providers(req.environ, resource_providers, want_version)))
|
||||
output, last_modified = _serialize_providers(
|
||||
req.environ, resource_providers, want_version)
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = last_modified
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
||||
|
||||
@ -303,6 +318,7 @@ def update_resource_provider(req):
|
||||
"""
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
|
||||
# The containing application will catch a not found here.
|
||||
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
|
||||
@ -330,8 +346,12 @@ def update_resource_provider(req):
|
||||
_('Unable to save resource provider %(rp_uuid)s: %(error)s') %
|
||||
{'rp_uuid': uuid, 'error': exc})
|
||||
|
||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
response = req.response
|
||||
response.status = 200
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_provider(req.environ, resource_provider, want_version)))
|
||||
req.response.status = 200
|
||||
req.response.content_type = 'application/json'
|
||||
return req.response
|
||||
response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
response.last_modified = resource_provider.updated_at
|
||||
response.cache_control = 'no-cache'
|
||||
return response
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -21,6 +22,7 @@ from nova.api.openstack.placement import wsgi_wrapper
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
def home(req):
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
min_version = microversion.min_version_string()
|
||||
max_version = microversion.max_version_string()
|
||||
# NOTE(cdent): As sections of the api are added, links can be
|
||||
@ -34,4 +36,7 @@ def home(req):
|
||||
version_json = jsonutils.dumps({'versions': [version_data]})
|
||||
req.response.body = encodeutils.to_utf8(version_json)
|
||||
req.response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.cache_control = 'no-cache'
|
||||
req.response.last_modified = timeutils.utcnow(with_timezone=True)
|
||||
return req.response
|
||||
|
@ -16,6 +16,7 @@ import copy
|
||||
import jsonschema
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -85,14 +86,26 @@ def _normalize_traits_qs_param(qs):
|
||||
return filters
|
||||
|
||||
|
||||
def _serialize_traits(traits):
|
||||
return {'traits': [trait.name for trait in traits]}
|
||||
def _serialize_traits(traits, want_version):
|
||||
last_modified = None
|
||||
get_last_modified = want_version.matches((1, 15))
|
||||
trait_names = []
|
||||
for trait in traits:
|
||||
if get_last_modified:
|
||||
last_modified = util.pick_last_modified(last_modified, trait)
|
||||
trait_names.append(trait.name)
|
||||
|
||||
# If there were no traits, set last_modified to now
|
||||
last_modified = last_modified or timeutils.utcnow(with_timezone=True)
|
||||
|
||||
return {'traits': trait_names}, last_modified
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@microversion.version_handler('1.6')
|
||||
def put_trait(req):
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
name = util.wsgi_path_item(req.environ, 'name')
|
||||
|
||||
try:
|
||||
@ -110,10 +123,16 @@ def put_trait(req):
|
||||
trait.create()
|
||||
req.response.status = 201
|
||||
except exception.TraitExists:
|
||||
# Get the trait that already exists to get last-modified time.
|
||||
if want_version.matches((1, 15)):
|
||||
trait = rp_obj.Trait.get_by_name(context, name)
|
||||
req.response.status = 204
|
||||
|
||||
req.response.content_type = None
|
||||
req.response.location = util.trait_url(req.environ, trait)
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.last_modified = trait.created_at
|
||||
req.response.cache_control = 'no-cache'
|
||||
return req.response
|
||||
|
||||
|
||||
@ -121,16 +140,20 @@ def put_trait(req):
|
||||
@microversion.version_handler('1.6')
|
||||
def get_trait(req):
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
name = util.wsgi_path_item(req.environ, 'name')
|
||||
|
||||
try:
|
||||
rp_obj.Trait.get_by_name(context, name)
|
||||
trait = rp_obj.Trait.get_by_name(context, name)
|
||||
except exception.TraitNotFound as ex:
|
||||
raise webob.exc.HTTPNotFound(
|
||||
explanation=ex.format_message())
|
||||
|
||||
req.response.status = 204
|
||||
req.response.content_type = None
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.last_modified = trait.created_at
|
||||
req.response.cache_control = 'no-cache'
|
||||
return req.response
|
||||
|
||||
|
||||
@ -163,6 +186,7 @@ def delete_trait(req):
|
||||
@util.check_accept('application/json')
|
||||
def list_traits(req):
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
filters = {}
|
||||
|
||||
try:
|
||||
@ -185,8 +209,11 @@ def list_traits(req):
|
||||
|
||||
traits = rp_obj.TraitList.get_all(context, filters)
|
||||
req.response.status = 200
|
||||
req.response.body = encodeutils.to_utf8(
|
||||
jsonutils.dumps(_serialize_traits(traits)))
|
||||
output, last_modified = _serialize_traits(traits, want_version)
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.last_modified = last_modified
|
||||
req.response.cache_control = 'no-cache'
|
||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||
req.response.content_type = 'application/json'
|
||||
return req.response
|
||||
|
||||
@ -196,6 +223,7 @@ def list_traits(req):
|
||||
@util.check_accept('application/json')
|
||||
def list_traits_for_resource_provider(req):
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
|
||||
# Resource provider object is needed for two things: If it is
|
||||
@ -211,9 +239,13 @@ def list_traits_for_resource_provider(req):
|
||||
{'uuid': uuid, 'error': exc})
|
||||
|
||||
traits = rp_obj.TraitList.get_all_by_resource_provider(context, rp)
|
||||
response_body = _serialize_traits(traits)
|
||||
response_body, last_modified = _serialize_traits(traits, want_version)
|
||||
response_body["resource_provider_generation"] = rp.generation
|
||||
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.last_modified = last_modified
|
||||
req.response.cache_control = 'no-cache'
|
||||
|
||||
req.response.status = 200
|
||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
||||
req.response.content_type = 'application/json'
|
||||
@ -225,6 +257,7 @@ def list_traits_for_resource_provider(req):
|
||||
@util.require_content('application/json')
|
||||
def update_traits_for_resource_provider(req):
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
data = util.extract_json(req.body, SET_TRAITS_FOR_RP_SCHEMA)
|
||||
rp_gen = data['resource_provider_generation']
|
||||
@ -248,9 +281,12 @@ def update_traits_for_resource_provider(req):
|
||||
|
||||
resource_provider.set_traits(trait_objs)
|
||||
|
||||
response_body = _serialize_traits(trait_objs)
|
||||
response_body, last_modified = _serialize_traits(trait_objs, want_version)
|
||||
response_body[
|
||||
'resource_provider_generation'] = resource_provider.generation
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.last_modified = last_modified
|
||||
req.response.cache_control = 'no-cache'
|
||||
req.response.status = 200
|
||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
||||
req.response.content_type = 'application/json'
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
@ -64,6 +65,7 @@ def list_usages(req):
|
||||
"""
|
||||
context = req.environ['placement.context']
|
||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
|
||||
# Resource provider object needed for two things: If it is
|
||||
# NotFound we'll get a 404 here, which needs to happen because
|
||||
@ -85,6 +87,14 @@ def list_usages(req):
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||
_serialize_usages(resource_provider, usage)))
|
||||
req.response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.cache_control = 'no-cache'
|
||||
# While it would be possible to generate a last-modified time
|
||||
# based on the collection of allocations that result in a usage
|
||||
# value (with some spelunking in the SQL) that doesn't align with
|
||||
# the question that is being asked in a request for usages: What
|
||||
# is the usage, now? So the last-modified time is set to utcnow.
|
||||
req.response.last_modified = timeutils.utcnow(with_timezone=True)
|
||||
return req.response
|
||||
|
||||
|
||||
@ -99,6 +109,7 @@ def get_total_usages(req):
|
||||
Return 404 Not Found if the wanted microversion does not match.
|
||||
"""
|
||||
context = req.environ['placement.context']
|
||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||
|
||||
schema = GET_USAGES_SCHEMA_1_9
|
||||
|
||||
@ -115,4 +126,12 @@ def get_total_usages(req):
|
||||
for resource in usages}}
|
||||
response.body = encodeutils.to_utf8(jsonutils.dumps(usages_dict))
|
||||
req.response.content_type = 'application/json'
|
||||
if want_version.matches((1, 15)):
|
||||
req.response.cache_control = 'no-cache'
|
||||
# While it would be possible to generate a last-modified time
|
||||
# based on the collection of allocations that result in a usage
|
||||
# value (with some spelunking in the SQL) that doesn't align with
|
||||
# the question that is being asked in a request for usages: What
|
||||
# is the usage, now? So the last-modified time is set to utcnow.
|
||||
req.response.last_modified = timeutils.utcnow(with_timezone=True)
|
||||
return req.response
|
||||
|
@ -54,9 +54,9 @@ VERSIONS = [
|
||||
# as GET. The 'allocation_requests' format in GET
|
||||
# /allocation_candidates is updated to be the same as well.
|
||||
'1.13', # Adds POST /allocations to set allocations for multiple consumers
|
||||
# as GET
|
||||
'1.14', # Adds parent and root provider UUID on resource provider
|
||||
# representation and 'in_tree' filter on GET /resource_providers
|
||||
'1.15', # Include last-modified and cache-control headers
|
||||
]
|
||||
|
||||
|
||||
|
@ -197,3 +197,13 @@ A new ``in_tree=<UUID>`` parameter is now available in the ``GET
|
||||
/resource-providers`` API call. Supplying a UUID value for the ``in_tree``
|
||||
parameter will cause all resource providers within the "provider tree" of the
|
||||
provider matching ``<UUID>`` to be returned.
|
||||
|
||||
1.15 Add 'last-modified' and 'cache-control' headers
|
||||
----------------------------------------------------
|
||||
|
||||
Throughout the API, 'last-modified' headers have been added to GET responses
|
||||
and those PUT and POST responses that have bodies. The value is either the
|
||||
actual last modified time of the most recently modified associated database
|
||||
entity or the current time if there is no direct mapping to the database. In
|
||||
addition, 'cache-control: no-cache' headers are added where the 'last-modified'
|
||||
header has been added to prevent inadvertent caching of resources.
|
||||
|
@ -17,6 +17,7 @@ import re
|
||||
import jsonschema
|
||||
from oslo_middleware import request_id
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import webob
|
||||
|
||||
@ -124,6 +125,25 @@ def json_error_formatter(body, status, title, environ):
|
||||
return {'errors': [error_dict]}
|
||||
|
||||
|
||||
def pick_last_modified(last_modified, obj):
|
||||
"""Choose max of last_modified and obj.updated_at or obj.created_at.
|
||||
|
||||
If updated_at is not implemented in `obj` use the current time in UTC.
|
||||
"""
|
||||
try:
|
||||
current_modified = (obj.updated_at or obj.created_at)
|
||||
except NotImplementedError:
|
||||
# If updated_at is not implemented, we are looking at objects that
|
||||
# have not come from the database, so "now" is the right modified
|
||||
# time.
|
||||
current_modified = timeutils.utcnow(with_timezone=True)
|
||||
if last_modified:
|
||||
last_modified = max(last_modified, current_modified)
|
||||
else:
|
||||
last_modified = current_modified
|
||||
return last_modified
|
||||
|
||||
|
||||
def require_content(content_type):
|
||||
"""Decorator to require a content type in a handler."""
|
||||
def decorator(f):
|
||||
|
@ -65,12 +65,19 @@ tests:
|
||||
status: 200
|
||||
response_headers:
|
||||
content-type: /application/json/
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.aggregates[0]: *agg_1
|
||||
$.aggregates[1]: *agg_2
|
||||
|
||||
- name: get those aggregates
|
||||
GET: $LAST_URL
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.aggregates.`len`: 2
|
||||
|
||||
@ -117,3 +124,26 @@ tests:
|
||||
- JSON does not validate
|
||||
response_json_paths:
|
||||
$.errors[0].title: Bad Request
|
||||
|
||||
# The next two tests confirm that prior to version 1.15 we do
|
||||
# not set the cache-control or last-modified headers on either
|
||||
# PUT or GET.
|
||||
|
||||
- name: put some aggregates v1.14
|
||||
PUT: $LAST_URL
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
data:
|
||||
- *agg_1
|
||||
- *agg_2
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get those aggregates v1.14
|
||||
GET: $LAST_URL
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
@ -83,6 +83,11 @@ tests:
|
||||
# storage show correct capacity and usage
|
||||
$.provider_summaries["$ENVIRON['SS_UUID']"].resources[DISK_GB].capacity: 1900 # 1.0 * 2000 - 100G
|
||||
$.provider_summaries["$ENVIRON['SS_UUID']"].resources[DISK_GB].used: 0
|
||||
response_forbidden_headers:
|
||||
# In the default microversion in this file (1.10) the cache headers
|
||||
# are not preset.
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
# Verify the 1.12 format of the allocation_requests sub object which
|
||||
# changes from a list-list to dict-ish format.
|
||||
@ -111,3 +116,13 @@ tests:
|
||||
# Verify that shared storage provider only has DISK_GB listed in the
|
||||
# resource requests, but is listed twice
|
||||
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources[DISK_GB]: [100, 100]
|
||||
|
||||
- name: get allocation candidates cache headers
|
||||
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100
|
||||
request_headers:
|
||||
# microversion 1.15 to cause cache headers
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
@ -373,6 +373,11 @@ tests:
|
||||
DISK_GB: 2
|
||||
VCPU: 8
|
||||
status: 204
|
||||
# These headers should not be present in any microversion on PUT
|
||||
# because there is no response body.
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: get those allocations for consumer
|
||||
GET: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
|
||||
@ -409,3 +414,37 @@ tests:
|
||||
- Allocation for resource provider 'be8b9cba-e7db-4a12-a386-99b4242167fe' that does not exist
|
||||
response_json_paths:
|
||||
$.errors[0].title: Bad Request
|
||||
|
||||
- name: get allocations for resource provider with cache headers 1.15
|
||||
GET: /resource_providers/fcfa516a-abbe-45d1-8152-d5225d82e596/allocations
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: get allocations for resource provider without cache headers 1.14
|
||||
GET: /resource_providers/fcfa516a-abbe-45d1-8152-d5225d82e596/allocations
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: get allocations for consumer with cache headers 1.15
|
||||
GET: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: get allocations for consumer without cache headers 1.14
|
||||
GET: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
@ -159,3 +159,20 @@ tests:
|
||||
$.errors[0].title: Not Found
|
||||
response_strings:
|
||||
- The resource could not be found.
|
||||
|
||||
- name: root at 1.15 has cache headers
|
||||
GET: /
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: root at 1.14 no cache headers
|
||||
GET: /
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
@ -166,6 +166,13 @@ tests:
|
||||
- name: get that inventory
|
||||
GET: $LOCATION
|
||||
status: 200
|
||||
request_headers:
|
||||
# set microversion to 1.15 to get timestamp headers
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.resource_provider_generation: 1
|
||||
$.total: 2048
|
||||
@ -175,6 +182,15 @@ tests:
|
||||
$.step_size: 10
|
||||
$.allocation_ratio: 1.0
|
||||
|
||||
- name: get inventory v1.14 no cache headers
|
||||
GET: $LAST_URL
|
||||
status: 200
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: modify the inventory
|
||||
PUT: $LAST_URL
|
||||
request_headers:
|
||||
@ -310,6 +326,13 @@ tests:
|
||||
|
||||
- name: get list of inventories
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||
request_headers:
|
||||
# set microversion to 1.15 to get timestamp headers
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.resource_provider_generation: 2
|
||||
$.inventories.DISK_GB.total: 2048
|
||||
@ -407,6 +430,8 @@ tests:
|
||||
PUT: $LOCATION/inventories
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
# set microversion to 1.15 to get timestamp headers
|
||||
openstack-api-version: placement 1.15
|
||||
data:
|
||||
resource_provider_generation: 0
|
||||
inventories:
|
||||
@ -415,6 +440,10 @@ tests:
|
||||
DISK_GB:
|
||||
total: 1024
|
||||
status: 200
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.resource_provider_generation: 1
|
||||
$.inventories.IPV4_ADDRESS.total: 253
|
||||
|
@ -39,13 +39,13 @@ tests:
|
||||
response_json_paths:
|
||||
$.errors[0].title: Not Acceptable
|
||||
|
||||
- name: latest microversion is 1.14
|
||||
- name: latest microversion is 1.15
|
||||
GET: /
|
||||
request_headers:
|
||||
openstack-api-version: placement latest
|
||||
response_headers:
|
||||
vary: /OpenStack-API-Version/
|
||||
openstack-api-version: placement 1.14
|
||||
openstack-api-version: placement 1.15
|
||||
|
||||
- name: other accept header bad version
|
||||
GET: /
|
||||
|
@ -0,0 +1,117 @@
|
||||
# Confirm the behavior and presence of last-modified headers for resource
|
||||
# classes across multiple microversions.
|
||||
#
|
||||
# We have the following routes, with associated microversion, and bodies.
|
||||
#
|
||||
# '/resource_classes': {
|
||||
# 'GET': resource_class.list_resource_classes,
|
||||
# v1.2, body
|
||||
# 'POST': resource_class.create_resource_class
|
||||
# v1.2, no body
|
||||
# },
|
||||
# '/resource_classes/{name}': {
|
||||
# 'GET': resource_class.get_resource_class,
|
||||
# v1.2, body
|
||||
# 'PUT': resource_class.update_resource_class,
|
||||
# v1.2, body, but time's arrow
|
||||
# v1.7, no body
|
||||
# 'DELETE': resource_class.delete_resource_class,
|
||||
# v1.2, no body
|
||||
# },
|
||||
#
|
||||
# This means that in 1.15 we only expect last-modified headers for
|
||||
# the two GET requests, for the other requests we should confirm it
|
||||
# is not there.
|
||||
|
||||
fixtures:
|
||||
- APIFixture
|
||||
|
||||
defaults:
|
||||
request_headers:
|
||||
x-auth-token: admin
|
||||
accept: application/json
|
||||
content-type: application/json
|
||||
openstack-api-version: placement 1.15
|
||||
|
||||
tests:
|
||||
|
||||
- name: get resource classes
|
||||
desc: last modified is now with standards only
|
||||
GET: /resource_classes
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: create a custom class
|
||||
PUT: /resource_classes/CUSTOM_MOO_MACHINE
|
||||
status: 201
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get custom class
|
||||
GET: $LAST_URL
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: get standard class
|
||||
GET: /resource_classes/VCPU
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: post a resource class
|
||||
POST: /resource_classes
|
||||
data:
|
||||
name: CUSTOM_ALPHA
|
||||
status: 201
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get resource classes including custom
|
||||
desc: last modified will still be now with customs because of standards
|
||||
GET: /resource_classes
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: put a resource class 1.6 microversion
|
||||
PUT: /resource_classes/CUSTOM_MOO_MACHINE
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.6
|
||||
data:
|
||||
name: CUSTOM_BETA
|
||||
status: 200
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get resource classes 1.14 microversion
|
||||
GET: /resource_classes
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get standard class 1.14 microversion
|
||||
GET: /resource_classes/VCPU
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get custom class 1.14 microversion
|
||||
GET: $LAST_URL
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
@ -12,8 +12,15 @@ tests:
|
||||
|
||||
- name: what is at resource providers
|
||||
GET: /resource_providers
|
||||
request_headers:
|
||||
# microversion 1.15 for cache headers
|
||||
openstack-api-version: placement 1.15
|
||||
response_json_paths:
|
||||
$.resource_providers: []
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: non admin forbidden
|
||||
GET: /resource_providers
|
||||
@ -78,6 +85,12 @@ tests:
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
content-type: application/json
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_json_paths:
|
||||
$.uuid: $ENVIRON['RP_UUID']
|
||||
$.name: $ENVIRON['RP_NAME']
|
||||
@ -104,6 +117,8 @@ tests:
|
||||
|
||||
- name: list one resource providers
|
||||
GET: /resource_providers
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_json_paths:
|
||||
$.resource_providers.`len`: 1
|
||||
$.resource_providers[0].uuid: $ENVIRON['RP_UUID']
|
||||
@ -113,6 +128,10 @@ tests:
|
||||
$.resource_providers[0].links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
|
||||
$.resource_providers[0].links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||
$.resource_providers[0].links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: filter out all resource providers by name
|
||||
GET: /resource_providers?name=flubblebubble
|
||||
@ -181,11 +200,15 @@ tests:
|
||||
PUT: /resource_providers/$RESPONSE['$.resource_providers[0].uuid']
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
openstack-api-version: placement 1.15
|
||||
data:
|
||||
name: new name
|
||||
status: 200
|
||||
response_headers:
|
||||
content-type: /application/json/
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
response_forbidden_headers:
|
||||
- location
|
||||
response_json_paths:
|
||||
@ -491,3 +514,11 @@ tests:
|
||||
- "Failed validating 'maxLength'"
|
||||
response_json_paths:
|
||||
$.errors[0].title: Bad Request
|
||||
|
||||
- name: confirm no cache-control headers before 1.15
|
||||
GET: /resource_providers
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
@ -5,7 +5,8 @@ fixtures:
|
||||
defaults:
|
||||
request_headers:
|
||||
x-auth-token: admin
|
||||
openstack-api-version: placement latest
|
||||
# traits introduced in 1.6
|
||||
openstack-api-version: placement 1.6
|
||||
|
||||
tests:
|
||||
|
||||
@ -34,6 +35,9 @@ tests:
|
||||
location: //traits/CUSTOM_TRAIT_1/
|
||||
response_forbidden_headers:
|
||||
- content-type
|
||||
# PUT in 1.6 version should not have cache headers
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: create a trait which existed
|
||||
PUT: /traits/CUSTOM_TRAIT_1
|
||||
@ -48,6 +52,9 @@ tests:
|
||||
status: 204
|
||||
response_forbidden_headers:
|
||||
- content-type
|
||||
# In early versions cache headers should not be present
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: get a non-existed trait
|
||||
GET: /traits/NON_EXISTED
|
||||
@ -56,6 +63,11 @@ tests:
|
||||
- name: delete a trait
|
||||
DELETE: /traits/CUSTOM_TRAIT_1
|
||||
status: 204
|
||||
response_forbidden_headers:
|
||||
- content-type
|
||||
# DELETE in any version should not have cache headers
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: delete a non-existed trait
|
||||
DELETE: /traits/CUSTOM_NON_EXSITED
|
||||
@ -133,6 +145,61 @@ tests:
|
||||
response_strings:
|
||||
- "Invalid query string parameters: Additional properties are not allowed"
|
||||
|
||||
- name: list traits 1.14 no cache headers
|
||||
GET: /traits
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: list traits 1.15 has cache headers
|
||||
GET: /traits
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: get trait 1.14 no cache headers
|
||||
GET: /traits/CUSTOM_TRAIT_1
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
status: 204
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: get trait 1.15 has cache headers
|
||||
GET: /traits/CUSTOM_TRAIT_1
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
status: 204
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: put trait 1.14 no cache headers
|
||||
PUT: /traits/CUSTOM_TRAIT_1
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
status: 204
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: put trait 1.15 has cache headers
|
||||
PUT: /traits/CUSTOM_TRAIT_1
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
status: 204
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: post new resource provider
|
||||
POST: /resource_providers
|
||||
request_headers:
|
||||
@ -152,6 +219,10 @@ tests:
|
||||
response_json_paths:
|
||||
$.resource_provider_generation: 0
|
||||
$.traits.`len`: 0
|
||||
response_forbidden_headers:
|
||||
# In 1.6 no cache headers
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: set traits for resource provider
|
||||
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
@ -169,6 +240,10 @@ tests:
|
||||
response_strings:
|
||||
- CUSTOM_TRAIT_1
|
||||
- CUSTOM_TRAIT_2
|
||||
response_forbidden_headers:
|
||||
# In 1.6 no cache headers
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: get associated traits
|
||||
GET: /traits?associated=true
|
||||
@ -272,3 +347,58 @@ tests:
|
||||
status: 404
|
||||
response_strings:
|
||||
- No resource provider with uuid non_existed found
|
||||
|
||||
- name: empty traits for resource provider 1.15 has cache headers
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: update rp trait 1.14 no cache headers
|
||||
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
data:
|
||||
traits:
|
||||
- CUSTOM_TRAIT_1
|
||||
- CUSTOM_TRAIT_2
|
||||
resource_provider_generation: 2
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
content-type: application/json
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: update rp trait 1.15 has cache headers
|
||||
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
data:
|
||||
traits:
|
||||
- CUSTOM_TRAIT_1
|
||||
- CUSTOM_TRAIT_2
|
||||
resource_provider_generation: 3
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
content-type: application/json
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: list traits for resource provider 1.14 no cache headers
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.14
|
||||
response_forbidden_headers:
|
||||
- cache-control
|
||||
- last-modified
|
||||
|
||||
- name: list traits for resource provider 1.15 has cache headers
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
@ -38,6 +38,21 @@ tests:
|
||||
response_json_paths:
|
||||
usages: {}
|
||||
|
||||
- name: get usages no cache headers base microversion
|
||||
GET: $LAST_URL
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
|
||||
- name: get usages cache headers 1.15
|
||||
GET: $LAST_URL
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
||||
- name: get total usages earlier version
|
||||
GET: /usages?project_id=$ENVIRON['PROJECT_ID']
|
||||
request_headers:
|
||||
|
@ -65,6 +65,10 @@ tests:
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.9
|
||||
status: 200
|
||||
# In pre 1.15 microversions cache headers not present
|
||||
response_forbidden_headers:
|
||||
- last-modified
|
||||
- cache-control
|
||||
response_json_paths:
|
||||
$.usages.DISK_GB: 20
|
||||
$.usages.VCPU: 1
|
||||
@ -87,3 +91,12 @@ tests:
|
||||
$.project_id: $ENVIRON['PROJECT_ID']
|
||||
$.user_id: $ENVIRON['USER_ID']
|
||||
$.`len`: 3
|
||||
|
||||
- name: get total usages with cache headers
|
||||
GET: /usages?project_id=$ENVIRON['PROJECT_ID']&user_id=$ENVIRON['ALT_USER_ID']
|
||||
request_headers:
|
||||
openstack-api-version: placement 1.15
|
||||
response_headers:
|
||||
cache-control: no-cache
|
||||
# Does last-modified look like a legit timestamp?
|
||||
last-modified: /^\w+, \d+ \w+ \d{4} [\d:]+ GMT$/
|
||||
|
@ -19,6 +19,7 @@ import webob
|
||||
|
||||
from nova.api.openstack.placement import handler
|
||||
from nova.api.openstack.placement.handlers import root
|
||||
from nova.api.openstack.placement import microversion
|
||||
from nova import test
|
||||
from nova.tests import uuidsentinel
|
||||
|
||||
@ -35,6 +36,9 @@ def _environ(path='/moo', method='GET'):
|
||||
'SERVER_NAME': 'example.com',
|
||||
'SERVER_PORT': '80',
|
||||
'wsgi.url_scheme': 'http',
|
||||
# The microversion version value is not used, but it
|
||||
# needs to be set to avoid a KeyError.
|
||||
microversion.MICROVERSION_ENVIRON: microversion.Version(1, 12),
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,8 +13,12 @@
|
||||
"""Unit tests for the utility functions used by the placement API."""
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
from oslo_middleware import request_id
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
import six.moves.urllib.parse as urlparse
|
||||
@ -594,3 +598,83 @@ class TestParseQsResourcesAndTraits(test.NoDBTestCase):
|
||||
'&required2=CUSTOM_SWITCH_BIG,CUSTOM_PHYSNET_PROD'
|
||||
'&resources3=CUSTOM_MAGIC:123')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.do_parse, qs)
|
||||
|
||||
|
||||
class TestPickLastModified(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPickLastModified, self).setUp()
|
||||
self.resource_provider = rp_obj.ResourceProvider(
|
||||
name=uuidsentinel.rp_name, uuid=uuidsentinel.rp_uuid)
|
||||
|
||||
def test_updated_versus_none(self):
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
self.resource_provider.updated_at = now
|
||||
self.resource_provider.created_at = now
|
||||
chosen_time = util.pick_last_modified(None, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
|
||||
def test_created_versus_none(self):
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
self.resource_provider.created_at = now
|
||||
self.resource_provider.updated_at = None
|
||||
chosen_time = util.pick_last_modified(None, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
|
||||
def test_last_modified_less(self):
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
less = now - datetime.timedelta(seconds=300)
|
||||
self.resource_provider.updated_at = now
|
||||
self.resource_provider.created_at = now
|
||||
chosen_time = util.pick_last_modified(less, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
|
||||
def test_last_modified_more(self):
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
more = now + datetime.timedelta(seconds=300)
|
||||
self.resource_provider.updated_at = now
|
||||
self.resource_provider.created_at = now
|
||||
chosen_time = util.pick_last_modified(more, self.resource_provider)
|
||||
self.assertEqual(more, chosen_time)
|
||||
|
||||
def test_last_modified_same(self):
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
self.resource_provider.updated_at = now
|
||||
self.resource_provider.created_at = now
|
||||
chosen_time = util.pick_last_modified(now, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
|
||||
def test_no_object_time_fields_less(self):
|
||||
# An unsaved ovo will not have the created_at or updated_at fields
|
||||
# present on the object at all.
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
less = now - datetime.timedelta(seconds=300)
|
||||
with mock.patch('oslo_utils.timeutils.utcnow') as mock_utc:
|
||||
mock_utc.return_value = now
|
||||
chosen_time = util.pick_last_modified(
|
||||
less, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
mock_utc.assert_called_once_with(with_timezone=True)
|
||||
|
||||
def test_no_object_time_fields_more(self):
|
||||
# An unsaved ovo will not have the created_at or updated_at fields
|
||||
# present on the object at all.
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
more = now + datetime.timedelta(seconds=300)
|
||||
with mock.patch('oslo_utils.timeutils.utcnow') as mock_utc:
|
||||
mock_utc.return_value = now
|
||||
chosen_time = util.pick_last_modified(
|
||||
more, self.resource_provider)
|
||||
self.assertEqual(more, chosen_time)
|
||||
mock_utc.assert_called_once_with(with_timezone=True)
|
||||
|
||||
def test_no_object_time_fields_none(self):
|
||||
# An unsaved ovo will not have the created_at or updated_at fields
|
||||
# present on the object at all.
|
||||
now = timeutils.utcnow(with_timezone=True)
|
||||
with mock.patch('oslo_utils.timeutils.utcnow') as mock_utc:
|
||||
mock_utc.return_value = now
|
||||
chosen_time = util.pick_last_modified(
|
||||
None, self.resource_provider)
|
||||
self.assertEqual(now, chosen_time)
|
||||
mock_utc.assert_called_once_with(with_timezone=True)
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Throughout the API, in microversion 1.15, 'last-modified' headers have been
|
||||
added to GET responses and those PUT and POST responses that have bodies.
|
||||
The value is either the actual last modified time of the most recently
|
||||
modified associated database entity or the current time if there is no
|
||||
direct mapping to the database. In addition, 'cache-control: no-cache'
|
||||
headers are added where the 'last-modified' header has been added to
|
||||
prevent inadvertent caching of resources.
|
Loading…
Reference in New Issue
Block a user