diff --git a/osc_placement/resources/allocation.py b/osc_placement/resources/allocation.py index f1384fe..6d1cd18 100644 --- a/osc_placement/resources/allocation.py +++ b/osc_placement/resources/allocation.py @@ -155,8 +155,8 @@ class SetAllocation(command.Lister, version.CheckerMixin): class UnsetAllocation(command.Lister, version.CheckerMixin): """Removes one or more sets of provider allocations for a consumer. - Note that omitting the ``--provider`` option is equivalent to removing - all allocations for the given consumer. + Note that omitting both the ``--provider`` and the ``--resource-class`` + option is equivalent to removing all allocations for the given consumer. This command requires ``--os-placement-api-version 1.12`` or greater. Use ``openstack resource provider allocation set`` for older versions. @@ -168,7 +168,10 @@ class UnsetAllocation(command.Lister, version.CheckerMixin): parser.add_argument( 'uuid', metavar='', - help='UUID of the consumer' + help='UUID of the consumer. It is strongly recommended to use ' + '``--os-placement-api-version 1.28`` or greater when using ' + 'this option to ensure the other allocation information is ' + 'retained. ' ) parser.add_argument( '--provider', @@ -180,22 +183,27 @@ class UnsetAllocation(command.Lister, version.CheckerMixin): 'consumer has allocations on more than one provider, for ' 'example after evacuating a server to another compute node ' 'and you want to cleanup allocations on the source compute ' - 'node resource provider in order to delete it. It is ' - 'strongly recommended to use ' - '``--os-placement-api-version 1.28`` or greater when using ' - 'this option to ensure the other allocation information is ' - 'retained. Specify multiple times to remove allocations ' - 'against multiple resource providers. Omit this option to ' - 'remove all allocations for the consumer.' + 'node resource provider in order to delete it. Specify ' + 'multiple times to remove allocations against multiple ' + 'resource providers. Omit this option to remove all ' + 'allocations for the consumer, or to remove all allocations' + 'of a specific resource class from all the resource provider ' + 'with the ``--resource_class`` option. ' + ) + parser.add_argument( + '--resource-class', + metavar='resource_class', + action='append', + default=[], + help='Name of a resource class from which to remove allocations ' + 'for the given consumer. This is useful when the consumer ' + 'has allocations on more than one resource class. ' + 'By default, this will remove allocations for the given ' + 'resource class from all the providers. If ``--provider`` ' + 'option is also specified, allocations to remove will be ' + 'limited to that resource class of the given resource ' + 'provider.' ) - # TODO(mriedem): Add a --resource-class option which can be used with - # or without --provider, e.g.: - # 1. allocation unset --provider P --resource-class VGPU = remove - # VGPU allocation from provider P for this consumer. - # 2. allocation unset --resource-class VGPU = remove VGPU allocations - # from all providers for this consumer. - # Make sure to update the note about omitting --provider in the - # command description above (due to example 2). return parser # NOTE(mriedem): We require >= 1.12 because PUT requires project_id/user_id @@ -210,19 +218,35 @@ class UnsetAllocation(command.Lister, version.CheckerMixin): # Get the current allocations. payload = http.request('GET', url).json() + allocations = payload['allocations'] - if parsed_args.provider: - allocations = payload['allocations'] - # Remove the given provider(s) from the allocations if it exists. - # Do not error out if the consumer does not have allocations - # against a provider in case we lost a race since the allocations - # are in the state the user wants them in anyway. - for rp_uuid in parsed_args.provider: - allocations.pop(rp_uuid, None) + if parsed_args.resource_class: + # Remove the given resource class. Do not error out if the + # consumer does not have allocations against that resource + # class. + rp_uuids = set(allocations) + if parsed_args.provider: + # If providers are also specified, we limit to remove + # allocations only from those providers + rp_uuids &= set(parsed_args.provider) + for rp_uuid in rp_uuids: + for rc in parsed_args.resource_class: + allocations[rp_uuid]['resources'].pop(rc, None) + if not allocations[rp_uuid]['resources']: + allocations.pop(rp_uuid, None) else: - # No --provider(s) specified so remove allocations from all - # providers. - allocations = {} + if parsed_args.provider: + # Remove the given provider(s) from the allocations if it + # exists. Do not error out if the consumer does not have + # allocations against a provider in case we lost a race since + # the allocations are in the state the user wants them in + # anyway. + for rp_uuid in parsed_args.provider: + allocations.pop(rp_uuid, None) + else: + # No --provider(s) specified so remove allocations from all + # providers. + allocations = {} supports_consumer_generation = self.compare_version(version.ge('1.28')) # 1.28+ allows PUTing an empty allocations dict as long as a diff --git a/osc_placement/tests/functional/base.py b/osc_placement/tests/functional/base.py index b1be13c..d7808a2 100644 --- a/osc_placement/tests/functional/base.py +++ b/osc_placement/tests/functional/base.py @@ -260,19 +260,18 @@ class BaseTestCase(base.BaseTestCase): return result def resource_allocation_unset(self, consumer_uuid, provider=None, - use_json=True): + resource_class=None, use_json=True): + cmd = 'resource provider allocation unset %s' % consumer_uuid + if resource_class: + cmd += ' ' + ' '.join( + '--resource-class %s' % rc for rc in resource_class) if provider: # --provider can be specified multiple times so if we only get # a single string value convert to a list. if isinstance(provider, six.string_types): provider = [provider] - cmd = 'resource provider allocation unset %s %s' % ( - ' '.join('--provider %s' % - rp_uuid for rp_uuid in provider), - consumer_uuid - ) - else: - cmd = 'resource provider allocation unset %s' % consumer_uuid + cmd += ' ' + ' '.join( + '--provider %s' % rp_uuid for rp_uuid in provider) result = self.openstack(cmd, use_json=use_json) def cleanup(uuid): diff --git a/osc_placement/tests/functional/test_allocation.py b/osc_placement/tests/functional/test_allocation.py index 68d06d5..affd11b 100644 --- a/osc_placement/tests/functional/test_allocation.py +++ b/osc_placement/tests/functional/test_allocation.py @@ -22,12 +22,8 @@ class TestAllocation(base.BaseTestCase): super(TestAllocation, self).setUp() self.rp1 = self.resource_provider_create() - self.inv_cpu1 = self.resource_inventory_set( - self.rp1['uuid'], - 'VCPU=4', - 'VCPU:max_unit=4', - 'MEMORY_MB=1024', - 'MEMORY_MB:max_unit=1024') + self.resource_inventory_set( + self.rp1['uuid'], 'VCPU=4', 'MEMORY_MB=1024') def test_allocation_show_not_found(self): consumer_uuid = str(uuid.uuid4()) @@ -141,12 +137,8 @@ class TestAllocation112(base.BaseTestCase): super(TestAllocation112, self).setUp() self.rp1 = self.resource_provider_create() - self.inv_cpu1 = self.resource_inventory_set( - self.rp1['uuid'], - 'VCPU=4', - 'VCPU:max_unit=4', - 'MEMORY_MB=1024', - 'MEMORY_MB:max_unit=1024') + self.resource_inventory_set( + self.rp1['uuid'], 'VCPU=4', 'MEMORY_MB=1024') def test_allocation_update(self): consumer_uuid = str(uuid.uuid4()) @@ -230,32 +222,49 @@ class TestAllocationUnset112(base.BaseTestCase): def setUp(self): super(TestAllocationUnset112, self).setUp() - # Create two providers with inventory. + # Create four providers with inventory. self.rp1 = self.resource_provider_create() - self.inv_cpu1 = self.resource_inventory_set( - self.rp1['uuid'], - 'VCPU=4', - 'VCPU:max_unit=4', - 'MEMORY_MB=1024', - 'MEMORY_MB:max_unit=1024') self.rp2 = self.resource_provider_create() - self.resource_inventory_set(self.rp2['uuid'], 'VGPU=1') - # Create allocations against both providers for the same consumer. - self.consumer_uuid = str(uuid.uuid4()) + self.rp3 = self.resource_provider_create() + self.rp4 = self.resource_provider_create() + + self.resource_inventory_set( + self.rp1['uuid'], 'VCPU=4', 'MEMORY_MB=1024') + self.resource_inventory_set( + self.rp2['uuid'], 'VGPU=1') + self.resource_inventory_set( + self.rp3['uuid'], 'VCPU=4', 'MEMORY_MB=1024', 'VGPU=1') + self.resource_inventory_set( + self.rp4['uuid'], 'VCPU=4', 'MEMORY_MB=1024', 'VGPU=1') + + self.consumer_uuid1 = str(uuid.uuid4()) + self.consumer_uuid2 = str(uuid.uuid4()) self.project_uuid = str(uuid.uuid4()) self.user_uuid = str(uuid.uuid4()) + + # Create allocations against rp1 and rp2 for consumer1. self.resource_allocation_set( - self.consumer_uuid, + self.consumer_uuid1, ['rp={},VCPU=2'.format(self.rp1['uuid']), 'rp={},MEMORY_MB=512'.format(self.rp1['uuid']), 'rp={},VGPU=1'.format(self.rp2['uuid'])], project_id=self.project_uuid, user_id=self.user_uuid) + # Create allocations against rp3 and rp4 for consumer1. + self.resource_allocation_set( + self.consumer_uuid2, + ['rp={},VCPU=1'.format(self.rp3['uuid']), + 'rp={},MEMORY_MB=256'.format(self.rp3['uuid']), + 'rp={},VGPU=1'.format(self.rp3['uuid']), + 'rp={},VCPU=1'.format(self.rp4['uuid']), + 'rp={},MEMORY_MB=256'.format(self.rp4['uuid'])], + project_id=self.project_uuid, user_id=self.user_uuid) + def test_allocation_unset_one_provider(self): """Tests removing allocations for one specific provider.""" # Remove the allocation for rp1. updated_allocs = self.resource_allocation_unset( - self.consumer_uuid, provider=self.rp1['uuid']) + self.consumer_uuid1, provider=self.rp1['uuid']) expected = [ {'resource_provider': self.rp2['uuid'], 'generation': 3, @@ -265,12 +274,62 @@ class TestAllocationUnset112(base.BaseTestCase): ] self.assertEqual(expected, updated_allocs) + def test_allocation_unset_one_resource_class(self): + """Tests removing allocations for resource classes.""" + updated_allocs = self.resource_allocation_unset( + self.consumer_uuid2, resource_class=['MEMORY_MB']) + expected = [ + {'resource_provider': self.rp3['uuid'], + 'generation': 3, + 'project_id': self.project_uuid, + 'user_id': self.user_uuid, + 'resources': {'VCPU': 1, 'VGPU': 1}}, + {'resource_provider': self.rp4['uuid'], + 'generation': 3, + 'project_id': self.project_uuid, + 'user_id': self.user_uuid, + 'resources': {'VCPU': 1}} + ] + self.assertEqual(expected, updated_allocs) + + def test_allocation_unset_resource_classes(self): + """Tests removing allocations for resource classes.""" + updated_allocs = self.resource_allocation_unset( + self.consumer_uuid2, resource_class=['VCPU', 'MEMORY_MB']) + expected = [ + {'resource_provider': self.rp3['uuid'], + 'generation': 3, + 'project_id': self.project_uuid, + 'user_id': self.user_uuid, + 'resources': {'VGPU': 1}} + ] + self.assertEqual(expected, updated_allocs) + + def test_allocation_unset_provider_and_rc(self): + """Tests removing allocations of resource classes for a provider .""" + updated_allocs = self.resource_allocation_unset( + self.consumer_uuid2, provider=self.rp3['uuid'], + resource_class=['VCPU', 'MEMORY_MB']) + expected = [ + {'resource_provider': self.rp3['uuid'], + 'generation': 3, + 'project_id': self.project_uuid, + 'user_id': self.user_uuid, + 'resources': {'VGPU': 1}}, + {'resource_provider': self.rp4['uuid'], + 'generation': 3, + 'project_id': self.project_uuid, + 'user_id': self.user_uuid, + 'resources': {'VCPU': 1, 'MEMORY_MB': 256}}, + ] + self.assertEqual(expected, updated_allocs) + def test_allocation_unset_remove_all_providers(self): """Tests removing all allocations by omitting the --provider option.""" # For this test pass use_json=False to make sure we get nothing back # in the output since there are no more allocations. updated_allocs = self.resource_allocation_unset( - self.consumer_uuid, use_json=False) + self.consumer_uuid1, use_json=False) self.assertEqual('', updated_allocs.strip()) diff --git a/releasenotes/notes/allocation-unset-resource-3ff87787eca13f18.yaml b/releasenotes/notes/allocation-unset-resource-3ff87787eca13f18.yaml new file mode 100644 index 0000000..a30fc68 --- /dev/null +++ b/releasenotes/notes/allocation-unset-resource-3ff87787eca13f18.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + The ``openstack resource provider allocation unset`` command now supports + ``--resource-class`` option, which accepts string of a resource class. + This will remove allocations for the given resource class from all the + providers. If ``--provider`` option is also specified, allocations to + remove will be limited to the given resource class of the given resource + provider. + + example1:: + + # remove VGPU allocation from provider P for this consumer. + allocation unset --provider P --resource-class VGPU + + example2:: + + # remove VGPU allocations from all providers for this consumer. + allocation unset --resource-class VGPU