Return uuid attribute for aggregates
Adds a Compute API microversion that triggers returning an aggregate's UUID field. This field is necessary for scripts that must populate the placement API with resource provider to aggregate relationships, which rely on UUIDs for global identification. APIImpact blueprint: return-uuid-from-os-aggregates-api Change-Id: I4112ccd508eb85403933fec8b52efd468e866772 Closes-bug: #1652642
This commit is contained in:
parent
d8b84520ab
commit
03c2776e49
|
@ -34,6 +34,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example List Aggregates: JSON response**
|
||||
|
||||
|
@ -79,6 +80,7 @@ Response
|
|||
- id: aggregate_id_body
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Create Aggregate: JSON response**
|
||||
|
||||
|
@ -118,6 +120,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Show Aggregate Details: JSON response**
|
||||
|
||||
|
@ -168,6 +171,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Update Aggregate: JSON response**
|
||||
|
||||
|
@ -240,6 +244,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Add Host: JSON response**
|
||||
|
||||
|
@ -289,6 +294,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Remove Host: JSON response**
|
||||
|
||||
|
@ -338,6 +344,7 @@ Response
|
|||
- metadata: aggregate_metadata
|
||||
- name: aggregate_name
|
||||
- updated_at: updated_consider_null
|
||||
- uuid: aggregate_uuid
|
||||
|
||||
**Example Create Or Update Aggregate Metadata: JSON response**
|
||||
|
||||
|
|
|
@ -967,6 +967,12 @@ aggregate_remove_host:
|
|||
in: body
|
||||
required: true
|
||||
type: object
|
||||
aggregate_uuid:
|
||||
description: |
|
||||
The UUID of the host aggregate.
|
||||
in: body
|
||||
type: string
|
||||
min_version: 2.41
|
||||
aggregates:
|
||||
description: |
|
||||
The list of existing aggregates.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"add_host": {
|
||||
"host": "compute"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"set_metadata":
|
||||
{
|
||||
"metadata":
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"aggregate":
|
||||
{
|
||||
"name": "name",
|
||||
"availability_zone": "nova"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T22:51:32.877711",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"id": 1,
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "86a0da0e-9f0c-4f51-a1e0-3c25edab3783"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"remove_host": {
|
||||
"host": "compute"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"aggregate":
|
||||
{
|
||||
"name": "newname",
|
||||
"availability_zone": "nova2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova2",
|
||||
"created_at": "2016-12-27T23:47:32.897139",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova2"
|
||||
},
|
||||
"name": "newname",
|
||||
"updated_at": "2016-12-27T23:47:33.067180",
|
||||
"uuid": "6f74e3f3-df28-48f3-98e1-ac941b1c5e43"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T23:47:30.594805",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [
|
||||
"compute"
|
||||
],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "d1842372-89c5-4fbd-ad5a-5d2e16c85456"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T23:47:30.563527",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "fd0a5b12-7e8d-469d-bfd5-64a6823e7407"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"aggregates": [
|
||||
{
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T23:47:32.911515",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [
|
||||
"compute"
|
||||
],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "6ba28ba7-f29b-45cc-a30b-6e3a40c2fb14"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T23:59:18.623100",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova",
|
||||
"key": "value"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": "2016-12-27T23:59:18.723348",
|
||||
"uuid": "26002bdb-62cc-41bd-813a-0ad22db32625"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "2016-12-27T23:47:30.594805",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "d1842372-89c5-4fbd-ad5a-5d2e16c85456"
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.40",
|
||||
"version": "2.41",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.40",
|
||||
"version": "2.41",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||
provided for listing servers.
|
||||
* 2.39 - Deprecates image-metadata proxy API
|
||||
* 2.40 - Adds simple tenant usage pagination support.
|
||||
* 2.41 - Return uuid attribute for aggregates.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
|
@ -105,7 +106,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.40"
|
||||
_MAX_API_VERSION = "2.41"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which related to network, images and baremetal
|
||||
|
|
|
@ -19,6 +19,7 @@ import datetime
|
|||
|
||||
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 aggregates
|
||||
from nova.api.openstack import extensions
|
||||
|
@ -47,7 +48,7 @@ class AggregateController(wsgi.Controller):
|
|||
context = _get_context(req)
|
||||
context.can(aggr_policies.POLICY_ROOT % 'index')
|
||||
aggregates = self.api.get_aggregate_list(context)
|
||||
return {'aggregates': [self._marshall_aggregate(a)['aggregate']
|
||||
return {'aggregates': [self._marshall_aggregate(req, a)['aggregate']
|
||||
for a in aggregates]}
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 201
|
||||
|
@ -77,7 +78,7 @@ class AggregateController(wsgi.Controller):
|
|||
except exception.InvalidAggregateAction as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
agg = self._marshall_aggregate(aggregate)
|
||||
agg = self._marshall_aggregate(req, aggregate)
|
||||
|
||||
# To maintain the same API result as before the changes for returning
|
||||
# nova objects were made.
|
||||
|
@ -95,7 +96,7 @@ class AggregateController(wsgi.Controller):
|
|||
aggregate = self.api.get_aggregate(context, id)
|
||||
except exception.AggregateNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
return self._marshall_aggregate(aggregate)
|
||||
return self._marshall_aggregate(req, aggregate)
|
||||
|
||||
@extensions.expected_errors((400, 404, 409))
|
||||
@validation.schema(aggregates.update_v20, '2.0', '2.0')
|
||||
|
@ -117,7 +118,7 @@ class AggregateController(wsgi.Controller):
|
|||
except exception.InvalidAggregateAction as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
return self._marshall_aggregate(aggregate)
|
||||
return self._marshall_aggregate(req, aggregate)
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 204
|
||||
# as this operation complete the deletion of aggregate resource and return
|
||||
|
@ -154,7 +155,7 @@ class AggregateController(wsgi.Controller):
|
|||
except (exception.AggregateHostExists,
|
||||
exception.InvalidAggregateAction) as e:
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
return self._marshall_aggregate(aggregate)
|
||||
return self._marshall_aggregate(req, aggregate)
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 202
|
||||
# for representing async API as this API just accepts the request and
|
||||
|
@ -179,7 +180,7 @@ class AggregateController(wsgi.Controller):
|
|||
msg = _('Cannot remove host %(host)s in aggregate %(id)s') % {
|
||||
'host': host, 'id': id}
|
||||
raise exc.HTTPConflict(explanation=msg)
|
||||
return self._marshall_aggregate(aggregate)
|
||||
return self._marshall_aggregate(req, aggregate)
|
||||
|
||||
@extensions.expected_errors((400, 404))
|
||||
@wsgi.action('set_metadata')
|
||||
|
@ -198,18 +199,19 @@ class AggregateController(wsgi.Controller):
|
|||
except exception.InvalidAggregateAction as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
return self._marshall_aggregate(aggregate)
|
||||
return self._marshall_aggregate(req, aggregate)
|
||||
|
||||
def _marshall_aggregate(self, aggregate):
|
||||
def _marshall_aggregate(self, req, aggregate):
|
||||
_aggregate = {}
|
||||
for key, value in self._build_aggregate_items(aggregate):
|
||||
for key, value in self._build_aggregate_items(req, aggregate):
|
||||
# NOTE(danms): The original API specified non-TZ-aware timestamps
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = value.replace(tzinfo=None)
|
||||
_aggregate[key] = value
|
||||
return {"aggregate": _aggregate}
|
||||
|
||||
def _build_aggregate_items(self, aggregate):
|
||||
def _build_aggregate_items(self, req, aggregate):
|
||||
show_uuid = api_version_request.is_supported(req, min_version="2.41")
|
||||
keys = aggregate.obj_fields
|
||||
# NOTE(rlrossit): Within the compute API, metadata will always be
|
||||
# set on the aggregate object (at a minimum to {}). Because of this,
|
||||
|
@ -217,11 +219,9 @@ class AggregateController(wsgi.Controller):
|
|||
# case it is only ['availability_zone']) without worrying about
|
||||
# lazy-loading an unset variable
|
||||
for key in keys:
|
||||
# NOTE(danms): Skip the uuid field because we have no microversion
|
||||
# to expose it
|
||||
if ((aggregate.obj_attr_is_set(key)
|
||||
or key in aggregate.obj_extra_fields) and
|
||||
key != 'uuid'):
|
||||
(show_uuid or key != 'uuid')):
|
||||
yield key, getattr(aggregate, key)
|
||||
|
||||
|
||||
|
|
|
@ -445,3 +445,11 @@ user documentation.
|
|||
`CONF.api.max_limit` to encourage the adoption of this new microversion,
|
||||
and circumvent the existing possibility DoS-like usage requests on systems
|
||||
with thousands of instances.
|
||||
|
||||
2.41
|
||||
----
|
||||
|
||||
The 'uuid' attribute of an aggregate is now returned from calls to the
|
||||
`/os-aggregates` endpoint. This attribute is auto-generated upon creation of
|
||||
an aggregate. The `os-aggregates` API resource endpoint remains an
|
||||
administrator-only API.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"add_host": {
|
||||
"host": "%(host_name)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"set_metadata":
|
||||
{
|
||||
"metadata":
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"aggregate":
|
||||
{
|
||||
"name": "name",
|
||||
"availability_zone": "nova"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"id": %(aggregate_id)s,
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"remove_host": {
|
||||
"host": "%(host_name)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"aggregate":
|
||||
{
|
||||
"name": "newname",
|
||||
"availability_zone": "nova2"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [
|
||||
"%(compute_host)s"
|
||||
],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"aggregates": [
|
||||
{
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [
|
||||
"%(compute_host)s"
|
||||
],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova",
|
||||
"key": "value"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": %(strtime)s,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"availability_zone": "nova",
|
||||
"created_at": "%(strtime)s",
|
||||
"deleted": false,
|
||||
"deleted_at": null,
|
||||
"hosts": [],
|
||||
"id": 1,
|
||||
"metadata": {
|
||||
"availability_zone": "nova"
|
||||
},
|
||||
"name": "name",
|
||||
"updated_at": null,
|
||||
"uuid": "%(uuid)s"
|
||||
}
|
||||
}
|
|
@ -13,12 +13,18 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
|
||||
|
||||
class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
ADMIN_API = True
|
||||
sample_dir = "os-aggregates"
|
||||
# extra_subs is a noop in the base v2.1 test class; it's used to sub in
|
||||
# additional details for response verification of actions performed on an
|
||||
# existing aggregate.
|
||||
extra_subs = {}
|
||||
|
||||
def _test_aggregate_create(self):
|
||||
subs = {
|
||||
|
@ -37,6 +43,7 @@ class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
|||
}
|
||||
response = self._do_post('os-aggregates/%s/action' % aggregate_id,
|
||||
'aggregate-add-host-post-req', subs)
|
||||
subs.update(self.extra_subs)
|
||||
self._verify_response('aggregates-add-host-post-resp', subs,
|
||||
response, 200)
|
||||
|
||||
|
@ -49,14 +56,15 @@ class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
|||
def test_aggregate_get(self):
|
||||
agg_id = self._test_aggregate_create()
|
||||
response = self._do_get('os-aggregates/%s' % agg_id)
|
||||
self._verify_response('aggregates-get-resp', {}, response, 200)
|
||||
self._verify_response('aggregates-get-resp', self.extra_subs,
|
||||
response, 200)
|
||||
|
||||
def test_add_metadata(self):
|
||||
agg_id = self._test_aggregate_create()
|
||||
response = self._do_post('os-aggregates/%s/action' % agg_id,
|
||||
'aggregate-metadata-post-req',
|
||||
{'action': 'set_metadata'})
|
||||
self._verify_response('aggregates-metadata-post-resp', {},
|
||||
self._verify_response('aggregates-metadata-post-resp', self.extra_subs,
|
||||
response, 200)
|
||||
|
||||
def test_add_host(self):
|
||||
|
@ -70,6 +78,7 @@ class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
|||
}
|
||||
response = self._do_post('os-aggregates/1/action',
|
||||
'aggregate-remove-host-post-req', subs)
|
||||
subs.update(self.extra_subs)
|
||||
self._verify_response('aggregates-remove-host-post-resp',
|
||||
subs, response, 200)
|
||||
|
||||
|
@ -78,4 +87,33 @@ class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
|||
response = self._do_put('os-aggregates/%s' % aggregate_id,
|
||||
'aggregate-update-post-req', {})
|
||||
self._verify_response('aggregate-update-post-resp',
|
||||
{}, response, 200)
|
||||
self.extra_subs, response, 200)
|
||||
|
||||
|
||||
class AggregatesV2_41_SampleJsonTest(AggregatesSampleJsonTest):
|
||||
microversion = '2.41'
|
||||
scenarios = [
|
||||
(
|
||||
"v2_41", {
|
||||
'api_major_version': 'v2.1',
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
def _test_aggregate_create(self):
|
||||
subs = {
|
||||
"aggregate_id": '(?P<id>\d+)',
|
||||
}
|
||||
response = self._do_post('os-aggregates', 'aggregate-post-req', subs)
|
||||
# This feels like cheating since we're getting the uuid from the
|
||||
# response before we even validate that it exists in the response based
|
||||
# on the sample, but we'll fail with a KeyError if it doesn't which is
|
||||
# maybe good enough. Alternatively we have to mock out the DB API
|
||||
# to return a fake aggregate with a hard-coded uuid that matches the
|
||||
# API sample which isn't fun either.
|
||||
subs['uuid'] = jsonutils.loads(response.content)['aggregate']['uuid']
|
||||
# save off the uuid for subs validation on other actions performed
|
||||
# on this aggregate
|
||||
self.extra_subs['uuid'] = subs['uuid']
|
||||
return self._verify_response('aggregate-post-resp',
|
||||
subs, response, 200)
|
||||
|
|
|
@ -89,6 +89,9 @@ class _IntegratedTestBase(test.TestCase):
|
|||
else:
|
||||
self.api = self.api_fixture.api
|
||||
|
||||
if hasattr(self, 'microversion'):
|
||||
self.api.microversion = self.microversion
|
||||
|
||||
self.useFixture(cast_as_call.CastAsCall(self.stubs))
|
||||
|
||||
self.addCleanup(nova.tests.unit.image.fake.FakeImageService_reset)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import mock
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack.compute import aggregates as aggregates_v21
|
||||
from nova.compute import api as compute_api
|
||||
from nova import context
|
||||
|
@ -743,11 +744,23 @@ class AggregateTestCaseV21(test.NoDBTestCase):
|
|||
'metadata': {'foo': 'bar', 'availability_zone': 'nova'},
|
||||
'hosts': ['host1', 'host2']}
|
||||
agg_obj = _make_agg_obj(agg)
|
||||
marshalled_agg = self.controller._marshall_aggregate(agg_obj)
|
||||
|
||||
# _marshall_aggregate() puts all fields and obj_extra_fields in the
|
||||
# top-level dict, so we need to put availability_zone at the top also
|
||||
agg['availability_zone'] = 'nova'
|
||||
|
||||
avr_v240 = api_version_request.APIVersionRequest("2.40")
|
||||
avr_v241 = api_version_request.APIVersionRequest("2.41")
|
||||
|
||||
req = mock.MagicMock(api_version_request=avr_v241)
|
||||
marshalled_agg = self.controller._marshall_aggregate(req, agg_obj)
|
||||
|
||||
self.assertEqual(agg, marshalled_agg['aggregate'])
|
||||
|
||||
req = mock.MagicMock(api_version_request=avr_v240)
|
||||
marshalled_agg = self.controller._marshall_aggregate(req, agg_obj)
|
||||
|
||||
# UUID isn't in microversion 2.40 and before
|
||||
del agg['uuid']
|
||||
self.assertEqual(agg, marshalled_agg['aggregate'])
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- A new 2.41 microversion was added to the Compute API. Users specifying this
|
||||
microversion will now see the 'uuid' attribute of aggregates when calling
|
||||
the `os-aggregates` REST API endpoint.
|
Loading…
Reference in New Issue