diff --git a/nova/api/openstack/placement/objects/resource_provider.py b/nova/api/openstack/placement/objects/resource_provider.py index dffcd0acdbaf..ccc1b831150a 100644 --- a/nova/api/openstack/placement/objects/resource_provider.py +++ b/nova/api/openstack/placement/objects/resource_provider.py @@ -1433,7 +1433,11 @@ class ResourceProviderList(base.ObjectListBase, base.VersionedObject): name = filters.pop('name', None) uuid = filters.pop('uuid', None) member_of = filters.pop('member_of', []) - required = filters.pop('required', []) + required = set(filters.pop('required', [])) + forbidden = set([trait for trait in required + if trait.startswith('!')]) + required = required - forbidden + forbidden = set([trait.lstrip('!') for trait in forbidden]) resources = filters.pop('resources', {}) # NOTE(sbauza): We want to key the dict by the resource class IDs @@ -1514,6 +1518,17 @@ class ResourceProviderList(base.ObjectListBase, base.VersionedObject): return [] query = query.where(rp.c.id.in_(rp_ids)) + # If 'forbidden' has values, filter out those providers that have + # that trait as one their traits. + if forbidden: + trait_map = _trait_ids_from_names(context, forbidden) + if len(trait_map) != len(forbidden): + missing = forbidden - set(trait_map) + raise exception.TraitNotFound(names=', '.join(missing)) + rp_ids = _get_provider_ids_having_any_trait(context, trait_map) + if rp_ids: + query = query.where(~rp.c.id.in_(rp_ids)) + if not resources: # Returns quickly the list in case we don't need to check the # resource usage diff --git a/nova/tests/functional/api/openstack/placement/db/test_resource_provider.py b/nova/tests/functional/api/openstack/placement/db/test_resource_provider.py index 9dab292a030d..e477163a6a9d 100644 --- a/nova/tests/functional/api/openstack/placement/db/test_resource_provider.py +++ b/nova/tests/functional/api/openstack/placement/db/test_resource_provider.py @@ -1136,6 +1136,49 @@ class ResourceProviderListTestCase(ResourceProviderBaseCase): [uuidsentinel.agg_1, uuidsentinel.agg_2]}) self.assertEqual(0, len(resource_providers)) + def test_get_all_by_required(self): + # Create some resource providers and give them each 0 or more traits. + # rp_name_0: no traits + # rp_name_1: CUSTOM_TRAIT_A + # rp_name_2: CUSTOM_TRAIT_A, CUSTOM_TRAIT_B + # rp_name_3: CUSTOM_TRAIT_A, CUSTOM_TRAIT_B, CUSTOM_TRAIT_C + trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', + 'CUSTOM_TRAIT_C'] + trait_objects = [] + for trait in trait_names: + trait_object = rp_obj.Trait(self.ctx, name=trait) + trait_object.create() + trait_objects.append(trait_object) + for rp_i in [0, 1, 2, 3]: + uuid = getattr(uuidsentinel, 'rp_uuid_' + str(rp_i)) + name = 'rp_name_' + str(rp_i) + rp = rp_obj.ResourceProvider(self.ctx, name=name, uuid=uuid) + rp.create() + if rp_i: + traits = trait_objects[0:rp_i] + rp.set_traits(traits) + + # Three rps (1, 2, 3) should have CUSTOM_TRAIT_A + custom_a_rps = rp_obj.ResourceProviderList.get_all_by_filters( + self.ctx, filters={'required': ['CUSTOM_TRAIT_A']}) + self.assertEqual(3, len(custom_a_rps)) + rp_names = [a_rp.name for a_rp in custom_a_rps] + expected_names = ['rp_name_%s' % i for i in [1, 2, 3]] + self.assertEqual(expected_names, sorted(rp_names)) + + # One rp (rp 1) if we forbid CUSTOM_TRAIT_B, with a single trait of + # CUSTOM_TRAIT_A + custom_a_rps = rp_obj.ResourceProviderList.get_all_by_filters( + self.ctx, + filters={'required': ['CUSTOM_TRAIT_A', '!CUSTOM_TRAIT_B']}) + self.assertEqual(1, len(custom_a_rps)) + self.assertEqual(uuidsentinel.rp_uuid_1, custom_a_rps[0].uuid) + self.assertEqual('rp_name_1', custom_a_rps[0].name) + traits = rp_obj.TraitList.get_all_by_resource_provider( + self.ctx, custom_a_rps[0]) + self.assertEqual(1, len(traits)) + self.assertEqual('CUSTOM_TRAIT_A', traits[0].name) + class TestResourceProviderAggregates(test.NoDBTestCase):