From 565fb8d8c4e7dff0e3df1f6708a81c0b6dc13c75 Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Wed, 21 Feb 2018 11:07:41 +0100 Subject: [PATCH] Add nested resource providers (v1.14) New fields to the output of resource provider list/show/create/set: root_provider_uuid parent_provider_uuid New optional parameter to resource provider create: --parent-provider UUID New optional parameter to resource provider set: --parent-provider UUID New optional parameter to resource provider list: --in-tree UUID Change-Id: Iafefbfce6b439190909476fd40ccb74ffb511b5d Partially-Implements: blueprint placement-osc-plugin-rocky Related-Bug: #1770636 --- osc_placement/resources/resource_provider.py | 86 ++++++++++++++++--- osc_placement/tests/functional/base.py | 19 ++-- .../functional/test_resource_provider.py | 78 +++++++++++++++++ osc_placement/version.py | 4 +- ...d-resource-providers-296961cc93ef30e8.yaml | 11 +++ 5 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/microversion-1.14-support-nested-resource-providers-296961cc93ef30e8.yaml diff --git a/osc_placement/resources/resource_provider.py b/osc_placement/resources/resource_provider.py index 38a26b3..a7bd875 100644 --- a/osc_placement/resources/resource_provider.py +++ b/osc_placement/resources/resource_provider.py @@ -19,15 +19,22 @@ from osc_placement import version BASE_URL = '/resource_providers' ALLOCATIONS_URL = BASE_URL + '/{uuid}/allocations' -FIELDS = ('uuid', 'name', 'generation') -class CreateResourceProvider(command.ShowOne): +class CreateResourceProvider(command.ShowOne, version.CheckerMixin): """Create a new resource provider""" def get_parser(self, prog_name): parser = super(CreateResourceProvider, self).get_parser(prog_name) + parser.add_argument( + '--parent-provider', + metavar='', + help='UUID of the parent provider.' + ' Omit for no parent.' + ' This option requires at least' + ' ``--os-placement-api-version 1.14``.' + ) parser.add_argument( '--uuid', metavar='', @@ -48,10 +55,19 @@ class CreateResourceProvider(command.ShowOne): if 'uuid' in parsed_args and parsed_args.uuid: data['uuid'] = parsed_args.uuid + if ('parent_provider' in parsed_args + and parsed_args.parent_provider): + self.check_version(version.ge('1.14')) + data['parent_provider_uuid'] = parsed_args.parent_provider resp = http.request('POST', BASE_URL, json=data) resource = http.request('GET', resp.headers['Location']).json() - return FIELDS, utils.get_dict_properties(resource, FIELDS) + + fields = ('uuid', 'name', 'generation') + if self.compare_version(version.ge('1.14')): + fields += ('root_provider_uuid', 'parent_provider_uuid') + + return fields, utils.get_dict_properties(resource, fields) class ListResourceProvider(command.Lister, version.CheckerMixin): @@ -94,6 +110,14 @@ class ListResourceProvider(command.Lister, version.CheckerMixin): 'This param requires at least ' '``--os-placement-api-version 1.4``.' ) + parser.add_argument( + '--in-tree', + metavar='', + help='Restrict listing to the same "provider tree"' + ' as the specified provider UUID.' + ' This option requires at least' + ' ``--os-placement-api-version 1.14``.' + ) return parser @@ -113,14 +137,22 @@ class ListResourceProvider(command.Lister, version.CheckerMixin): filters['resources'] = ','.join( resource.replace('=', ':') for resource in parsed_args.resource) + if 'in_tree' in parsed_args and parsed_args.in_tree: + self.check_version(version.ge('1.14')) + filters['in_tree'] = parsed_args.in_tree url = common.url_with_filters(BASE_URL, filters) resources = http.request('GET', url).json()['resource_providers'] - rows = (utils.get_dict_properties(r, FIELDS) for r in resources) - return FIELDS, rows + + fields = ('uuid', 'name', 'generation') + if self.compare_version(version.ge('1.14')): + fields += ('root_provider_uuid', 'parent_provider_uuid') + + rows = (utils.get_dict_properties(r, fields) for r in resources) + return fields, rows -class ShowResourceProvider(command.ShowOne): +class ShowResourceProvider(command.ShowOne, version.CheckerMixin): """Show resource provider details""" def get_parser(self, prog_name): @@ -145,18 +177,20 @@ class ShowResourceProvider(command.ShowOne): url = BASE_URL + '/' + parsed_args.uuid resource = http.request('GET', url).json() + fields = ('uuid', 'name', 'generation') + if self.compare_version(version.ge('1.14')): + fields += ('root_provider_uuid', 'parent_provider_uuid') + if parsed_args.allocations: allocs_url = ALLOCATIONS_URL.format(uuid=parsed_args.uuid) allocs = http.request('GET', allocs_url).json()['allocations'] resource['allocations'] = allocs + fields += ('allocations',) - fields_ext = FIELDS + ('allocations', ) - return fields_ext, utils.get_dict_properties(resource, fields_ext) - else: - return FIELDS, utils.get_dict_properties(resource, FIELDS) + return fields, utils.get_dict_properties(resource, fields) -class SetResourceProvider(command.ShowOne): +class SetResourceProvider(command.ShowOne, version.CheckerMixin): """Update an existing resource provider""" def get_parser(self, prog_name): @@ -173,6 +207,14 @@ class SetResourceProvider(command.ShowOne): help='A new name of the resource provider', required=True ) + parser.add_argument( + '--parent-provider', + metavar='', + help='UUID of the parent provider.' + ' Can only be set if the resource provider has no parent yet.' + ' This option requires at least' + ' ``--os-placement-api-version 1.14``.' + ) return parser @@ -180,9 +222,25 @@ class SetResourceProvider(command.ShowOne): http = self.app.client_manager.placement url = BASE_URL + '/' + parsed_args.uuid - resource = http.request('PUT', url, - json={'name': parsed_args.name}).json() - return FIELDS, utils.get_dict_properties(resource, FIELDS) + data = dict(name=parsed_args.name) + # Not knowing the previous state of a resource the client cannot catch + # it, but if the user tries to re-parent a resource provider the server + # returns an easy to understand error: + # Unable to save resource provider RP-ID: + # Object action update failed because: + # re-parenting a provider is not currently allowed. + # (HTTP 400) + if ('parent_provider' in parsed_args + and parsed_args.parent_provider): + self.check_version(version.ge('1.14')) + data['parent_provider_uuid'] = parsed_args.parent_provider + resource = http.request('PUT', url, json=data).json() + + fields = ('uuid', 'name', 'generation') + if self.compare_version(version.ge('1.14')): + fields += ('root_provider_uuid', 'parent_provider_uuid') + + return fields, utils.get_dict_properties(resource, fields) class DeleteResourceProvider(command.Command): diff --git a/osc_placement/tests/functional/base.py b/osc_placement/tests/functional/base.py index 46c7095..a6309ed 100644 --- a/osc_placement/tests/functional/base.py +++ b/osc_placement/tests/functional/base.py @@ -59,14 +59,18 @@ class BaseTestCase(base.BaseTestCase): message, e.output, 'Command "%s" fails with different message' % e.cmd) - def resource_provider_create(self, name=''): + def resource_provider_create(self, + name='', + parent_provider_uuid=None): if not name: random_part = ''.join(random.choice(string.ascii_letters) for i in range(10)) name = RP_PREFIX + random_part - res = self.openstack('resource provider create ' + name, - use_json=True) + to_exec = 'resource provider create ' + name + if parent_provider_uuid is not None: + to_exec += ' --parent-provider ' + parent_provider_uuid + res = self.openstack(to_exec, use_json=True) def cleanup(): try: @@ -80,8 +84,10 @@ class BaseTestCase(base.BaseTestCase): return res - def resource_provider_set(self, uuid, name): + def resource_provider_set(self, uuid, name, parent_provider_uuid=None): to_exec = 'resource provider set ' + uuid + ' --name ' + name + if parent_provider_uuid is not None: + to_exec += ' --parent-provider ' + parent_provider_uuid return self.openstack(to_exec, use_json=True) def resource_provider_show(self, uuid, allocations=False): @@ -92,7 +98,8 @@ class BaseTestCase(base.BaseTestCase): return self.openstack(cmd, use_json=True) def resource_provider_list(self, uuid=None, name=None, - aggregate_uuids=None, resources=None): + aggregate_uuids=None, resources=None, + in_tree=None): to_exec = 'resource provider list' if uuid: to_exec += ' --uuid ' + uuid @@ -103,6 +110,8 @@ class BaseTestCase(base.BaseTestCase): '--aggregate-uuid %s' % a for a in aggregate_uuids) if resources: to_exec += ' ' + ' '.join('--resource %s' % r for r in resources) + if in_tree: + to_exec += ' --in-tree ' + in_tree return self.openstack(to_exec, use_json=True) diff --git a/osc_placement/tests/functional/test_resource_provider.py b/osc_placement/tests/functional/test_resource_provider.py index d74541b..1842af2 100644 --- a/osc_placement/tests/functional/test_resource_provider.py +++ b/osc_placement/tests/functional/test_resource_provider.py @@ -185,3 +185,81 @@ class TestResourceProvider14(base.BaseTestCase): rps = self.resource_provider_list(resources=['PCI_DEVICE=16']) self.assertEqual(1, len(rps)) self.assertEqual(rp2['uuid'], rps[0]['uuid']) + + +class TestResourceProvider114(base.BaseTestCase): + VERSION = '1.14' + + def test_resource_provider_create(self): + created = self.resource_provider_create() + self.assertIn('root_provider_uuid', created) + self.assertIn('parent_provider_uuid', created) + + def test_resource_provider_set(self): + created = self.resource_provider_create() + updated = self.resource_provider_set( + created['uuid'], name='some_new_name') + self.assertIn('root_provider_uuid', updated) + self.assertIn('parent_provider_uuid', updated) + + def test_resource_provider_show(self): + created = self.resource_provider_create() + retrieved = self.resource_provider_show(created['uuid']) + self.assertIn('root_provider_uuid', retrieved) + self.assertIn('parent_provider_uuid', retrieved) + + def test_resource_provider_list(self): + self.resource_provider_create() + retrieved = self.resource_provider_list()[0] + self.assertIn('root_provider_uuid', retrieved) + self.assertIn('parent_provider_uuid', retrieved) + + def test_resource_provider_create_with_parent(self): + parent = self.resource_provider_create() + child = self.resource_provider_create( + parent_provider_uuid=parent['uuid']) + self.assertEqual(child['parent_provider_uuid'], parent['uuid']) + + def test_resource_provider_create_then_set_parent(self): + parent = self.resource_provider_create() + wannabe_child = self.resource_provider_create() + child = self.resource_provider_set( + wannabe_child['uuid'], + name='mandatory_name_1', + parent_provider_uuid=parent['uuid']) + self.assertEqual(child['parent_provider_uuid'], parent['uuid']) + + def test_resource_provider_set_reparent(self): + parent1 = self.resource_provider_create() + parent2 = self.resource_provider_create() + child = self.resource_provider_create( + parent_provider_uuid=parent1['uuid']) + exc = self.assertRaises( + subprocess.CalledProcessError, + self.resource_provider_set, + child['uuid'], + name='mandatory_name_2', + parent_provider_uuid=parent2['uuid']) + self.assertIn('HTTP 400', exc.output.decode('utf-8')) + + def test_resource_provider_list_in_tree(self): + rp1 = self.resource_provider_create() + rp2 = self.resource_provider_create( + parent_provider_uuid=rp1['uuid']) + rp3 = self.resource_provider_create( + parent_provider_uuid=rp1['uuid']) + self.resource_provider_create() # not in-tree + retrieved = self.resource_provider_list(in_tree=rp2['uuid']) + self.assertEqual( + set([rp['uuid'] for rp in retrieved]), + set([rp1['uuid'], rp2['uuid'], rp3['uuid']]) + ) + + def test_resource_provider_delete_parent(self): + parent = self.resource_provider_create() + self.resource_provider_create(parent_provider_uuid=parent['uuid']) + exc = self.assertRaises( + subprocess.CalledProcessError, + self.resource_provider_delete, + parent['uuid']) + self.assertIn('HTTP 409', exc.output.decode('utf-8')) diff --git a/osc_placement/version.py b/osc_placement/version.py index cf3ffe7..c3f0179 100644 --- a/osc_placement/version.py +++ b/osc_placement/version.py @@ -27,7 +27,9 @@ SUPPORTED_VERSIONS = [ '1.9', '1.10', '1.11', - '1.12' + '1.12', + '1.13', # unused + '1.14' ] diff --git a/releasenotes/notes/microversion-1.14-support-nested-resource-providers-296961cc93ef30e8.yaml b/releasenotes/notes/microversion-1.14-support-nested-resource-providers-296961cc93ef30e8.yaml new file mode 100644 index 0000000..aa9ca8f --- /dev/null +++ b/releasenotes/notes/microversion-1.14-support-nested-resource-providers-296961cc93ef30e8.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Support is added for the `1.14`_ placement API microversion by adding + the ``root_provider_uuid`` and ``parent_provider_uuid`` to the output of + resource provider list/show/create/set commands. Also resource provider + create/set commands now have a new option ``--parent-provider ``. + And ``resource provider list`` has a new option ``--in-tree ``. + + .. _1.14: https://docs.openstack.org/nova/latest/user/placement.html#add-nested-resource-providers +