Add support for 1.19 microversion

The 1.19 microversion of placement adds support for the
generation parameter when setting aggregates on a resource
provider. The value is an integer generation, available
when listing or showing resource providers.

If an earlier version is chosen, but a --generation arg
provided, an error is reported. Similarly, if not generation
is provided and the version is 1.19 or later, an error
is reported.

The new TestAggregate119 inherits from TestAggregate to
repeat the general validation it does, overrides those
methods which require a generation when using version
1.19, and adds two failure tests. One test in the super
class is adjusted so that it works as desired in both
classes and in both versions of Python (Python3 has
different behavor of a *args before kw args). I considered
changing the signature of base.resource_provider_aggregate_set
but that would have meant changes is several places.

Change-Id: I74157998dd19d8a8d2b68d12ed3726aac11eb013
This commit is contained in:
Chris Dent 2019-03-05 17:29:12 +00:00
parent 53b32590ad
commit 206978486f
5 changed files with 137 additions and 4 deletions

View File

@ -11,6 +11,7 @@
# under the License.
from osc_lib.command import command
from osc_lib import exceptions
from osc_placement import version
@ -18,7 +19,7 @@ BASE_URL = '/resource_providers/{uuid}/aggregates'
FIELDS = ('uuid',)
class SetAggregate(command.Lister):
class SetAggregate(command.Lister, version.CheckerMixin):
"""Associate a list of aggregates with the resource provider.
@ -48,6 +49,17 @@ class SetAggregate(command.Lister):
default=[]
)
parser.add_argument(
'--generation',
metavar='<resource_provider_generation>',
type=int,
help='The generation of resource provider. Must match the server-'
'side generation of the resource provider or the operation '
'will fail.\n\n'
'This param requires at least '
'``--os-placement-api-version 1.19``.'
)
return parser
@version.check(version.ge('1.1'))
@ -55,7 +67,24 @@ class SetAggregate(command.Lister):
http = self.app.client_manager.placement
url = BASE_URL.format(uuid=parsed_args.uuid)
resp = http.request('PUT', url, json=parsed_args.aggregate).json()
aggregate = parsed_args.aggregate
generation = None
if 'generation' in parsed_args and parsed_args.generation is not None:
self.check_version(version.ge('1.19'))
generation = parsed_args.generation
if self.compare_version(version.lt('1.19')):
resp = http.request('PUT', url, json=aggregate).json()
# Microversion 1.19 and beyond a generation argument is
# required to write aggregates.
elif generation is not None:
data = {'aggregates': aggregate,
'resource_provider_generation': generation}
resp = http.request('PUT', url, json=data).json()
else:
raise exceptions.CommandError(
'A generation must be specified.')
return FIELDS, [[r] for r in resp['aggregates']]

View File

@ -220,10 +220,14 @@ class BaseTestCase(base.BaseTestCase):
return self.openstack('resource provider aggregate list ' + uuid,
use_json=True)
def resource_provider_aggregate_set(self, uuid, *aggregates):
def resource_provider_aggregate_set(self, uuid, *aggregates,
**kwargs):
generation = kwargs.get('generation')
cmd = 'resource provider aggregate set %s ' % uuid
cmd += ' '.join('--aggregate %s' % aggregate
for aggregate in aggregates)
if generation is not None:
cmd += ' --generation %s' % generation
return self.openstack(cmd, use_json=True)
def resource_class_list(self):

View File

@ -82,7 +82,98 @@ class TestAggregate(base.BaseTestCase):
def test_fail_if_incorrect_aggregate_uuid(self):
rp = self.resource_provider_create()
aggs = ['abc', 'efg']
self.assertCommandFailed(
"is not a 'uuid'",
self.resource_provider_aggregate_set,
rp['uuid'], 'abc', 'efg')
rp['uuid'], *aggs)
# In version 1.1 a generation is not allowed.
def test_fail_generation_arg_version_handling(self):
rp = self.resource_provider_create()
agg = str(uuid.uuid4())
self.assertCommandFailed(
"Operation or argument is not supported with version 1.1",
self.resource_provider_aggregate_set,
rp['uuid'], agg, generation=5)
class TestAggregate119(TestAggregate):
VERSION = '1.19'
def test_success_set_aggregate(self):
rp = self.resource_provider_create()
aggs = {str(uuid.uuid4()) for _ in range(2)}
rows = self.resource_provider_aggregate_set(
rp['uuid'], *aggs, generation=rp['generation'])
self.assertEqual(aggs, {r['uuid'] for r in rows})
rows = self.resource_provider_aggregate_list(rp['uuid'])
self.assertEqual(aggs, {r['uuid'] for r in rows})
self.resource_provider_aggregate_set(
rp['uuid'], *[], generation=rp['generation'] + 1)
rows = self.resource_provider_aggregate_list(rp['uuid'])
self.assertEqual([], rows)
def test_success_set_multiple_aggregates(self):
# each rp is associated with two aggregates
rps = [self.resource_provider_create() for _ in range(2)]
aggs = {str(uuid.uuid4()) for _ in range(2)}
for rp in rps:
rows = self.resource_provider_aggregate_set(
rp['uuid'], *aggs, generation=rp['generation'])
self.assertEqual(aggs, {r['uuid'] for r in rows})
# remove association for the first aggregate
rows = self.resource_provider_aggregate_set(
rps[0]['uuid'], *[], generation=rp['generation'] + 1)
self.assertEqual([], rows)
# second rp should be in aggregates
rows = self.resource_provider_aggregate_list(rps[1]['uuid'])
self.assertEqual(aggs, {r['uuid'] for r in rows})
# cleanup
rows = self.resource_provider_aggregate_set(
rps[1]['uuid'], *[], generation=rp['generation'] + 1)
self.assertEqual([], rows)
def test_success_set_large_number_aggregates(self):
rp = self.resource_provider_create()
aggs = {str(uuid.uuid4()) for _ in range(100)}
rows = self.resource_provider_aggregate_set(
rp['uuid'], *aggs, generation=rp['generation'])
self.assertEqual(aggs, {r['uuid'] for r in rows})
rows = self.resource_provider_aggregate_set(
rp['uuid'], *[], generation=rp['generation'] + 1)
self.assertEqual([], rows)
def test_fail_incorrect_generation(self):
rp = self.resource_provider_create()
agg = str(uuid.uuid4())
self.assertCommandFailed(
"Please update the generation and try again.",
self.resource_provider_aggregate_set,
rp['uuid'], agg, generation=99999)
def test_fail_generation_not_int(self):
rp = self.resource_provider_create()
agg = str(uuid.uuid4())
self.assertCommandFailed(
"invalid int value",
self.resource_provider_aggregate_set,
rp['uuid'], agg, generation='barney')
def test_fail_if_incorrect_aggregate_uuid(self):
rp = self.resource_provider_create()
aggs = ['abc', 'efg']
self.assertCommandFailed(
"is not a 'uuid'",
self.resource_provider_aggregate_set,
rp['uuid'], *aggs, generation=rp['generation'])
# In version 1.19 a generation is required.
def test_fail_generation_arg_version_handling(self):
rp = self.resource_provider_create()
agg = str(uuid.uuid4())
self.assertCommandFailed(
"A generation must be specified.",
self.resource_provider_aggregate_set,
rp['uuid'], agg)

View File

@ -34,6 +34,7 @@ SUPPORTED_VERSIONS = [
'1.16',
'1.17',
'1.18',
'1.19',
]

View File

@ -0,0 +1,8 @@
---
features:
- |
Support is added for the `1.19`_ placement API microversion by adding
the ``--generation`` option to the
``openstack resource provider aggregate set`` command.
.. _1.19: https://docs.openstack.org/placement/latest/placement-api-microversion-history.html#include-generation-and-conflict-detection-in-provider-aggregates-apis