diff --git a/api-ref/source/flavors.inc b/api-ref/source/flavors.inc index 941e54783bab..d3d8e4d453a9 100644 --- a/api-ref/source/flavors.inc +++ b/api-ref/source/flavors.inc @@ -105,11 +105,12 @@ Response - swap: flavor_swap - rxtx_factor: flavor_rxtx_factor - os-flavor-access:is_public: flavor_is_public + - extra_specs: extra_specs_2_61 -**Example Create Flavor (v2.55)** +**Example Create Flavor (v2.61)** -.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-create-post-resp.json +.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json :language: javascript List Flavors With Details @@ -155,10 +156,11 @@ Response - swap: flavor_swap - rxtx_factor: flavor_rxtx_factor - os-flavor-access:is_public: flavor_is_public + - extra_specs: extra_specs_2_61 -**Example List Flavors With Details (v2.55)** +**Example List Flavors With Details (v2.61)** -.. literalinclude:: ../../doc/api_samples/flavors/v2.55/flavors-detail-resp.json +.. literalinclude:: ../../doc/api_samples/flavors/v2.61/flavors-detail-resp.json :language: javascript Show Flavor Details @@ -197,10 +199,11 @@ Response - swap: flavor_swap - rxtx_factor: flavor_rxtx_factor - os-flavor-access:is_public: flavor_is_public + - extra_specs: extra_specs_2_61 -**Example Show Flavor Details (v2.55)** +**Example Show Flavor Details (v2.61)** -.. literalinclude:: ../../doc/api_samples/flavors/v2.55/flavor-get-resp.json +.. literalinclude:: ../../doc/api_samples/flavors/v2.61/flavor-get-resp.json :language: javascript Update Flavor Description @@ -252,11 +255,12 @@ Response - swap: flavor_swap - rxtx_factor: flavor_rxtx_factor - os-flavor-access:is_public: flavor_is_public + - extra_specs: extra_specs_2_61 -**Example Update Flavor Description (v2.55)** +**Example Update Flavor Description (v2.61)** -.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.55/flavor-update-resp.json +.. literalinclude:: ../../doc/api_samples/flavor-manage/v2.61/flavor-update-resp.json :language: javascript Delete Flavor diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 16ede5782ee7..d7158e952149 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -2332,6 +2332,15 @@ extra_specs_2_47: in: body required: false type: object +extra_specs_2_61: + min_version: 2.61 + description: | + A dictionary of the flavor's extra-specs key-and-value pairs. This will + only be included if the user is allowed by policy to index flavor + extra_specs. + in: body + required: false + type: object fault: description: | A fault object. Only displayed in the failed response. diff --git a/doc/api_samples/flavor-manage/v2.61/flavor-create-post-req.json b/doc/api_samples/flavor-manage/v2.61/flavor-create-post-req.json new file mode 100644 index 000000000000..0d9926d72027 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.61/flavor-create-post-req.json @@ -0,0 +1,11 @@ +{ + "flavor": { + "name": "test_flavor", + "ram": 1024, + "vcpus": 2, + "disk": 10, + "id": "10", + "rxtx_factor": 2.0, + "description": "test description" + } +} diff --git a/doc/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json b/doc/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json new file mode 100644 index 000000000000..ff9af699f31e --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json @@ -0,0 +1,26 @@ +{ + "flavor": { + "OS-FLV-DISABLED:disabled": false, + "disk": 10, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "10", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/10", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/10", + "rel": "bookmark" + } + ], + "name": "test_flavor", + "ram": 1024, + "swap": "", + "rxtx_factor": 2.0, + "vcpus": 2, + "description": "test description", + "extra_specs": {} + } +} diff --git a/doc/api_samples/flavor-manage/v2.61/flavor-update-req.json b/doc/api_samples/flavor-manage/v2.61/flavor-update-req.json new file mode 100644 index 000000000000..93c8e1e8ab23 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.61/flavor-update-req.json @@ -0,0 +1,5 @@ +{ + "flavor": { + "description": "updated description" + } +} diff --git a/doc/api_samples/flavor-manage/v2.61/flavor-update-resp.json b/doc/api_samples/flavor-manage/v2.61/flavor-update-resp.json new file mode 100644 index 000000000000..3b985acbd4c1 --- /dev/null +++ b/doc/api_samples/flavor-manage/v2.61/flavor-update-resp.json @@ -0,0 +1,26 @@ +{ + "flavor": { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/1", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "updated description", + "extra_specs": {} + } +} diff --git a/doc/api_samples/flavors/v2.61/flavor-get-resp.json b/doc/api_samples/flavors/v2.61/flavor-get-resp.json new file mode 100644 index 000000000000..124110adb7b4 --- /dev/null +++ b/doc/api_samples/flavors/v2.61/flavor-get-resp.json @@ -0,0 +1,29 @@ +{ + "flavor": { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "7", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "test description", + "extra_specs": { + "key1": "value1", + "key2": "value2" + } + } +} diff --git a/doc/api_samples/flavors/v2.61/flavors-detail-resp.json b/doc/api_samples/flavors/v2.61/flavors-detail-resp.json new file mode 100644 index 000000000000..6325cccaa26d --- /dev/null +++ b/doc/api_samples/flavors/v2.61/flavors-detail-resp.json @@ -0,0 +1,179 @@ +{ + "flavors": [ + { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/1", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "2", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/2", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 40, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "3", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/3", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "ram": 4096, + "swap": "", + "vcpus": 2, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 80, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "4", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/4", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "ram": 8192, + "swap": "", + "vcpus": 4, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 160, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "5", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/5", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5", + "rel": "bookmark" + } + ], + "name": "m1.xlarge", + "ram": 16384, + "swap": "", + "vcpus": 8, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "6", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/6", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6", + "rel": "bookmark" + } + ], + "name": "m1.tiny.specs", + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": { + "hw:cpu_model": "SandyBridge", + "hw:mem_page_size": "2048", + "hw:cpu_policy": "dedicated" + } + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "7", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "test description", + "extra_specs": { + "key1": "value1", + "key2": "value2" + } + } + ] +} diff --git a/doc/api_samples/flavors/v2.61/flavors-list-resp.json b/doc/api_samples/flavors/v2.61/flavors-list-resp.json new file mode 100644 index 000000000000..f368ed5c66fd --- /dev/null +++ b/doc/api_samples/flavors/v2.61/flavors-list-resp.json @@ -0,0 +1,109 @@ +{ + "flavors": [ + { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/1", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "description": null + }, + { + "id": "2", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/2", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "description": null + }, + { + "id": "3", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/3", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "description": null + }, + { + "id": "4", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/4", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "description": null + }, + { + "id": "5", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/5", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/5", + "rel": "bookmark" + } + ], + "name": "m1.xlarge", + "description": null + }, + { + "id": "6", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/6", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/6", + "rel": "bookmark" + } + ], + "name": "m1.tiny.specs", + "description": null + }, + { + "id": "7", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/flavors/7", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/7", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "description": "test description" + } + ] +} diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index df5ba5575837..6e50403cbabd 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.60", + "version": "2.61", "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 8c9f6b767ed2..4d3c06a7caff 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.60", + "version": "2.61", "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 d25e5b28a018..4df2854c3592 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -143,6 +143,9 @@ REST_API_VERSION_HISTORY = """REST API Version History: API. And the os-migrations API now returns both the id and the uuid in response. * 2.60 - Add support for attaching a single volume to multiple instances. + * 2.61 - Exposes flavor extra_specs in the flavor representation. Flavor + extra_specs will be included in Response body of GET, POST, PUT + /flavors APIs. """ # The minimum and maximum versions of the API supported @@ -151,7 +154,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.60" +_MAX_API_VERSION = "2.61" 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/flavor_manage.py b/nova/api/openstack/compute/flavor_manage.py index b028448c7241..68aeef68e36e 100644 --- a/nova/api/openstack/compute/flavor_manage.py +++ b/nova/api/openstack/compute/flavor_manage.py @@ -21,6 +21,7 @@ from nova.compute import flavors from nova import exception from nova import objects from nova.policies import base +from nova.policies import flavor_extra_specs as fes_policies from nova.policies import flavor_manage as fm_policies from nova import policy @@ -110,7 +111,17 @@ class FlavorManageController(wsgi.Controller): exception.FlavorIdExists) as err: raise webob.exc.HTTPConflict(explanation=err.format_message()) - return self._view_builder.show(req, flavor, include_description) + include_extra_specs = False + if api_version_request.is_supported( + req, flavors_view.FLAVOR_EXTRA_SPECS_MICROVERSION): + include_extra_specs = context.can( + fes_policies.POLICY_ROOT % 'index', fatal=False) + # NOTE(yikun): This empty extra_spec only for keeping consistent + # with other related flavor api. + flavor.extra_specs = {} + + return self._view_builder.show(req, flavor, include_description, + include_extra_specs=include_extra_specs) @wsgi.Controller.api_version(flavors_view.FLAVOR_DESCRIPTION_MICROVERSION) @wsgi.action('update') @@ -133,4 +144,10 @@ class FlavorManageController(wsgi.Controller): # Cache the flavor so the flavor_access and flavor_rxtx extensions # can add stuff to the response. req.cache_db_flavor(flavor) - return self._view_builder.show(req, flavor, include_description=True) + include_extra_specs = False + if api_version_request.is_supported( + req, flavors_view.FLAVOR_EXTRA_SPECS_MICROVERSION): + include_extra_specs = context.can( + fes_policies.POLICY_ROOT % 'index', fatal=False) + return self._view_builder.show(req, flavor, include_description=True, + include_extra_specs=include_extra_specs) diff --git a/nova/api/openstack/compute/flavors.py b/nova/api/openstack/compute/flavors.py index 91673d1f673b..5b5fd9075218 100644 --- a/nova/api/openstack/compute/flavors.py +++ b/nova/api/openstack/compute/flavors.py @@ -26,6 +26,7 @@ from nova.compute import flavors from nova import exception from nova.i18n import _ from nova import objects +from nova.policies import flavor_extra_specs as fes_policies from nova import utils ALIAS = 'flavors' @@ -47,9 +48,16 @@ class FlavorsController(wsgi.Controller): @wsgi.expected_errors(400) def detail(self, req): """Return all flavors in detail.""" + context = req.environ['nova.context'] limited_flavors = self._get_flavors(req) req.cache_db_flavors(limited_flavors) - return self._view_builder.detail(req, limited_flavors) + include_extra_specs = False + if api_version_request.is_supported( + req, flavors_view.FLAVOR_EXTRA_SPECS_MICROVERSION): + include_extra_specs = context.can( + fes_policies.POLICY_ROOT % 'index', fatal=False) + return self._view_builder.detail( + req, limited_flavors, include_extra_specs=include_extra_specs) @wsgi.expected_errors(404) def show(self, req, id): @@ -61,9 +69,16 @@ class FlavorsController(wsgi.Controller): except exception.FlavorNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) + include_extra_specs = False + if api_version_request.is_supported( + req, flavors_view.FLAVOR_EXTRA_SPECS_MICROVERSION): + include_extra_specs = context.can( + fes_policies.POLICY_ROOT % 'index', fatal=False) include_description = api_version_request.is_supported( req, flavors_view.FLAVOR_DESCRIPTION_MICROVERSION) - return self._view_builder.show(req, flavor, include_description) + return self._view_builder.show( + req, flavor, include_description=include_description, + include_extra_specs=include_extra_specs) def _parse_is_public(self, is_public): """Parse is_public into something usable.""" diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst index d1215e374be3..42ff34513556 100644 --- a/nova/api/openstack/compute/rest_api_version_history.rst +++ b/nova/api/openstack/compute/rest_api_version_history.rst @@ -765,3 +765,18 @@ From this version of the API users can attach a ``multiattach`` capable volume to multiple instances. The API request for creating the additional attachments is the same. The chosen virt driver and the volume back end has to support the functionality as well. + +2.61 +---- + +Exposes flavor extra_specs in the flavor representation. Now users can see the +flavor extra-specs in flavor APIs response and do not need to call +``GET /flavors/{flavor_id}/os-extra_specs`` API. If the user is prevented by +policy from indexing extra-specs, then the ``extra_specs`` field will not be +included in the flavor information. Flavor extra_specs will be included in +Response body of the following APIs: + +* ``GET /flavors/detail`` +* ``GET /flavors/{flavor_id}`` +* ``POST /flavors`` +* ``PUT /flavors/{flavor_id}`` diff --git a/nova/api/openstack/compute/views/flavors.py b/nova/api/openstack/compute/views/flavors.py index fcdd6fcf4ca4..a7b2a045ddce 100644 --- a/nova/api/openstack/compute/views/flavors.py +++ b/nova/api/openstack/compute/views/flavors.py @@ -19,6 +19,7 @@ from nova.policies import flavor_access as fa_policies from nova.policies import flavor_rxtx as fr_policies FLAVOR_DESCRIPTION_MICROVERSION = '2.55' +FLAVOR_EXTRA_SPECS_MICROVERSION = '2.61' class ViewBuilder(common.ViewBuilder): @@ -26,10 +27,12 @@ class ViewBuilder(common.ViewBuilder): _collection_name = "flavors" def basic(self, request, flavor, include_description=False, - update_is_public=None, update_rxtx_factor=None): - # update_is_public & update_rxtx_factor are placeholder param - # which are not used in this method as basic() method is used by - # index() (GET /flavors) which does not return those keys in response. + update_is_public=None, update_rxtx_factor=None, + include_extra_specs=False): + # include_extra_specs & update_is_public & update_rxtx_factor are + # placeholder param which are not used in this method as basic() method + # is used by index() (GET /flavors) which does not return those keys in + # response. flavor_dict = { "flavor": { "id": flavor["flavorid"], @@ -46,7 +49,8 @@ class ViewBuilder(common.ViewBuilder): return flavor_dict def show(self, request, flavor, include_description=False, - update_is_public=None, update_rxtx_factor=None): + update_is_public=None, update_rxtx_factor=None, + include_extra_specs=False): flavor_dict = { "flavor": { "id": flavor["flavorid"], @@ -66,6 +70,9 @@ class ViewBuilder(common.ViewBuilder): if include_description: flavor_dict['flavor']['description'] = flavor.description + if include_extra_specs: + flavor_dict['flavor']['extra_specs'] = flavor.extra_specs + # TODO(gmann): 'update_is_public' & 'update_rxtx_factor' are policies # checks. Once os-flavor-access & os-flavor-rxtx policies are # removed, 'os-flavor-access:is_public' and 'rxtx_factor' need to be @@ -96,7 +103,7 @@ class ViewBuilder(common.ViewBuilder): return self._list_view(self.basic, request, flavors, coll_name, include_description=include_description) - def detail(self, request, flavors): + def detail(self, request, flavors, include_extra_specs=False): """Return the 'detail' view of flavors.""" coll_name = self._collection_name + '/detail' include_description = api_version_request.is_supported( @@ -109,11 +116,12 @@ class ViewBuilder(common.ViewBuilder): return self._list_view(self.show, request, flavors, coll_name, include_description=include_description, update_is_public=update_is_public, - update_rxtx_factor=update_rxtx_factor) + update_rxtx_factor=update_rxtx_factor, + include_extra_specs=include_extra_specs) def _list_view(self, func, request, flavors, coll_name, include_description=False, update_is_public=None, - update_rxtx_factor=None): + update_rxtx_factor=None, include_extra_specs=False): """Provide a view for a list of flavors. :param func: Function used to format the flavor data @@ -127,11 +135,14 @@ class ViewBuilder(common.ViewBuilder): included in the response dict. :param update_rxtx_factor: If the flavor.rxtx_factor field should be included in the response dict. + :param include_extra_specs: If the flavor.extra_specs should be + included in the response dict. :returns: Flavor reply data in dictionary format """ flavor_list = [func(request, flavor, include_description, - update_is_public, update_rxtx_factor)["flavor"] + update_is_public, update_rxtx_factor, + include_extra_specs)["flavor"] for flavor in flavors] flavors_links = self._get_collection_links(request, flavors, diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-req.json.tpl new file mode 100644 index 000000000000..2065b445cf3e --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-req.json.tpl @@ -0,0 +1,11 @@ +{ + "flavor": { + "name": "%(flavor_name)s", + "ram": 1024, + "vcpus": 2, + "disk": 10, + "id": "%(flavor_id)s", + "rxtx_factor": 2.0, + "description": "test description" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json.tpl new file mode 100644 index 000000000000..0c1a0745cd8a --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-create-post-resp.json.tpl @@ -0,0 +1,26 @@ +{ + "flavor": { + "disk": 10, + "id": "%(flavor_id)s", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/%(flavor_id)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/%(flavor_id)s", + "rel": "bookmark" + } + ], + "name": "%(flavor_name)s", + "os-flavor-access:is_public": true, + "ram": 1024, + "vcpus": 2, + "OS-FLV-DISABLED:disabled": false, + "OS-FLV-EXT-DATA:ephemeral": 0, + "swap": "", + "rxtx_factor": 2.0, + "description": "test description", + "extra_specs": {} + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-req.json.tpl new file mode 100644 index 000000000000..93c8e1e8ab23 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-req.json.tpl @@ -0,0 +1,5 @@ +{ + "flavor": { + "description": "updated description" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-resp.json.tpl new file mode 100644 index 000000000000..3b985acbd4c1 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavor-manage/v2.61/flavor-update-resp.json.tpl @@ -0,0 +1,26 @@ +{ + "flavor": { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "os-flavor-access:is_public": true, + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/flavors/1", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "updated description", + "extra_specs": {} + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavor-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavor-get-resp.json.tpl new file mode 100644 index 000000000000..c086c874da8c --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavor-get-resp.json.tpl @@ -0,0 +1,29 @@ +{ + "flavor": { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "%(flavorid)s", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/%(flavorid)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/%(flavorid)s", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "os-flavor-access:is_public": true, + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "test description", + "extra_specs": { + "key1": "value1", + "key2": "value2" + } + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-detail-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-detail-resp.json.tpl new file mode 100644 index 000000000000..990ba575f0a9 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-detail-resp.json.tpl @@ -0,0 +1,179 @@ +{ + "flavors": [ + { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "1", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/1", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "os-flavor-access:is_public": true, + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "2", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/2", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "os-flavor-access:is_public": true, + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 40, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "3", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/3", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "os-flavor-access:is_public": true, + "ram": 4096, + "swap": "", + "vcpus": 2, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 80, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "4", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/4", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "os-flavor-access:is_public": true, + "ram": 8192, + "swap": "", + "vcpus": 4, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 160, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "5", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/5", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/5", + "rel": "bookmark" + } + ], + "name": "m1.xlarge", + "os-flavor-access:is_public": true, + "ram": 16384, + "swap": "", + "vcpus": 8, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": {} + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 1, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "6", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/6", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/6", + "rel": "bookmark" + } + ], + "name": "m1.tiny.specs", + "os-flavor-access:is_public": true, + "ram": 512, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": null, + "extra_specs": { + "hw:cpu_model": "SandyBridge", + "hw:mem_page_size": "2048", + "hw:cpu_policy": "dedicated" + } + }, + { + "OS-FLV-DISABLED:disabled": false, + "disk": 20, + "OS-FLV-EXT-DATA:ephemeral": 0, + "id": "%(flavorid)s", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/%(flavorid)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/%(flavorid)s", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "os-flavor-access:is_public": true, + "ram": 2048, + "swap": "", + "vcpus": 1, + "rxtx_factor": 1.0, + "description": "test description", + "extra_specs": { + "key1": "value1", + "key2": "value2" + } + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-list-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-list-resp.json.tpl new file mode 100644 index 000000000000..79a23e8760ba --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/flavors/v2.61/flavors-list-resp.json.tpl @@ -0,0 +1,109 @@ +{ + "flavors": [ + { + "id": "1", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/1", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "description": null + }, + { + "id": "2", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/2", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "description": null + }, + { + "id": "3", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/3", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "description": null + }, + { + "id": "4", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/4", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "description": null + }, + { + "id": "5", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/5", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/5", + "rel": "bookmark" + } + ], + "name": "m1.xlarge", + "description": null + }, + { + "id": "6", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/6", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/6", + "rel": "bookmark" + } + ], + "name": "m1.tiny.specs", + "description": null + }, + { + "id": "%(flavorid)s", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/flavors/%(flavorid)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/flavors/%(flavorid)s", + "rel": "bookmark" + } + ], + "name": "m1.small.description", + "description": "test description" + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/test_flavors.py b/nova/tests/functional/api_sample_tests/test_flavors.py index b2b42975b924..34f3886490c6 100644 --- a/nova/tests/functional/api_sample_tests/test_flavors.py +++ b/nova/tests/functional/api_sample_tests/test_flavors.py @@ -57,3 +57,26 @@ class FlavorsSampleJsonTest2_55(FlavorsSampleJsonTest): new_flavor.create() self.flavor_show_id = new_flavor_id self.subs = {'flavorid': new_flavor_id} + + +class FlavorsSampleJsonTest2_61(FlavorsSampleJsonTest): + microversion = '2.61' + scenarios = [('v2_61', {'api_major_version': 'v2.1'})] + + def setUp(self): + super(FlavorsSampleJsonTest2_61, self).setUp() + # Get the existing flavors created by DefaultFlavorsFixture. + ctxt = nova_context.get_admin_context() + flavors = objects.FlavorList.get_all(ctxt) + # Flavors are sorted by flavorid in ascending order by default, so + # get the last flavor in the list and create a new flavor with an + # incremental flavorid so we have a predictable sort order for the + # sample response. + new_flavor_id = int(flavors[-1].flavorid) + 1 + new_flavor = objects.Flavor( + ctxt, memory_mb=2048, vcpus=1, root_gb=20, flavorid=new_flavor_id, + name='m1.small.description', description='test description', + extra_specs={"key1": "value1", "key2": "value2"}) + new_flavor.create() + self.flavor_show_id = new_flavor_id + self.subs = {'flavorid': new_flavor_id} diff --git a/nova/tests/unit/api/openstack/compute/test_flavors.py b/nova/tests/unit/api/openstack/compute/test_flavors.py index eb29f16644d4..1abc89e8e00d 100644 --- a/nova/tests/unit/api/openstack/compute/test_flavors.py +++ b/nova/tests/unit/api/openstack/compute/test_flavors.py @@ -52,6 +52,8 @@ class FlavorsTestV21(test.TestCase): microversion = '2.1' # Flag to tell the test if a description should be expected in a response. expect_description = False + # Flag to tell the test if a extra_specs should be expected in a response. + expect_extra_specs = False def setUp(self): super(FlavorsTestV21, self).setUp() @@ -73,6 +75,8 @@ class FlavorsTestV21(test.TestCase): expected['swap'] = flavor.swap if self.expect_description: expected['description'] = flavor.description + if self.expect_extra_specs: + expected['extra_specs'] = flavor.extra_specs @mock.patch('nova.objects.Flavor.get_by_flavor_id', side_effect=return_flavor_not_found) @@ -769,6 +773,12 @@ class FlavorsTestV2_55(FlavorsTestV21): expect_description = True +class FlavorsTestV2_61(FlavorsTestV2_55): + """Run the same tests as we would for v2.55 but with a extra_specs.""" + microversion = '2.61' + expect_extra_specs = True + + class FlavorsPolicyEnforcementV21(test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index 0d58501d301f..21dca72492ca 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -705,7 +705,8 @@ FLAVORS = { vcpu_weight=None, disabled=False, is_public=True, - description=None + description=None, + extra_specs={"key1": "value1", "key2": "value2"} ), '2': objects.Flavor( id=2, @@ -720,7 +721,8 @@ FLAVORS = { vcpu_weight=None, disabled=True, is_public=True, - description='flavor 2 description' + description='flavor 2 description', + extra_specs={} ), } diff --git a/releasenotes/notes/add-extra-specs-to-flavor-list-362a4794c0871f2f.yaml b/releasenotes/notes/add-extra-specs-to-flavor-list-362a4794c0871f2f.yaml new file mode 100644 index 000000000000..51f06da1243b --- /dev/null +++ b/releasenotes/notes/add-extra-specs-to-flavor-list-362a4794c0871f2f.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Exposes flavor extra_specs in the flavor representation since microversion + 2.61. Flavor extra_specs will be included in Response body of the + following APIs: + + * ``GET /flavors/detail`` + * ``GET /flavors/{flavor_id}`` + * ``POST /flavors`` + * ``PUT /flavors/{flavor_id}`` + + Now users can see the flavor extra-specs in flavor APIs response and do + not need to call ``GET /flavors/{flavor_id}/os-extra_specs`` API. The + visibility of the flavor extra_specs within the flavor resource will be + controlled by the same policy rules as are used for showing the flavor + extra_specs. If the user has no access to query extra_specs, the + ``flavor.extra_specs`` will not be included.