diff --git a/nova/api/openstack/placement/handler.py b/nova/api/openstack/placement/handler.py index 4862826e9212..7c8733ec68ce 100644 --- a/nova/api/openstack/placement/handler.py +++ b/nova/api/openstack/placement/handler.py @@ -28,6 +28,7 @@ import webob from oslo_log import log as logging +from nova.api.openstack.placement.handlers import aggregate from nova.api.openstack.placement.handlers import allocation from nova.api.openstack.placement.handlers import inventory from nova.api.openstack.placement.handlers import resource_provider @@ -79,6 +80,10 @@ ROUTE_DECLARATIONS = { '/resource_providers/{uuid}/usages': { 'GET': usage.list_usages }, + '/resource_providers/{uuid}/aggregates': { + 'GET': aggregate.get_aggregates, + 'PUT': aggregate.set_aggregates + }, '/resource_providers/{uuid}/allocations': { 'GET': allocation.list_for_resource_provider, }, diff --git a/nova/api/openstack/placement/handlers/aggregate.py b/nova/api/openstack/placement/handlers/aggregate.py new file mode 100644 index 000000000000..51eea051bbd4 --- /dev/null +++ b/nova/api/openstack/placement/handlers/aggregate.py @@ -0,0 +1,74 @@ +# 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. +"""Aggregate handlers for Placement API.""" + +from oslo_serialization import jsonutils +import webob + +from nova.api.openstack.placement import microversion +from nova.api.openstack.placement import util +from nova import objects + + +PUT_AGGREGATES_SCHEMA = { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": True +} + + +def _send_aggregates(response, aggregate_uuids): + response.status = 200 + response.body = jsonutils.dumps(_serialize_aggregates(aggregate_uuids)) + response.content_type = 'application/json' + return response + + +def _serialize_aggregates(aggregate_uuids): + return {'aggregates': aggregate_uuids} + + +@webob.dec.wsgify +@util.check_accept('application/json') +def get_aggregates(req): + """GET a list of aggregates associated with a resource provider. + + If the resource provider does not exist return a 404. + + On success return a 200 with an application/json body containing a + list of aggregate uuids. + """ + microversion.raise_404_if_not_version(req, (1, 1)) + context = req.environ['placement.context'] + uuid = util.wsgi_path_item(req.environ, 'uuid') + resource_provider = objects.ResourceProvider.get_by_uuid( + context, uuid) + aggregate_uuids = resource_provider.get_aggregates() + + return _send_aggregates(req.response, aggregate_uuids) + + +@webob.dec.wsgify +@util.require_content('application/json') +def set_aggregates(req): + microversion.raise_404_if_not_version(req, (1, 1)) + context = req.environ['placement.context'] + uuid = util.wsgi_path_item(req.environ, 'uuid') + resource_provider = objects.ResourceProvider.get_by_uuid( + context, uuid) + aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA) + resource_provider.set_aggregates(aggregate_uuids) + + return _send_aggregates(req.response, aggregate_uuids) diff --git a/nova/api/openstack/placement/microversion.py b/nova/api/openstack/placement/microversion.py index a0397402c65b..9618a01bdd3d 100644 --- a/nova/api/openstack/placement/microversion.py +++ b/nova/api/openstack/placement/microversion.py @@ -35,6 +35,7 @@ VERSIONED_METHODS = collections.defaultdict(list) # The Canonical Version List VERSIONS = [ '1.0', + '1.1', # initial support for aggregate.get_aggregates and set_aggregates ] @@ -68,6 +69,13 @@ def parse_version_string(version_string): version_string, exc)) +def raise_404_if_not_version(req, min_version, max_version=None): + """Utility to raise a 404 if the wanted microversion does not match.""" + want_version = req.environ[MICROVERSION_ENVIRON] + if not want_version.matches(min_version, max_version): + raise webob.exc.HTTPNotFound + + class MicroversionMiddleware(object): """WSGI middleware for getting microversion info.""" diff --git a/nova/api/openstack/placement/rest_api_version_history.rst b/nova/api/openstack/placement/rest_api_version_history.rst index 141bbdf89667..9fc3d8028738 100644 --- a/nova/api/openstack/placement/rest_api_version_history.rst +++ b/nova/api/openstack/placement/rest_api_version_history.rst @@ -17,3 +17,12 @@ Nova 14.0.0 (Newton). This contains the following routes: * /resource_providers/inventories * /resource_providers/usages * /allocations + +1.1 +___ + +The 1.1 version adds support for associating aggregates with +resource providers with ``GET`` and ``PUT`` methods on one new +route: + +* /resource_providers/{uuid}/aggregates diff --git a/nova/tests/functional/api/openstack/placement/gabbits/aggregate.yaml b/nova/tests/functional/api/openstack/placement/gabbits/aggregate.yaml new file mode 100644 index 000000000000..4444ecfccfe4 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/aggregate.yaml @@ -0,0 +1,104 @@ + +fixtures: + - APIFixture + +defaults: + request_headers: + x-auth-token: admin + content-type: application/json + openstack-api-version: placement latest + +vars: + - &agg_1 f918801a-5e54-4bee-9095-09a9d0c786b8 + - &agg_2 a893eb5c-e2a0-4251-ab26-f71d3b0cfc0b + +tests: +- name: get aggregates for bad resource provider + GET: /resource_providers/6984bb2d-830d-4c8d-ac64-c5a8103664be/aggregates + status: 404 + +- name: put aggregates for bad resource provider + PUT: /resource_providers/6984bb2d-830d-4c8d-ac64-c5a8103664be/aggregates + data: [] + status: 404 + +- name: post new resource provider + POST: /resource_providers + data: + name: $ENVIRON['RP_NAME'] + uuid: $ENVIRON['RP_UUID'] + status: 201 + response_headers: + location: //resource_providers/[a-f0-9-]+/ + +- name: get empty aggregates + GET: /resource_providers/$ENVIRON['RP_UUID']/aggregates + response_json_paths: + $.aggregates: [] + +- name: aggregates 404 for out of date microversion get + GET: /resource_providers/$ENVIRON['RP_UUID']/aggregates + request_headers: + openstack-api-version: placement 1.0 + status: 404 + +- name: aggregates 404 for out of date microversion put + PUT: /resource_providers/$ENVIRON['RP_UUID']/aggregates + request_headers: + openstack-api-version: placement 1.0 + status: 404 + +- name: put some aggregates + PUT: $LAST_URL + data: + - *agg_1 + - *agg_2 + status: 200 + response_headers: + content-type: /application/json/ + response_json_paths: + $.aggregates[0]: *agg_1 + $.aggregates[1]: *agg_2 + +- name: get those aggregates + GET: $LAST_URL + response_json_paths: + $.aggregates.`len`: 2 + +- name: clear those aggregates + PUT: $LAST_URL + data: [] + status: 200 + response_json_paths: + $.aggregates: [] + +- name: get empty aggregates again + GET: /resource_providers/$ENVIRON['RP_UUID']/aggregates + response_json_paths: + $.aggregates: [] + +- name: put non json + PUT: $LAST_URL + data: '{"bad", "not json"}' + status: 400 + response_strings: + - Malformed JSON + +- name: put invalid json not array + PUT: $LAST_URL + data: + aggregates: + - *agg_1 + - *agg_2 + status: 400 + response_strings: + - JSON does not validate + +- name: put invalid json not uuids + PUT: $LAST_URL + data: + - harry + - sally + status: 400 + response_strings: + - JSON does not validate diff --git a/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml b/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml index a23effcabfcf..5cb89e76f79f 100644 --- a/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml +++ b/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml @@ -37,13 +37,13 @@ tests: response_strings: - "Unacceptable version header: 0.5" -- name: latest microversion is 1.0 +- name: latest microversion is 1.1 GET: / request_headers: openstack-api-version: placement latest response_headers: vary: /OpenStack-API-Version/ - openstack-api-version: placement 1.0 + openstack-api-version: placement 1.1 - name: other accept header bad version GET: /