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
This commit is contained in:
parent
f3ed1e7112
commit
565fb8d8c4
|
@ -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='<parent_provider>',
|
||||
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='<uuid>',
|
||||
|
@ -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='<in_tree>',
|
||||
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='<parent_provider>',
|
||||
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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -27,7 +27,9 @@ SUPPORTED_VERSIONS = [
|
|||
'1.9',
|
||||
'1.10',
|
||||
'1.11',
|
||||
'1.12'
|
||||
'1.12',
|
||||
'1.13', # unused
|
||||
'1.14'
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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 <UUID>``.
|
||||
And ``resource provider list`` has a new option ``--in-tree <UUID>``.
|
||||
|
||||
.. _1.14: https://docs.openstack.org/nova/latest/user/placement.html#add-nested-resource-providers
|
||||
|
Loading…
Reference in New Issue