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_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
from nova.api.openstack.placement import util
|
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.status = 200
|
||||||
response.body = encodeutils.to_utf8(
|
response.body = encodeutils.to_utf8(
|
||||||
jsonutils.dumps(_serialize_aggregates(aggregate_uuids)))
|
jsonutils.dumps(_serialize_aggregates(aggregate_uuids)))
|
||||||
response.content_type = 'application/json'
|
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
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +69,7 @@ def get_aggregates(req):
|
|||||||
context, uuid)
|
context, uuid)
|
||||||
aggregate_uuids = resource_provider.get_aggregates()
|
aggregate_uuids = resource_provider.get_aggregates()
|
||||||
|
|
||||||
return _send_aggregates(req.response, aggregate_uuids)
|
return _send_aggregates(req, aggregate_uuids)
|
||||||
|
|
||||||
|
|
||||||
@wsgi_wrapper.PlacementWsgify
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -73,4 +83,4 @@ def set_aggregates(req):
|
|||||||
aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA)
|
aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA)
|
||||||
resource_provider.set_aggregates(aggregate_uuids)
|
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_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
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."""
|
"""Turn allocations into a dict of resources keyed by key_fetcher."""
|
||||||
allocation_data = collections.defaultdict(dict)
|
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:
|
for allocation in allocations:
|
||||||
|
if get_last_modified:
|
||||||
|
last_modified = util.pick_last_modified(last_modified, allocation)
|
||||||
key = key_fetcher(allocation)
|
key = key_fetcher(allocation)
|
||||||
if 'resources' not in allocation_data[key]:
|
if 'resources' not in allocation_data[key]:
|
||||||
allocation_data[key]['resources'] = {}
|
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['project_id'] = allocations[0].project_id
|
||||||
result['user_id'] = allocations[0].user_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):
|
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):
|
def list_for_consumer(req):
|
||||||
"""List allocations associated with a consumer."""
|
"""List allocations associated with a consumer."""
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
consumer_id = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
consumer_id = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
||||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
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(
|
allocations = rp_obj.AllocationList.get_all_by_consumer_id(
|
||||||
context, consumer_id)
|
context, consumer_id)
|
||||||
|
|
||||||
allocations_json = jsonutils.dumps(
|
output, last_modified = _serialize_allocations_for_consumer(
|
||||||
_serialize_allocations_for_consumer(allocations, want_version))
|
allocations, want_version)
|
||||||
|
allocations_json = jsonutils.dumps(output)
|
||||||
|
|
||||||
req.response.status = 200
|
response = req.response
|
||||||
req.response.body = encodeutils.to_utf8(allocations_json)
|
response.status = 200
|
||||||
req.response.content_type = 'application/json'
|
response.body = encodeutils.to_utf8(allocations_json)
|
||||||
return req.response
|
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
|
@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
|
# using a dict of dicts for the output we are potentially limiting
|
||||||
# ourselves in terms of sorting and filtering.
|
# ourselves in terms of sorting and filtering.
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
|
|
||||||
# confirm existence of resource provider so we get a reasonable
|
# 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)
|
allocs = rp_obj.AllocationList.get_all_by_resource_provider(context, rp)
|
||||||
|
|
||||||
allocations_json = jsonutils.dumps(
|
output, last_modified = _serialize_allocations_for_resource_provider(
|
||||||
_serialize_allocations_for_resource_provider(allocs, rp))
|
allocs, rp)
|
||||||
|
allocations_json = jsonutils.dumps(output)
|
||||||
|
|
||||||
req.response.status = 200
|
response = req.response
|
||||||
req.response.body = encodeutils.to_utf8(allocations_json)
|
response.status = 200
|
||||||
req.response.content_type = 'application/json'
|
response.body = encodeutils.to_utf8(allocations_json)
|
||||||
return req.response
|
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,
|
def _new_allocations(context, resource_provider_uuid, consumer_uuid,
|
||||||
|
@ -17,6 +17,7 @@ import collections
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@ -224,4 +225,7 @@ def list_allocation_candidates(req):
|
|||||||
json_data = jsonutils.dumps(trx_cands)
|
json_data = jsonutils.dumps(trx_cands)
|
||||||
response.body = encodeutils.to_utf8(json_data)
|
response.body = encodeutils.to_utf8(json_data)
|
||||||
response.content_type = 'application/json'
|
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
|
return response
|
||||||
|
@ -161,21 +161,33 @@ def _make_inventory_object(resource_provider, resource_class, **data):
|
|||||||
return inventory
|
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."""
|
"""Send a JSON representation of a list of inventories."""
|
||||||
|
response = req.response
|
||||||
response.status = 200
|
response.status = 200
|
||||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
output, last_modified = _serialize_inventories(
|
||||||
_serialize_inventories(inventories, resource_provider.generation)))
|
inventories, resource_provider.generation)
|
||||||
|
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||||
response.content_type = 'application/json'
|
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
|
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."""
|
"""Send a JSON representation of one single inventory."""
|
||||||
|
response = req.response
|
||||||
response.status = status
|
response.status = status
|
||||||
response.body = encodeutils.to_utf8(jsonutils.dumps(_serialize_inventory(
|
response.body = encodeutils.to_utf8(jsonutils.dumps(_serialize_inventory(
|
||||||
inventory, generation=resource_provider.generation)))
|
inventory, generation=resource_provider.generation)))
|
||||||
response.content_type = 'application/json'
|
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
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -195,11 +207,13 @@ def _serialize_inventories(inventories, generation):
|
|||||||
inventories_by_class = {inventory.resource_class: inventory
|
inventories_by_class = {inventory.resource_class: inventory
|
||||||
for inventory in inventories}
|
for inventory in inventories}
|
||||||
inventories_dict = {}
|
inventories_dict = {}
|
||||||
|
last_modified = None
|
||||||
for resource_class, inventory in inventories_by_class.items():
|
for resource_class, inventory in inventories_by_class.items():
|
||||||
|
last_modified = util.pick_last_modified(last_modified, inventory)
|
||||||
inventories_dict[resource_class] = _serialize_inventory(
|
inventories_dict[resource_class] = _serialize_inventory(
|
||||||
inventory, generation=None)
|
inventory, generation=None)
|
||||||
return {'resource_provider_generation': generation,
|
return ({'resource_provider_generation': generation,
|
||||||
'inventories': inventories_dict}
|
'inventories': inventories_dict}, last_modified)
|
||||||
|
|
||||||
|
|
||||||
@wsgi_wrapper.PlacementWsgify
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -238,7 +252,7 @@ def create_inventory(req):
|
|||||||
response = req.response
|
response = req.response
|
||||||
response.location = util.inventory_url(
|
response.location = util.inventory_url(
|
||||||
req.environ, resource_provider, resource_class)
|
req.environ, resource_provider, resource_class)
|
||||||
return _send_inventory(response, resource_provider, inventory,
|
return _send_inventory(req, resource_provider, inventory,
|
||||||
status=201)
|
status=201)
|
||||||
|
|
||||||
|
|
||||||
@ -294,7 +308,7 @@ def get_inventories(req):
|
|||||||
|
|
||||||
inv_list = rp_obj.InventoryList.get_all_by_resource_provider(context, rp)
|
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
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -323,7 +337,7 @@ def get_inventory(req):
|
|||||||
_('No inventory of class %(class)s for %(rp_uuid)s') %
|
_('No inventory of class %(class)s for %(rp_uuid)s') %
|
||||||
{'class': resource_class, 'rp_uuid': uuid})
|
{'class': resource_class, 'rp_uuid': uuid})
|
||||||
|
|
||||||
return _send_inventory(req.response, rp, inventory)
|
return _send_inventory(req, rp, inventory)
|
||||||
|
|
||||||
|
|
||||||
@wsgi_wrapper.PlacementWsgify
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -383,7 +397,7 @@ def set_inventories(req):
|
|||||||
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
||||||
'error': exc})
|
'error': exc})
|
||||||
|
|
||||||
return _send_inventories(req.response, resource_provider, inventories)
|
return _send_inventories(req, resource_provider, inventories)
|
||||||
|
|
||||||
|
|
||||||
@wsgi_wrapper.PlacementWsgify
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -468,4 +482,4 @@ def update_inventory(req):
|
|||||||
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
||||||
'error': exc})
|
'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_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@ -56,12 +57,17 @@ def _serialize_resource_class(environ, rc):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def _serialize_resource_classes(environ, rcs):
|
def _serialize_resource_classes(environ, rcs, want_version):
|
||||||
output = []
|
output = []
|
||||||
|
last_modified = None
|
||||||
|
get_last_modified = want_version.matches((1, 15))
|
||||||
for rc in rcs:
|
for rc in rcs:
|
||||||
|
if get_last_modified:
|
||||||
|
last_modified = util.pick_last_modified(last_modified, rc)
|
||||||
data = _serialize_resource_class(environ, rc)
|
data = _serialize_resource_class(environ, rc)
|
||||||
output.append(data)
|
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
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -131,6 +137,7 @@ def get_resource_class(req):
|
|||||||
"""
|
"""
|
||||||
name = util.wsgi_path_item(req.environ, 'name')
|
name = util.wsgi_path_item(req.environ, 'name')
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
# The containing application will catch a not found here.
|
# The containing application will catch a not found here.
|
||||||
rc = rp_obj.ResourceClass.get_by_name(context, name)
|
rc = rp_obj.ResourceClass.get_by_name(context, name)
|
||||||
|
|
||||||
@ -138,6 +145,13 @@ def get_resource_class(req):
|
|||||||
_serialize_resource_class(req.environ, rc))
|
_serialize_resource_class(req.environ, rc))
|
||||||
)
|
)
|
||||||
req.response.content_type = 'application/json'
|
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
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
@ -151,13 +165,17 @@ def list_resource_classes(req):
|
|||||||
a collection of resource classes.
|
a collection of resource classes.
|
||||||
"""
|
"""
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
rcs = rp_obj.ResourceClassList.get_all(context)
|
rcs = rp_obj.ResourceClassList.get_all(context)
|
||||||
|
|
||||||
response = req.response
|
response = req.response
|
||||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
output, last_modified = _serialize_resource_classes(
|
||||||
_serialize_resource_classes(req.environ, rcs))
|
req.environ, rcs, want_version)
|
||||||
)
|
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
|
if want_version.matches((1, 15)):
|
||||||
|
response.last_modified = last_modified
|
||||||
|
response.cache_control = 'no-cache'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import copy
|
|||||||
from oslo_db import exception as db_exc
|
from oslo_db import exception as db_exc
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -140,10 +141,15 @@ def _serialize_provider(environ, resource_provider, want_version):
|
|||||||
|
|
||||||
def _serialize_providers(environ, resource_providers, want_version):
|
def _serialize_providers(environ, resource_providers, want_version):
|
||||||
output = []
|
output = []
|
||||||
|
last_modified = None
|
||||||
|
get_last_modified = want_version.matches((1, 15))
|
||||||
for provider in resource_providers:
|
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)
|
provider_data = _serialize_provider(environ, provider, want_version)
|
||||||
output.append(provider_data)
|
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
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -219,6 +225,7 @@ def get_resource_provider(req):
|
|||||||
On success return a 200 with an application/json body representing
|
On success return a 200 with an application/json body representing
|
||||||
the resource provider.
|
the resource provider.
|
||||||
"""
|
"""
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
# The containing application will catch a not found here.
|
# The containing application will catch a not found here.
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
@ -226,11 +233,15 @@ def get_resource_provider(req):
|
|||||||
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
|
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
|
||||||
context, uuid)
|
context, uuid)
|
||||||
|
|
||||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
response = req.response
|
||||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(
|
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||||
_serialize_provider(req.environ, resource_provider, want_version)))
|
_serialize_provider(req.environ, resource_provider, want_version)))
|
||||||
req.response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
return req.response
|
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
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@ -287,9 +298,13 @@ def list_resource_providers(req):
|
|||||||
{'error': exc})
|
{'error': exc})
|
||||||
|
|
||||||
response = req.response
|
response = req.response
|
||||||
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
output, last_modified = _serialize_providers(
|
||||||
_serialize_providers(req.environ, resource_providers, want_version)))
|
req.environ, resource_providers, want_version)
|
||||||
|
response.body = encodeutils.to_utf8(jsonutils.dumps(output))
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
|
if want_version.matches((1, 15)):
|
||||||
|
response.last_modified = last_modified
|
||||||
|
response.cache_control = 'no-cache'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -303,6 +318,7 @@ def update_resource_provider(req):
|
|||||||
"""
|
"""
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
|
|
||||||
# The containing application will catch a not found here.
|
# The containing application will catch a not found here.
|
||||||
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
|
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') %
|
_('Unable to save resource provider %(rp_uuid)s: %(error)s') %
|
||||||
{'rp_uuid': uuid, 'error': exc})
|
{'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)))
|
_serialize_provider(req.environ, resource_provider, want_version)))
|
||||||
req.response.status = 200
|
response.content_type = 'application/json'
|
||||||
req.response.content_type = 'application/json'
|
if want_version.matches((1, 15)):
|
||||||
return req.response
|
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_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@ -21,6 +22,7 @@ from nova.api.openstack.placement import wsgi_wrapper
|
|||||||
|
|
||||||
@wsgi_wrapper.PlacementWsgify
|
@wsgi_wrapper.PlacementWsgify
|
||||||
def home(req):
|
def home(req):
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
min_version = microversion.min_version_string()
|
min_version = microversion.min_version_string()
|
||||||
max_version = microversion.max_version_string()
|
max_version = microversion.max_version_string()
|
||||||
# NOTE(cdent): As sections of the api are added, links can be
|
# 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]})
|
version_json = jsonutils.dumps({'versions': [version_data]})
|
||||||
req.response.body = encodeutils.to_utf8(version_json)
|
req.response.body = encodeutils.to_utf8(version_json)
|
||||||
req.response.content_type = 'application/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
|
return req.response
|
||||||
|
@ -16,6 +16,7 @@ import copy
|
|||||||
import jsonschema
|
import jsonschema
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@ -85,14 +86,26 @@ def _normalize_traits_qs_param(qs):
|
|||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
|
||||||
def _serialize_traits(traits):
|
def _serialize_traits(traits, want_version):
|
||||||
return {'traits': [trait.name for trait in traits]}
|
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
|
@wsgi_wrapper.PlacementWsgify
|
||||||
@microversion.version_handler('1.6')
|
@microversion.version_handler('1.6')
|
||||||
def put_trait(req):
|
def put_trait(req):
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
name = util.wsgi_path_item(req.environ, 'name')
|
name = util.wsgi_path_item(req.environ, 'name')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -110,10 +123,16 @@ def put_trait(req):
|
|||||||
trait.create()
|
trait.create()
|
||||||
req.response.status = 201
|
req.response.status = 201
|
||||||
except exception.TraitExists:
|
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.status = 204
|
||||||
|
|
||||||
req.response.content_type = None
|
req.response.content_type = None
|
||||||
req.response.location = util.trait_url(req.environ, trait)
|
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
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
@ -121,16 +140,20 @@ def put_trait(req):
|
|||||||
@microversion.version_handler('1.6')
|
@microversion.version_handler('1.6')
|
||||||
def get_trait(req):
|
def get_trait(req):
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
name = util.wsgi_path_item(req.environ, 'name')
|
name = util.wsgi_path_item(req.environ, 'name')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rp_obj.Trait.get_by_name(context, name)
|
trait = rp_obj.Trait.get_by_name(context, name)
|
||||||
except exception.TraitNotFound as ex:
|
except exception.TraitNotFound as ex:
|
||||||
raise webob.exc.HTTPNotFound(
|
raise webob.exc.HTTPNotFound(
|
||||||
explanation=ex.format_message())
|
explanation=ex.format_message())
|
||||||
|
|
||||||
req.response.status = 204
|
req.response.status = 204
|
||||||
req.response.content_type = None
|
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
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
@ -163,6 +186,7 @@ def delete_trait(req):
|
|||||||
@util.check_accept('application/json')
|
@util.check_accept('application/json')
|
||||||
def list_traits(req):
|
def list_traits(req):
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -185,8 +209,11 @@ def list_traits(req):
|
|||||||
|
|
||||||
traits = rp_obj.TraitList.get_all(context, filters)
|
traits = rp_obj.TraitList.get_all(context, filters)
|
||||||
req.response.status = 200
|
req.response.status = 200
|
||||||
req.response.body = encodeutils.to_utf8(
|
output, last_modified = _serialize_traits(traits, want_version)
|
||||||
jsonutils.dumps(_serialize_traits(traits)))
|
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'
|
req.response.content_type = 'application/json'
|
||||||
return req.response
|
return req.response
|
||||||
|
|
||||||
@ -196,6 +223,7 @@ def list_traits(req):
|
|||||||
@util.check_accept('application/json')
|
@util.check_accept('application/json')
|
||||||
def list_traits_for_resource_provider(req):
|
def list_traits_for_resource_provider(req):
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
|
|
||||||
# Resource provider object is needed for two things: If it is
|
# 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})
|
{'uuid': uuid, 'error': exc})
|
||||||
|
|
||||||
traits = rp_obj.TraitList.get_all_by_resource_provider(context, rp)
|
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
|
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.status = 200
|
||||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
||||||
req.response.content_type = 'application/json'
|
req.response.content_type = 'application/json'
|
||||||
@ -225,6 +257,7 @@ def list_traits_for_resource_provider(req):
|
|||||||
@util.require_content('application/json')
|
@util.require_content('application/json')
|
||||||
def update_traits_for_resource_provider(req):
|
def update_traits_for_resource_provider(req):
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
||||||
data = util.extract_json(req.body, SET_TRAITS_FOR_RP_SCHEMA)
|
data = util.extract_json(req.body, SET_TRAITS_FOR_RP_SCHEMA)
|
||||||
rp_gen = data['resource_provider_generation']
|
rp_gen = data['resource_provider_generation']
|
||||||
@ -248,9 +281,12 @@ def update_traits_for_resource_provider(req):
|
|||||||
|
|
||||||
resource_provider.set_traits(trait_objs)
|
resource_provider.set_traits(trait_objs)
|
||||||
|
|
||||||
response_body = _serialize_traits(trait_objs)
|
response_body, last_modified = _serialize_traits(trait_objs, want_version)
|
||||||
response_body[
|
response_body[
|
||||||
'resource_provider_generation'] = resource_provider.generation
|
'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.status = 200
|
||||||
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
|
||||||
req.response.content_type = 'application/json'
|
req.response.content_type = 'application/json'
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@ -64,6 +65,7 @@ def list_usages(req):
|
|||||||
"""
|
"""
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
uuid = util.wsgi_path_item(req.environ, 'uuid')
|
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
|
# Resource provider object needed for two things: If it is
|
||||||
# NotFound we'll get a 404 here, which needs to happen because
|
# 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(
|
response.body = encodeutils.to_utf8(jsonutils.dumps(
|
||||||
_serialize_usages(resource_provider, usage)))
|
_serialize_usages(resource_provider, usage)))
|
||||||
req.response.content_type = 'application/json'
|
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
|
return req.response
|
||||||
|
|
||||||
|
|
||||||
@ -99,6 +109,7 @@ def get_total_usages(req):
|
|||||||
Return 404 Not Found if the wanted microversion does not match.
|
Return 404 Not Found if the wanted microversion does not match.
|
||||||
"""
|
"""
|
||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
|
|
||||||
schema = GET_USAGES_SCHEMA_1_9
|
schema = GET_USAGES_SCHEMA_1_9
|
||||||
|
|
||||||
@ -115,4 +126,12 @@ def get_total_usages(req):
|
|||||||
for resource in usages}}
|
for resource in usages}}
|
||||||
response.body = encodeutils.to_utf8(jsonutils.dumps(usages_dict))
|
response.body = encodeutils.to_utf8(jsonutils.dumps(usages_dict))
|
||||||
req.response.content_type = 'application/json'
|
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
|
return req.response
|
||||||
|
@ -54,9 +54,9 @@ VERSIONS = [
|
|||||||
# as GET. The 'allocation_requests' format in GET
|
# as GET. The 'allocation_requests' format in GET
|
||||||
# /allocation_candidates is updated to be the same as well.
|
# /allocation_candidates is updated to be the same as well.
|
||||||
'1.13', # Adds POST /allocations to set allocations for multiple consumers
|
'1.13', # Adds POST /allocations to set allocations for multiple consumers
|
||||||
# as GET
|
|
||||||
'1.14', # Adds parent and root provider UUID on resource provider
|
'1.14', # Adds parent and root provider UUID on resource provider
|
||||||
# representation and 'in_tree' filter on GET /resource_providers
|
# 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``
|
/resource-providers`` API call. Supplying a UUID value for the ``in_tree``
|
||||||
parameter will cause all resource providers within the "provider tree" of the
|
parameter will cause all resource providers within the "provider tree" of the
|
||||||
provider matching ``<UUID>`` to be returned.
|
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
|
import jsonschema
|
||||||
from oslo_middleware import request_id
|
from oslo_middleware import request_id
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -124,6 +125,25 @@ def json_error_formatter(body, status, title, environ):
|
|||||||
return {'errors': [error_dict]}
|
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):
|
def require_content(content_type):
|
||||||
"""Decorator to require a content type in a handler."""
|
"""Decorator to require a content type in a handler."""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
|
@ -65,12 +65,19 @@ tests:
|
|||||||
status: 200
|
status: 200
|
||||||
response_headers:
|
response_headers:
|
||||||
content-type: /application/json/
|
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:
|
response_json_paths:
|
||||||
$.aggregates[0]: *agg_1
|
$.aggregates[0]: *agg_1
|
||||||
$.aggregates[1]: *agg_2
|
$.aggregates[1]: *agg_2
|
||||||
|
|
||||||
- name: get those aggregates
|
- name: get those aggregates
|
||||||
GET: $LAST_URL
|
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:
|
response_json_paths:
|
||||||
$.aggregates.`len`: 2
|
$.aggregates.`len`: 2
|
||||||
|
|
||||||
@ -117,3 +124,26 @@ tests:
|
|||||||
- JSON does not validate
|
- JSON does not validate
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Bad Request
|
$.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
|
# 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].capacity: 1900 # 1.0 * 2000 - 100G
|
||||||
$.provider_summaries["$ENVIRON['SS_UUID']"].resources[DISK_GB].used: 0
|
$.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
|
# Verify the 1.12 format of the allocation_requests sub object which
|
||||||
# changes from a list-list to dict-ish format.
|
# 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
|
# Verify that shared storage provider only has DISK_GB listed in the
|
||||||
# resource requests, but is listed twice
|
# resource requests, but is listed twice
|
||||||
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources[DISK_GB]: [100, 100]
|
$.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
|
DISK_GB: 2
|
||||||
VCPU: 8
|
VCPU: 8
|
||||||
status: 204
|
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
|
- name: get those allocations for consumer
|
||||||
GET: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
|
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
|
- Allocation for resource provider 'be8b9cba-e7db-4a12-a386-99b4242167fe' that does not exist
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Bad Request
|
$.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
|
$.errors[0].title: Not Found
|
||||||
response_strings:
|
response_strings:
|
||||||
- The resource could not be found.
|
- 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
|
- name: get that inventory
|
||||||
GET: $LOCATION
|
GET: $LOCATION
|
||||||
status: 200
|
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:
|
response_json_paths:
|
||||||
$.resource_provider_generation: 1
|
$.resource_provider_generation: 1
|
||||||
$.total: 2048
|
$.total: 2048
|
||||||
@ -175,6 +182,15 @@ tests:
|
|||||||
$.step_size: 10
|
$.step_size: 10
|
||||||
$.allocation_ratio: 1.0
|
$.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
|
- name: modify the inventory
|
||||||
PUT: $LAST_URL
|
PUT: $LAST_URL
|
||||||
request_headers:
|
request_headers:
|
||||||
@ -310,6 +326,13 @@ tests:
|
|||||||
|
|
||||||
- name: get list of inventories
|
- name: get list of inventories
|
||||||
GET: /resource_providers/$ENVIRON['RP_UUID']/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:
|
response_json_paths:
|
||||||
$.resource_provider_generation: 2
|
$.resource_provider_generation: 2
|
||||||
$.inventories.DISK_GB.total: 2048
|
$.inventories.DISK_GB.total: 2048
|
||||||
@ -407,6 +430,8 @@ tests:
|
|||||||
PUT: $LOCATION/inventories
|
PUT: $LOCATION/inventories
|
||||||
request_headers:
|
request_headers:
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
|
# set microversion to 1.15 to get timestamp headers
|
||||||
|
openstack-api-version: placement 1.15
|
||||||
data:
|
data:
|
||||||
resource_provider_generation: 0
|
resource_provider_generation: 0
|
||||||
inventories:
|
inventories:
|
||||||
@ -415,6 +440,10 @@ tests:
|
|||||||
DISK_GB:
|
DISK_GB:
|
||||||
total: 1024
|
total: 1024
|
||||||
status: 200
|
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:
|
response_json_paths:
|
||||||
$.resource_provider_generation: 1
|
$.resource_provider_generation: 1
|
||||||
$.inventories.IPV4_ADDRESS.total: 253
|
$.inventories.IPV4_ADDRESS.total: 253
|
||||||
|
@ -39,13 +39,13 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Not Acceptable
|
$.errors[0].title: Not Acceptable
|
||||||
|
|
||||||
- name: latest microversion is 1.14
|
- name: latest microversion is 1.15
|
||||||
GET: /
|
GET: /
|
||||||
request_headers:
|
request_headers:
|
||||||
openstack-api-version: placement latest
|
openstack-api-version: placement latest
|
||||||
response_headers:
|
response_headers:
|
||||||
vary: /OpenStack-API-Version/
|
vary: /OpenStack-API-Version/
|
||||||
openstack-api-version: placement 1.14
|
openstack-api-version: placement 1.15
|
||||||
|
|
||||||
- name: other accept header bad version
|
- name: other accept header bad version
|
||||||
GET: /
|
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
|
- name: what is at resource providers
|
||||||
GET: /resource_providers
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
# microversion 1.15 for cache headers
|
||||||
|
openstack-api-version: placement 1.15
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.resource_providers: []
|
$.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
|
- name: non admin forbidden
|
||||||
GET: /resource_providers
|
GET: /resource_providers
|
||||||
@ -78,6 +85,12 @@ tests:
|
|||||||
GET: /resource_providers/$ENVIRON['RP_UUID']
|
GET: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
request_headers:
|
request_headers:
|
||||||
content-type: application/json
|
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:
|
response_json_paths:
|
||||||
$.uuid: $ENVIRON['RP_UUID']
|
$.uuid: $ENVIRON['RP_UUID']
|
||||||
$.name: $ENVIRON['RP_NAME']
|
$.name: $ENVIRON['RP_NAME']
|
||||||
@ -104,6 +117,8 @@ tests:
|
|||||||
|
|
||||||
- name: list one resource providers
|
- name: list one resource providers
|
||||||
GET: /resource_providers
|
GET: /resource_providers
|
||||||
|
request_headers:
|
||||||
|
openstack-api-version: placement 1.15
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.resource_providers.`len`: 1
|
$.resource_providers.`len`: 1
|
||||||
$.resource_providers[0].uuid: $ENVIRON['RP_UUID']
|
$.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 = "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 = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||||
$.resource_providers[0].links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
|
$.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
|
- name: filter out all resource providers by name
|
||||||
GET: /resource_providers?name=flubblebubble
|
GET: /resource_providers?name=flubblebubble
|
||||||
@ -181,11 +200,15 @@ tests:
|
|||||||
PUT: /resource_providers/$RESPONSE['$.resource_providers[0].uuid']
|
PUT: /resource_providers/$RESPONSE['$.resource_providers[0].uuid']
|
||||||
request_headers:
|
request_headers:
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
|
openstack-api-version: placement 1.15
|
||||||
data:
|
data:
|
||||||
name: new name
|
name: new name
|
||||||
status: 200
|
status: 200
|
||||||
response_headers:
|
response_headers:
|
||||||
content-type: /application/json/
|
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:
|
response_forbidden_headers:
|
||||||
- location
|
- location
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
@ -491,3 +514,11 @@ tests:
|
|||||||
- "Failed validating 'maxLength'"
|
- "Failed validating 'maxLength'"
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Bad Request
|
$.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:
|
defaults:
|
||||||
request_headers:
|
request_headers:
|
||||||
x-auth-token: admin
|
x-auth-token: admin
|
||||||
openstack-api-version: placement latest
|
# traits introduced in 1.6
|
||||||
|
openstack-api-version: placement 1.6
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ tests:
|
|||||||
location: //traits/CUSTOM_TRAIT_1/
|
location: //traits/CUSTOM_TRAIT_1/
|
||||||
response_forbidden_headers:
|
response_forbidden_headers:
|
||||||
- content-type
|
- content-type
|
||||||
|
# PUT in 1.6 version should not have cache headers
|
||||||
|
- cache-control
|
||||||
|
- last-modified
|
||||||
|
|
||||||
- name: create a trait which existed
|
- name: create a trait which existed
|
||||||
PUT: /traits/CUSTOM_TRAIT_1
|
PUT: /traits/CUSTOM_TRAIT_1
|
||||||
@ -48,6 +52,9 @@ tests:
|
|||||||
status: 204
|
status: 204
|
||||||
response_forbidden_headers:
|
response_forbidden_headers:
|
||||||
- content-type
|
- content-type
|
||||||
|
# In early versions cache headers should not be present
|
||||||
|
- cache-control
|
||||||
|
- last-modified
|
||||||
|
|
||||||
- name: get a non-existed trait
|
- name: get a non-existed trait
|
||||||
GET: /traits/NON_EXISTED
|
GET: /traits/NON_EXISTED
|
||||||
@ -56,6 +63,11 @@ tests:
|
|||||||
- name: delete a trait
|
- name: delete a trait
|
||||||
DELETE: /traits/CUSTOM_TRAIT_1
|
DELETE: /traits/CUSTOM_TRAIT_1
|
||||||
status: 204
|
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
|
- name: delete a non-existed trait
|
||||||
DELETE: /traits/CUSTOM_NON_EXSITED
|
DELETE: /traits/CUSTOM_NON_EXSITED
|
||||||
@ -133,6 +145,61 @@ tests:
|
|||||||
response_strings:
|
response_strings:
|
||||||
- "Invalid query string parameters: Additional properties are not allowed"
|
- "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
|
- name: post new resource provider
|
||||||
POST: /resource_providers
|
POST: /resource_providers
|
||||||
request_headers:
|
request_headers:
|
||||||
@ -152,6 +219,10 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.resource_provider_generation: 0
|
$.resource_provider_generation: 0
|
||||||
$.traits.`len`: 0
|
$.traits.`len`: 0
|
||||||
|
response_forbidden_headers:
|
||||||
|
# In 1.6 no cache headers
|
||||||
|
- cache-control
|
||||||
|
- last-modified
|
||||||
|
|
||||||
- name: set traits for resource provider
|
- name: set traits for resource provider
|
||||||
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
|
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
|
||||||
@ -169,6 +240,10 @@ tests:
|
|||||||
response_strings:
|
response_strings:
|
||||||
- CUSTOM_TRAIT_1
|
- CUSTOM_TRAIT_1
|
||||||
- CUSTOM_TRAIT_2
|
- CUSTOM_TRAIT_2
|
||||||
|
response_forbidden_headers:
|
||||||
|
# In 1.6 no cache headers
|
||||||
|
- cache-control
|
||||||
|
- last-modified
|
||||||
|
|
||||||
- name: get associated traits
|
- name: get associated traits
|
||||||
GET: /traits?associated=true
|
GET: /traits?associated=true
|
||||||
@ -272,3 +347,58 @@ tests:
|
|||||||
status: 404
|
status: 404
|
||||||
response_strings:
|
response_strings:
|
||||||
- No resource provider with uuid non_existed found
|
- 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:
|
response_json_paths:
|
||||||
usages: {}
|
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
|
- name: get total usages earlier version
|
||||||
GET: /usages?project_id=$ENVIRON['PROJECT_ID']
|
GET: /usages?project_id=$ENVIRON['PROJECT_ID']
|
||||||
request_headers:
|
request_headers:
|
||||||
|
@ -65,6 +65,10 @@ tests:
|
|||||||
request_headers:
|
request_headers:
|
||||||
openstack-api-version: placement 1.9
|
openstack-api-version: placement 1.9
|
||||||
status: 200
|
status: 200
|
||||||
|
# In pre 1.15 microversions cache headers not present
|
||||||
|
response_forbidden_headers:
|
||||||
|
- last-modified
|
||||||
|
- cache-control
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.usages.DISK_GB: 20
|
$.usages.DISK_GB: 20
|
||||||
$.usages.VCPU: 1
|
$.usages.VCPU: 1
|
||||||
@ -87,3 +91,12 @@ tests:
|
|||||||
$.project_id: $ENVIRON['PROJECT_ID']
|
$.project_id: $ENVIRON['PROJECT_ID']
|
||||||
$.user_id: $ENVIRON['USER_ID']
|
$.user_id: $ENVIRON['USER_ID']
|
||||||
$.`len`: 3
|
$.`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 import handler
|
||||||
from nova.api.openstack.placement.handlers import root
|
from nova.api.openstack.placement.handlers import root
|
||||||
|
from nova.api.openstack.placement import microversion
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests import uuidsentinel
|
from nova.tests import uuidsentinel
|
||||||
|
|
||||||
@ -35,6 +36,9 @@ def _environ(path='/moo', method='GET'):
|
|||||||
'SERVER_NAME': 'example.com',
|
'SERVER_NAME': 'example.com',
|
||||||
'SERVER_PORT': '80',
|
'SERVER_PORT': '80',
|
||||||
'wsgi.url_scheme': 'http',
|
'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."""
|
"""Unit tests for the utility functions used by the placement API."""
|
||||||
|
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
|
import mock
|
||||||
from oslo_middleware import request_id
|
from oslo_middleware import request_id
|
||||||
|
from oslo_utils import timeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
import six.moves.urllib.parse as urlparse
|
import six.moves.urllib.parse as urlparse
|
||||||
@ -594,3 +598,83 @@ class TestParseQsResourcesAndTraits(test.NoDBTestCase):
|
|||||||
'&required2=CUSTOM_SWITCH_BIG,CUSTOM_PHYSNET_PROD'
|
'&required2=CUSTOM_SWITCH_BIG,CUSTOM_PHYSNET_PROD'
|
||||||
'&resources3=CUSTOM_MAGIC:123')
|
'&resources3=CUSTOM_MAGIC:123')
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.do_parse, qs)
|
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