Add microversion to support extra_specs in flavor API.

Exposes flavor extra_specs in the flavor representation since
microversion 2.61. Now users can see the flavor extra-specs
in flavor APIs response only and do not need to call
``GET /flavors/{flavor_id}/extra_specs`` API.

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}``

Part of blueprint add-extra-specs-to-flavor-list

Change-Id: I048747633babf690a63c6de9773bff5547872053
This commit is contained in:
Yikun Jiang 2018-03-30 18:00:03 +08:00
parent 1e6564b7d4
commit 0baba40b1b
27 changed files with 923 additions and 26 deletions

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,11 @@
{
"flavor": {
"name": "test_flavor",
"ram": 1024,
"vcpus": 2,
"disk": 10,
"id": "10",
"rxtx_factor": 2.0,
"description": "test description"
}
}

View File

@ -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": {}
}
}

View File

@ -0,0 +1,5 @@
{
"flavor": {
"description": "updated description"
}
}

View File

@ -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": {}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.60",
"version": "2.61",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.60",
"version": "2.61",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -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

View File

@ -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)

View File

@ -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."""

View File

@ -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}``

View File

@ -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,

View File

@ -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"
}
}

View File

@ -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": {}
}
}

View File

@ -0,0 +1,5 @@
{
"flavor": {
"description": "updated description"
}
}

View File

@ -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": {}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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}

View File

@ -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):

View File

@ -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={}
),
}

View File

@ -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.