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:
Bence Romsics 2018-02-21 11:07:41 +01:00 committed by Matt Riedemann
parent f3ed1e7112
commit 565fb8d8c4
5 changed files with 178 additions and 20 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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'))

View File

@ -27,7 +27,9 @@ SUPPORTED_VERSIONS = [
'1.9',
'1.10',
'1.11',
'1.12'
'1.12',
'1.13', # unused
'1.14'
]

View File

@ -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