From 339129870692467b703220dbc3905fd8bffe6a83 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 14 Oct 2019 08:11:27 -0700 Subject: [PATCH] Add image caching API for aggregates This adds a new microversion and support for requesting image pre-caching on an aggregate. Related to blueprint image-precache-support Change-Id: I4ab96095106b38737ed355fcad07e758f8b5a9b0 --- api-ref/source/os-aggregates.inc | 33 +++++++ api-ref/source/parameters.yaml | 5 + .../v2.81/aggregate-add-host-post-req.json | 5 + .../v2.81/aggregate-images-post-req.json | 6 ++ .../v2.81/aggregate-metadata-post-req.json | 9 ++ .../v2.81/aggregate-post-req.json | 7 ++ .../v2.81/aggregate-post-resp.json | 12 +++ .../v2.81/aggregate-remove-host-post-req.json | 5 + .../v2.81/aggregate-update-post-req.json | 7 ++ .../v2.81/aggregate-update-post-resp.json | 16 +++ .../v2.81/aggregates-add-host-post-resp.json | 18 ++++ .../v2.81/aggregates-get-resp.json | 16 +++ .../v2.81/aggregates-list-get-resp.json | 20 ++++ .../v2.81/aggregates-metadata-post-resp.json | 17 ++++ .../aggregates-remove-host-post-resp.json | 16 +++ .../versions/v21-version-get-resp.json | 2 +- .../versions/versions-get-resp.json | 2 +- nova/api/openstack/api_version_request.py | 4 +- nova/api/openstack/compute/aggregates.py | 30 ++++++ .../compute/rest_api_version_history.rst | 6 ++ nova/api/openstack/compute/routes.py | 3 + .../compute/schemas/aggregate_images.py | 34 +++++++ nova/policies/aggregates.py | 11 +++ .../aggregate-add-host-post-req.json.tpl | 5 + .../v2.81/aggregate-images-post-req.json.tpl | 6 ++ .../aggregate-metadata-post-req.json.tpl | 9 ++ .../v2.81/aggregate-post-req.json.tpl | 7 ++ .../v2.81/aggregate-post-resp.json.tpl | 12 +++ .../aggregate-remove-host-post-req.json.tpl | 5 + .../v2.81/aggregate-update-post-req.json.tpl | 7 ++ .../v2.81/aggregate-update-post-resp.json.tpl | 16 +++ .../aggregates-add-host-post-resp.json.tpl | 18 ++++ .../v2.81/aggregates-get-resp.json.tpl | 16 +++ .../v2.81/aggregates-list-get-resp.json.tpl | 20 ++++ .../aggregates-metadata-post-resp.json.tpl | 17 ++++ .../aggregates-remove-host-post-resp.json.tpl | 16 +++ .../api_sample_tests/test_aggregates.py | 21 ++++ nova/tests/functional/test_aggregates.py | 98 +++++++++++++++++++ nova/tests/unit/test_policy.py | 1 + .../image-precaching-d46506568fefa1ea.yaml | 15 +++ 40 files changed, 570 insertions(+), 3 deletions(-) create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-post-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregates-get-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json create mode 100644 doc/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json create mode 100644 nova/api/openstack/compute/schemas/aggregate_images.py create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-get-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json.tpl create mode 100644 releasenotes/notes/image-precaching-d46506568fefa1ea.yaml diff --git a/api-ref/source/os-aggregates.inc b/api-ref/source/os-aggregates.inc index 9e612ad06d84..6021c8e8607a 100644 --- a/api-ref/source/os-aggregates.inc +++ b/api-ref/source/os-aggregates.inc @@ -354,3 +354,36 @@ Response .. literalinclude:: ../../doc/api_samples/os-aggregates/v2.41/aggregates-metadata-post-resp.json :language: javascript + +Request Image Pre-caching for Aggregate +======================================= + +.. rest_method:: POST /os-aggregates/{aggregate_id}/images + +Requests that a set of images be pre-cached on compute nodes within the referenced aggregate. + +This API is available starting with microversion 2.81. + +Normal response codes: 202 + +Error response codes: badRequest(400), unauthorized(401), forbidden(403), +itemNotFound(404) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - aggregate_id: aggregate_id + - cache: cache + - cache.id: image_id_body + +**Example Request Image pre-caching for Aggregate (v2.81): JSON request** + +.. literalinclude:: ../../doc/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json + :language: javascript + +Response +-------- + +The response body is always empty. diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 2f22b0eda3d2..840160174066 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -1997,6 +1997,11 @@ boot_index: in: body required: true type: integer +cache: + description: A list of image objects to cache. + in: body + required: true + type: array certificate: description: | The certificate object. diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json new file mode 100644 index 000000000000..4e6bdfef3f4f --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json @@ -0,0 +1,5 @@ +{ + "add_host": { + "host": "compute" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json new file mode 100644 index 000000000000..8894e97f069f --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json @@ -0,0 +1,6 @@ +{ + "cache": + [ + {"id": "70a599e0-31e7-49b7-b260-868f441e862b"} + ] +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json new file mode 100644 index 000000000000..7331e06a8c0b --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json @@ -0,0 +1,9 @@ +{ + "set_metadata": + { + "metadata": + { + "key": "value" + } + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-post-req.json new file mode 100644 index 000000000000..624fe0c62916 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-post-req.json @@ -0,0 +1,7 @@ +{ + "aggregate": + { + "name": "name", + "availability_zone": "london" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-post-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregate-post-resp.json new file mode 100644 index 000000000000..2e399d9c6c4c --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-post-resp.json @@ -0,0 +1,12 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "2019-10-08T15:15:27.988513", + "deleted": false, + "deleted_at": null, + "id": 1, + "name": "name", + "updated_at": null, + "uuid": "a25e34a2-4fc1-4876-82d0-cf930fa04b82" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json new file mode 100644 index 000000000000..e42b053009e8 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json @@ -0,0 +1,5 @@ +{ + "remove_host": { + "host": "compute" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json b/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json new file mode 100644 index 000000000000..0af1a37a4d9c --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json @@ -0,0 +1,7 @@ +{ + "aggregate": + { + "name": "newname", + "availability_zone": "nova2" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json new file mode 100644 index 000000000000..350128a1a551 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "nova2", + "created_at": "2019-10-11T14:19:00.718841", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "nova2" + }, + "name": "newname", + "updated_at": "2019-10-11T14:19:00.785838", + "uuid": "4e7fa22f-f6cf-4e81-a5c7-6dc485815f81" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json new file mode 100644 index 000000000000..decbc8d365de --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json @@ -0,0 +1,18 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "2019-10-11T14:19:05.250053", + "deleted": false, + "deleted_at": null, + "hosts": [ + "compute" + ], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "47832b50-a192-4900-affe-8f7fdf2d7f22" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregates-get-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregates-get-resp.json new file mode 100644 index 000000000000..7d978bdf2759 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregates-get-resp.json @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "2019-10-11T14:19:07.366577", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "7c5ff84a-c901-4733-adf8-06875e265080" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json new file mode 100644 index 000000000000..e1b5f11539a6 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json @@ -0,0 +1,20 @@ +{ + "aggregates": [ + { + "availability_zone": "london", + "created_at": "2019-10-11T14:19:07.386637", + "deleted": false, + "deleted_at": null, + "hosts": [ + "compute" + ], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "070cb72c-f463-4f72-9c61-2c0556eb8c07" + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json new file mode 100644 index 000000000000..f0860dad8ec4 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json @@ -0,0 +1,17 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "2019-10-11T14:19:03.103465", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london", + "key": "value" + }, + "name": "name", + "updated_at": "2019-10-11T14:19:03.169058", + "uuid": "0843db7c-f161-446d-84c8-d936320da2e8" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json b/doc/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json new file mode 100644 index 000000000000..b9b5bdefcde9 --- /dev/null +++ b/doc/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "2019-10-11T14:19:05.250053", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "47832b50-a192-4900-affe-8f7fdf2d7f22" + } +} \ No newline at end of file diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index a604ada7be22..3877e25f57d8 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.80", + "version": "2.81", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index b9e556a075fd..6edc435a0ed4 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.80", + "version": "2.81", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index f95e52010f60..09038f416ab3 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -217,6 +217,8 @@ REST_API_VERSION_HISTORY = """REST API Version History: ``GET /os-migrations``, ``GET /servers/{server_id}/migrations``, and ``GET /servers/{server_id}/migrations/{migration_id}``. + * 2.81 - Adds support for image cache management by aggregate by adding + ``POST /os-aggregates/{aggregate_id}/images``. """ # The minimum and maximum versions of the API supported @@ -225,7 +227,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: # Note(cyeoh): This only applies for the v2.1 API once microversions # support is fully merged. It does not affect the V2 API. _MIN_API_VERSION = "2.1" -_MAX_API_VERSION = "2.80" +_MAX_API_VERSION = "2.81" DEFAULT_API_VERSION = _MIN_API_VERSION # Almost all proxy APIs which are related to network, images and baremetal diff --git a/nova/api/openstack/compute/aggregates.py b/nova/api/openstack/compute/aggregates.py index 4fce9e9ce2b8..ede4813d73c0 100644 --- a/nova/api/openstack/compute/aggregates.py +++ b/nova/api/openstack/compute/aggregates.py @@ -21,10 +21,12 @@ from webob import exc from nova.api.openstack import api_version_request from nova.api.openstack import common +from nova.api.openstack.compute.schemas import aggregate_images from nova.api.openstack.compute.schemas import aggregates from nova.api.openstack import wsgi from nova.api import validation from nova.compute import api as compute +from nova.conductor import api as conductor from nova import exception from nova.i18n import _ from nova.policies import aggregates as aggr_policies @@ -39,6 +41,7 @@ class AggregateController(wsgi.Controller): def __init__(self): super(AggregateController, self).__init__() self.api = compute.AggregateAPI() + self.conductor_tasks = conductor.ComputeTaskAPI() @wsgi.expected_errors(()) def index(self, req): @@ -222,3 +225,30 @@ class AggregateController(wsgi.Controller): key in aggregate.obj_extra_fields) and (show_uuid or key != 'uuid')): yield key, getattr(aggregate, key) + + @wsgi.Controller.api_version('2.81') + @wsgi.response(202) + @wsgi.expected_errors((400, 404)) + @validation.schema(aggregate_images.aggregate_images_v2_81) + def images(self, req, id, body): + """Allows image cache management requests.""" + context = _get_context(req) + context.can(aggr_policies.NEW_POLICY_ROOT % 'images') + + image_ids = [] + for image_req in body.get('cache'): + image_ids.append(image_req['id']) + + if image_ids != list(set(image_ids)): + raise exc.HTTPBadRequest( + explanation=_('Duplicate images in request')) + + try: + aggregate = self.api.get_aggregate(context, id) + except exception.AggregateNotFound as e: + raise exc.HTTPNotFound(explanation=e.format_message()) + + try: + self.conductor_tasks.cache_images(context, aggregate, image_ids) + except exception.NovaException as e: + raise exc.HTTPBadRequest(explanation=e.format_message()) diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst index 8fbb3dba9698..e0615bf6ee5c 100644 --- a/nova/api/openstack/compute/rest_api_version_history.rst +++ b/nova/api/openstack/compute/rest_api_version_history.rst @@ -1048,3 +1048,9 @@ project, for example: * ``GET /os-migrations?user_id=ef9d34b4-45d0-4530-871b-3fb535988394`` * ``GET /os-migrations?project_id=011ee9f4-8f16-4c38-8633-a254d420fd54`` * ``GET /os-migrations?user_id=ef9d34b4-45d0-4530-871b-3fb535988394&project_id=011ee9f4-8f16-4c38-8633-a254d420fd54`` + +2.81 +---- + +Adds support for image cache management by aggregate by adding +``POST /os-aggregates/{aggregate_id}/images``. diff --git a/nova/api/openstack/compute/routes.py b/nova/api/openstack/compute/routes.py index 763db39d9290..7a9271c66b55 100644 --- a/nova/api/openstack/compute/routes.py +++ b/nova/api/openstack/compute/routes.py @@ -454,6 +454,9 @@ ROUTE_LIST = ( ('/os-aggregates/{id}/action', { 'POST': [aggregates_controller, 'action'], }), + ('/os-aggregates/{id}/images', { + 'POST': [aggregates_controller, 'images'], + }), ('/os-assisted-volume-snapshots', { 'POST': [assisted_volume_snapshots_controller, 'create'] }), diff --git a/nova/api/openstack/compute/schemas/aggregate_images.py b/nova/api/openstack/compute/schemas/aggregate_images.py new file mode 100644 index 000000000000..b1b0cf84da73 --- /dev/null +++ b/nova/api/openstack/compute/schemas/aggregate_images.py @@ -0,0 +1,34 @@ +# 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. + +from nova.api.validation import parameter_types + + +aggregate_images_v2_81 = { + 'type': 'object', + 'properties': { + 'cache': { + 'type': ['array'], + 'minItems': 1, + 'items': { + 'type': 'object', + 'properties': { + 'id': parameter_types.image_id, + }, + 'additionalProperties': False, + 'required': ['id'], + }, + }, + }, + 'required': ['cache'], + 'additionalProperties': False, +} diff --git a/nova/policies/aggregates.py b/nova/policies/aggregates.py index 04fd22ee65e7..8065b00d8d16 100644 --- a/nova/policies/aggregates.py +++ b/nova/policies/aggregates.py @@ -19,6 +19,7 @@ from nova.policies import base POLICY_ROOT = 'os_compute_api:os-aggregates:%s' +NEW_POLICY_ROOT = 'compute:aggregates:%s' aggregates_policies = [ @@ -102,6 +103,16 @@ aggregates_policies = [ 'method': 'GET' } ]), + policy.DocumentedRuleDefault( + NEW_POLICY_ROOT % 'images', + base.RULE_ADMIN_API, + "Request image caching for an aggregate", + [ + { + 'path': '/os-aggregates/{aggregate_id}/images', + 'method': 'POST' + } + ]), ] diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json.tpl new file mode 100644 index 000000000000..97395bf2f22e --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-add-host-post-req.json.tpl @@ -0,0 +1,5 @@ +{ + "add_host": { + "host": "%(host_name)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json.tpl new file mode 100644 index 000000000000..be89474407bf --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-images-post-req.json.tpl @@ -0,0 +1,6 @@ +{ + "cache": + [ + {"id": "%(image_id)s"} + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json.tpl new file mode 100644 index 000000000000..63a2921cacc8 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-metadata-post-req.json.tpl @@ -0,0 +1,9 @@ +{ + "set_metadata": + { + "metadata": + { + "key": "value" + } + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-req.json.tpl new file mode 100644 index 000000000000..deeb3bc55a9e --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-req.json.tpl @@ -0,0 +1,7 @@ +{ + "aggregate": + { + "name": "name", + "availability_zone": "london" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-resp.json.tpl new file mode 100644 index 000000000000..fe125c1944a7 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-post-resp.json.tpl @@ -0,0 +1,12 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "id": %(aggregate_id)s, + "name": "name", + "updated_at": null, + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json.tpl new file mode 100644 index 000000000000..4663e5293119 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-remove-host-post-req.json.tpl @@ -0,0 +1,5 @@ +{ + "remove_host": { + "host": "%(host_name)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json.tpl new file mode 100644 index 000000000000..55e4b09346ea --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-req.json.tpl @@ -0,0 +1,7 @@ +{ + "aggregate": + { + "name": "newname", + "availability_zone": "nova2" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json.tpl new file mode 100644 index 000000000000..b10db24655fc --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregate-update-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "nova2", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "nova2" + }, + "name": "newname", + "updated_at": "%(strtime)s", + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json.tpl new file mode 100644 index 000000000000..9e08704ad165 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-add-host-post-resp.json.tpl @@ -0,0 +1,18 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [ + "%(compute_host)s" + ], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-get-resp.json.tpl new file mode 100644 index 000000000000..710a837db31e --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-get-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json.tpl new file mode 100644 index 000000000000..6ff95d01d862 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-list-get-resp.json.tpl @@ -0,0 +1,20 @@ +{ + "aggregates": [ + { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [ + "%(compute_host)s" + ], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "%(uuid)s" + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json.tpl new file mode 100644 index 000000000000..689c5fc91937 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-metadata-post-resp.json.tpl @@ -0,0 +1,17 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london", + "key": "value" + }, + "name": "name", + "updated_at": %(strtime)s, + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json.tpl new file mode 100644 index 000000000000..710a837db31e --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-aggregates/v2.81/aggregates-remove-host-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "aggregate": { + "availability_zone": "london", + "created_at": "%(strtime)s", + "deleted": false, + "deleted_at": null, + "hosts": [], + "id": 1, + "metadata": { + "availability_zone": "london" + }, + "name": "name", + "updated_at": null, + "uuid": "%(uuid)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/test_aggregates.py b/nova/tests/functional/api_sample_tests/test_aggregates.py index d5bb13abed93..81ca6b5083d1 100644 --- a/nova/tests/functional/api_sample_tests/test_aggregates.py +++ b/nova/tests/functional/api_sample_tests/test_aggregates.py @@ -16,6 +16,7 @@ from oslo_serialization import jsonutils from nova.tests.functional.api_sample_tests import api_sample_base +from nova.tests.unit.image import fake as fake_image class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21): @@ -117,3 +118,23 @@ class AggregatesV2_41_SampleJsonTest(AggregatesSampleJsonTest): self.extra_subs['uuid'] = subs['uuid'] return self._verify_response('aggregate-post-resp', subs, response, 200) + + +class AggregatesV2_81_SampleJsonTest(AggregatesV2_41_SampleJsonTest): + microversion = '2.81' + scenarios = [ + ( + "v2_81", { + 'api_major_version': 'v2.1', + }, + ) + ] + + def test_images(self): + agg_id = self._test_aggregate_create() + image = fake_image.get_valid_image_id() + response = self._do_post('os-aggregates/%s/images' % agg_id, + 'aggregate-images-post-req', + {'image_id': image}) + # No response body, so just check the status + self.assertEqual(202, response.status_code) diff --git a/nova/tests/functional/test_aggregates.py b/nova/tests/functional/test_aggregates.py index 02115ffe9010..d1c32094388d 100644 --- a/nova/tests/functional/test_aggregates.py +++ b/nova/tests/functional/test_aggregates.py @@ -41,6 +41,7 @@ class AggregatesTest(integrated_helpers._IntegratedTestBase): agg = self.api.post_aggregate(agg) for service in compute_services: self.api.add_host_to_aggregate(agg['id'], service['host']) + self._test_aggregate = agg return len(compute_services) def test_add_hosts(self): @@ -56,6 +57,103 @@ class AggregatesTest(integrated_helpers._IntegratedTestBase): self.assertEqual(2, self._add_hosts_to_aggregate()) +class AggregatesV281Test(AggregatesTest): + api_major_version = 'v2.1' + microversion = '2.81' + + def setUp(self): + self.flags(compute_driver='fake.FakeDriverWithCaching') + super(AggregatesV281Test, self).setUp() + + def test_cache_images_on_aggregate(self): + self._add_hosts_to_aggregate() + agg = self._test_aggregate + img = '155d900f-4e14-4e4c-a73d-069cbf4541e6' + + self.assertEqual(set(), self.compute.driver.cached_images) + + body = {'cache': [ + {'id': img}, + ]} + self.api.api_post('/os-aggregates/%s/images' % agg['id'], body, + check_response_status=[202]) + + self.assertEqual(set([img]), self.compute.driver.cached_images) + + def test_cache_images_on_aggregate_missing_image(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + # NOTE(danms): This image-id does not exist + img = '155d900f-4e14-4e4c-a73d-069cbf4541e0' + body = {'cache': [ + {'id': img}, + ]} + self.api.api_post('/os-aggregates/%s/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_on_missing_aggregate(self): + img = '155d900f-4e14-4e4c-a73d-069cbf4541e6' + body = {'cache': [ + {'id': img}, + ]} + self.api.api_post('/os-aggregates/123/images', body, + check_response_status=[404]) + + def test_cache_images_with_duplicates(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + img = '155d900f-4e14-4e4c-a73d-069cbf4541e6' + body = {'cache': [ + {'id': img}, + {'id': img}, + ]} + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_with_no_images(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + body = {'cache': []} + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_with_additional_in_image(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + img = '155d900f-4e14-4e4c-a73d-069cbf4541e6' + body = {'cache': [ + {'id': img, 'power': '1.21 gigawatts'}, + ]} + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_with_missing_image_id(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + body = {'cache': [ + {'power': '1.21 gigawatts'}, + ]} + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_with_missing_cache(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + body = {} + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + def test_cache_images_with_additional_in_cache(self): + agg = {'aggregate': {'name': 'test-aggregate'}} + agg = self.api.post_aggregate(agg) + img = '155d900f-4e14-4e4c-a73d-069cbf4541e6' + body = {'cache': [{'id': img}], + 'power': '1.21 gigawatts', + } + self.api.api_post('/os-aggregates/%i/images' % agg['id'], body, + check_response_status=[400]) + + class AggregateRequestFiltersTest( integrated_helpers.ProviderUsageBaseTestCase): microversion = 'latest' diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index d80328d4d814..3a77b632f54f 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -288,6 +288,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase): self.fake_policy = jsonutils.loads(fake_policy.policy_data) self.admin_only_rules = ( +"compute:aggregates:images", "compute:server:topology:host:index", "network:attach_external_network", "os_compute_api:servers:create:forced_host", diff --git a/releasenotes/notes/image-precaching-d46506568fefa1ea.yaml b/releasenotes/notes/image-precaching-d46506568fefa1ea.yaml new file mode 100644 index 000000000000..475ae81c90e4 --- /dev/null +++ b/releasenotes/notes/image-precaching-d46506568fefa1ea.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Image pre-caching on hosts by aggregate is now supported (where + supported by the underlying virt driver) as of microversion + 2.81. A group of hosts within an aggregate can be compelled to + fetch and cache a list of images to reduce time-to-boot + latency. Adds the new API: + + * ``POST /os-aggregates/{aggregate_id}/images`` + + which is controlled by the policy ``compute:aggregates:images`` rule. + + See the `[image_cache]/precache_concurrency` config option + for more information about throttling this operation.