From 206978486f874c6e0d7c49b24fb2392dd5a9d657 Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Tue, 5 Mar 2019 17:29:12 +0000 Subject: [PATCH] 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 --- osc_placement/resources/aggregate.py | 33 ++++++- osc_placement/tests/functional/base.py | 6 +- .../tests/functional/test_aggregate.py | 93 ++++++++++++++++++- osc_placement/version.py | 1 + ...aggregate-generation-c276739ec1cbc549.yaml | 8 ++ 5 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/microversion-1.19-resource-provider-aggregate-generation-c276739ec1cbc549.yaml diff --git a/osc_placement/resources/aggregate.py b/osc_placement/resources/aggregate.py index 0d83c84..12c76f3 100644 --- a/osc_placement/resources/aggregate.py +++ b/osc_placement/resources/aggregate.py @@ -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='', + 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']] diff --git a/osc_placement/tests/functional/base.py b/osc_placement/tests/functional/base.py index c4ec2c6..2dd5767 100644 --- a/osc_placement/tests/functional/base.py +++ b/osc_placement/tests/functional/base.py @@ -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): diff --git a/osc_placement/tests/functional/test_aggregate.py b/osc_placement/tests/functional/test_aggregate.py index 01d04a9..2598dcd 100644 --- a/osc_placement/tests/functional/test_aggregate.py +++ b/osc_placement/tests/functional/test_aggregate.py @@ -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) diff --git a/osc_placement/version.py b/osc_placement/version.py index efdc1fb..7434618 100644 --- a/osc_placement/version.py +++ b/osc_placement/version.py @@ -34,6 +34,7 @@ SUPPORTED_VERSIONS = [ '1.16', '1.17', '1.18', + '1.19', ] diff --git a/releasenotes/notes/microversion-1.19-resource-provider-aggregate-generation-c276739ec1cbc549.yaml b/releasenotes/notes/microversion-1.19-resource-provider-aggregate-generation-c276739ec1cbc549.yaml new file mode 100644 index 0000000..c20dfd2 --- /dev/null +++ b/releasenotes/notes/microversion-1.19-resource-provider-aggregate-generation-c276739ec1cbc549.yaml @@ -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