[placement] Filter resource providers by forbidden traits in db

This is the db-side work for filtering resource providers by
forbidden traits. Since we control whether forbidden is allowed
or not at the API layer, we can add support here in the DB layer
such that it works all the time.

Subsequent patches will turn on the the functionality in a microversion.

Partially implements blueprint placement-forbidden-traits
Change-Id: I46796d49931df20b002d1a7e6bb3a6be34dabefa
This commit is contained in:
Chris Dent 2018-03-23 20:26:28 +00:00
parent 8bde4042da
commit e980f6198c
2 changed files with 59 additions and 1 deletions

View File

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

View File

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