diff --git a/nova/api/openstack/placement/handler.py b/nova/api/openstack/placement/handler.py index f8d5da5f2f97..d2ddc4fa69bf 100644 --- a/nova/api/openstack/placement/handler.py +++ b/nova/api/openstack/placement/handler.py @@ -29,6 +29,7 @@ import webob from nova.api.openstack.placement.handlers import inventory from nova.api.openstack.placement.handlers import resource_provider from nova.api.openstack.placement.handlers import root +from nova.api.openstack.placement.handlers import usage from nova.api.openstack.placement import util from nova import exception @@ -61,6 +62,9 @@ ROUTE_DECLARATIONS = { 'PUT': inventory.update_inventory, 'DELETE': inventory.delete_inventory }, + '/resource_providers/{uuid}/usages': { + 'GET': usage.list_usages + }, } diff --git a/nova/api/openstack/placement/handlers/usage.py b/nova/api/openstack/placement/handlers/usage.py new file mode 100644 index 000000000000..aaa59e6b40b2 --- /dev/null +++ b/nova/api/openstack/placement/handlers/usage.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Placement API handlers for usage information.""" + +from oslo_serialization import jsonutils +import webob + +from nova.api.openstack.placement import util +from nova import objects + + +def _serialize_usages(resource_provider, usage): + usage_dict = {resource.resource_class: resource.usage + for resource in usage} + return {'resource_provider_generation': resource_provider.generation, + 'usages': usage_dict} + + +@webob.dec.wsgify +@util.check_accept('application/json') +def list_usages(req): + """GET a dictionary of resource provider usage by resource class. + + If the resource provider does not exist return a 404. + + On success return a 200 with an application/json representation of + the usage dictionary. + """ + context = req.environ['placement.context'] + uuid = util.wsgi_path_item(req.environ, 'uuid') + + # Resource provider object needed for two things: If it is + # NotFound we'll get a 404 here, which needs to happen because + # get_all_by_resource_provider_uuid can return an empty list. + # It is also needed for the generation, used in the outgoing + # representation. + resource_provider = objects.ResourceProvider.get_by_uuid( + context, uuid) + usage = objects.UsageList.get_all_by_resource_provider_uuid( + context, uuid) + + response = req.response + response.body = jsonutils.dumps( + _serialize_usages(resource_provider, usage)) + req.response.content_type = 'application/json' + return req.response diff --git a/nova/tests/functional/api/openstack/placement/fixtures.py b/nova/tests/functional/api/openstack/placement/fixtures.py index 0a16c08a5ad3..8eef396c1d85 100644 --- a/nova/tests/functional/api/openstack/placement/fixtures.py +++ b/nova/tests/functional/api/openstack/placement/fixtures.py @@ -18,6 +18,8 @@ from oslo_utils import uuidutils from nova.api.openstack.placement import deploy from nova import conf from nova import config +from nova import context +from nova import objects from nova.tests import fixtures @@ -66,3 +68,34 @@ class APIFixture(fixture.GabbiFixture): self.main_db_fixture.cleanup() if self.conf: self.conf.reset() + + +class AllocationFixture(APIFixture): + """An APIFixture that has some pre-made Allocations.""" + + def start_fixture(self): + super(AllocationFixture, self).start_fixture() + self.context = context.get_admin_context() + # Stealing from the super + rp_name = os.environ['RP_NAME'] + rp_uuid = os.environ['RP_UUID'] + rp = objects.ResourceProvider( + self.context, name=rp_name, uuid=rp_uuid) + rp.create() + inventory = objects.Inventory( + self.context, resource_provider=rp, + resource_class='DISK_GB', total=2048) + inventory.obj_set_defaults() + rp.add_inventory(inventory) + allocation = objects.Allocation( + self.context, resource_provider=rp, + resource_class='DISK_GB', + consumer_id=uuidutils.generate_uuid(), + used=512) + allocation.create() + allocation = objects.Allocation( + self.context, resource_provider=rp, + resource_class='DISK_GB', + consumer_id=uuidutils.generate_uuid(), + used=512) + allocation.create() diff --git a/nova/tests/functional/api/openstack/placement/gabbits/usage.yaml b/nova/tests/functional/api/openstack/placement/gabbits/usage.yaml new file mode 100644 index 000000000000..ef1952e1d4f4 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/usage.yaml @@ -0,0 +1,34 @@ +# More interesting tests for usages are in with_allocations + +fixtures: + - APIFixture + +defaults: + request_headers: + x-auth-token: admin + +tests: + +- name: fail to get usages for missing provider + GET: /resource_providers/fae14fa3-4b43-498c-a33c-4a1d00edb577/usages + status: 404 + +- name: create provider + POST: /resource_providers + request_headers: + content-type: application/json + data: + name: a name + status: 201 + +- name: check provider exists + GET: $LOCATION + response_json_paths: + name: a name + +- name: get empty usages + GET: $LAST_URL/usages + request_headers: + content-type: application/json + response_json_paths: + usages: {} diff --git a/nova/tests/functional/api/openstack/placement/gabbits/with-allocations.yaml b/nova/tests/functional/api/openstack/placement/gabbits/with-allocations.yaml new file mode 100644 index 000000000000..79f61efd341c --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/with-allocations.yaml @@ -0,0 +1,25 @@ + +fixtures: + - AllocationFixture + +defaults: + request_headers: + x-auth-token: admin + +tests: + +- name: confirm inventories + GET: /resource_providers/$ENVIRON['RP_UUID']/inventories + response_json_paths: + $.inventories.DISK_GB.total: 2048 + $.inventories.DISK_GB.reserved: 0 + +- name: get usages + GET: /resource_providers/$ENVIRON['RP_UUID']/usages + response_headers: + # use a regex here because charset, which is not only not + # required but superfluous, is present + content-type: /application/json/ + response_json_paths: + $.resource_provider_generation: 1 + $.usages.DISK_GB: 1024