Merge "placement: allow filter providers in tree"

This commit is contained in:
Zuul 2017-12-07 08:56:32 +00:00 committed by Gerrit Code Review
commit ede4cf2ef6
2 changed files with 190 additions and 5 deletions

View File

@ -1411,7 +1411,8 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
# 'resources': {
# 'VCPU': 1,
# 'MEMORY_MB': 1024
# }
# },
# 'in_tree': <uuid>,
# }
if not filters:
filters = {}
@ -1454,6 +1455,26 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
query = query.where(rp.c.name == name)
if uuid:
query = query.where(rp.c.uuid == uuid)
if 'in_tree' in filters:
# The 'in_tree' parameter is the UUID of a resource provider that
# the caller wants to limit the returned providers to only those
# within its "provider tree". So, we look up the resource provider
# having the UUID specified by the 'in_tree' parameter and grab the
# root_provider_id value of that record. We can then ask for only
# those resource providers having a root_provider_id of that value.
tree_uuid = filters.pop('in_tree')
tree_ids = _provider_ids_from_uuid(context, tree_uuid)
if tree_ids is None:
# List operations should simply return an empty list when a
# non-existing resource provider UUID is given.
return []
root_id = tree_ids.root_id
# TODO(jaypipes): Remove this OR condition when root_provider_id
# is not nullable in the database and all resource provider records
# have populated the root provider ID.
where_cond = sa.or_(rp.c.id == root_id,
rp.c.root_provider_id == root_id)
query = query.where(where_cond)
# If 'member_of' has values join with the PlacementAggregates to
# get those resource providers that are associated with any of the
@ -1561,10 +1582,12 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
:param context: `nova.context.RequestContext` that may be used to grab
a DB connection.
:param filters: Can be `name`, `uuid`, `member_of` or `resources` where
`member_of` is a list of aggregate uuids and
`resources` is a dict of amounts keyed by resource
classes.
:param filters: Can be `name`, `uuid`, `member_of`, `in_tree` or
`resources` where `member_of` is a list of aggregate
uuids, `in_tree` is a UUID of a resource provider that
we can use to find the root provider ID of the tree of
providers to filter results by and `resources` is a
dict of amounts keyed by resource classes.
:type filters: dict
"""
_ensure_rc_cache(context)

View File

@ -325,6 +325,124 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
)
grandchild_rp.set_inventory(inv_list)
# Check all providers returned when getting by root UUID
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.root_rp,
}
)
self.assertEqual(3, len(rps))
# Check all providers returned when getting by child UUID
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.child_rp,
}
)
self.assertEqual(3, len(rps))
# Check all providers returned when getting by grandchild UUID
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.grandchild_rp,
}
)
self.assertEqual(3, len(rps))
# Make sure that the member_of and uuid filters work with the in_tree
# filter
# No aggregate associations yet, so expect no records when adding a
# member_of filter
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'member_of': [uuidsentinel.agg],
'in_tree': uuidsentinel.grandchild_rp,
}
)
self.assertEqual(0, len(rps))
# OK, associate the grandchild with an aggregate and verify that ONLY
# the grandchild is returned when asking for the grandchild's tree
# along with the aggregate as member_of
grandchild_rp.set_aggregates([uuidsentinel.agg])
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'member_of': [uuidsentinel.agg],
'in_tree': uuidsentinel.grandchild_rp,
}
)
self.assertEqual(1, len(rps))
self.assertEqual(uuidsentinel.grandchild_rp, rps[0].uuid)
# Try filtering on an unknown UUID and verify no results
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'uuid': uuidsentinel.unknown_rp,
'in_tree': uuidsentinel.grandchild_rp,
}
)
self.assertEqual(0, len(rps))
# And now check that filtering for just the child's UUID along with the
# tree produces just a single provider (the child)
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'uuid': uuidsentinel.child_rp,
'in_tree': uuidsentinel.grandchild_rp,
}
)
self.assertEqual(1, len(rps))
self.assertEqual(uuidsentinel.child_rp, rps[0].uuid)
# Ensure that the resources filter also continues to work properly with
# the in_tree filter. Request resources that none of the providers
# currently have and ensure no providers are returned
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.grandchild_rp,
'resources': {
'VCPU': 200,
}
}
)
self.assertEqual(0, len(rps))
# And now ask for one VCPU, which should only return us the grandchild
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.grandchild_rp,
'resources': {
'VCPU': 1,
}
}
)
self.assertEqual(1, len(rps))
self.assertEqual(uuidsentinel.grandchild_rp, rps[0].uuid)
# Finally, verify we still get the grandchild if filtering on the
# parent's UUID as in_tree
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.child_rp,
'resources': {
'VCPU': 1,
}
}
)
self.assertEqual(1, len(rps))
self.assertEqual(uuidsentinel.grandchild_rp, rps[0].uuid)
allocs = [
rp_obj.Allocation(
resource_provider=grandchild_rp,
@ -352,6 +470,50 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
child_rp.destroy()
root_rp.destroy()
def test_get_all_in_tree_old_records(self):
"""Simulate an old resource provider record in the database that has no
root_provider_uuid set and ensure that when selecting all providers in
a tree, passing in that old resource provider, that we still get that
provider returned.
"""
# Passing a non-existing resource provider UUID should return an empty
# list
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.rp1,
}
)
self.assertEqual([], rps.objects)
rp_tbl = rp_obj._RP_TBL
conn = self.api_db.get_engine().connect()
# First, set up a record for an "old-style" resource provider with no
# root provider UUID.
ins_stmt = rp_tbl.insert().values(
id=1,
uuid=uuidsentinel.rp1,
name='rp-1',
root_provider_id=None,
parent_provider_id=None,
generation=42,
)
conn.execute(ins_stmt)
# NOTE(jaypipes): This is just disabling the online data migration that
# occurs in _from_db_object() that sets root provider ID to ensure we
# don't have any migrations messing with the end result.
with mock.patch('nova.objects.resource_provider.'
'_set_root_provider_id'):
rps = rp_obj.ResourceProviderList.get_all_by_filters(
self.ctx,
filters={
'in_tree': uuidsentinel.rp1,
}
)
self.assertEqual(1, len(rps))
def test_destroy_resource_provider(self):
created_resource_provider = rp_obj.ResourceProvider(
context=self.ctx,