Prepare in_tree allocation candidates

This patch adds a filter of in_tree query for both paths,
_get_trees_matching_all() and _get_provider_ids_matching().

This patch changes _get_providers_with_resource() to filter it using
SQL. An alternative is to leave it simple and filter it out of the
function not using SQL, but we don't take this to get more performance
and note that this doesn't lose the logging capability of existing
aggregate/traits filter used for debugging.

Change-Id: I374b906e8084b6c432a22023154bef0c896767c3
Blueprint: alloc-candidates-in-tree
This commit is contained in:
Tetsuro Nakamura 2019-02-08 04:48:46 +00:00 committed by Eric Fried
parent dcb359055e
commit e7f3b1d59d
3 changed files with 107 additions and 40 deletions

View File

@ -3019,10 +3019,10 @@ def _get_provider_ids_for_traits_and_aggs(ctx, required_traits,
@db_api.placement_context_manager.reader
def _get_provider_ids_matching(ctx, resources, required_traits,
forbidden_traits, member_of=None):
forbidden_traits, member_of, tree_root_id):
"""Returns a list of tuples of (internal provider ID, root provider ID)
that have available inventory to satisfy all the supplied requests for
resources.
resources. If no providers match, the empty list is returned.
:note: This function is used for scenarios that do NOT involve sharing
providers.
@ -3040,6 +3040,9 @@ def _get_provider_ids_matching(ctx, resources, required_traits,
the allocation_candidates returned will only be for
resource providers that are members of one or more of the
supplied aggregates of each aggregate UUID list.
:param tree_root_id: An optional root resource provider ID. If provided,
the result will be restricted to providers in the tree
with this root ID.
"""
# The iteratively filtered set of resource provider internal IDs that match
# all the constraints in the request
@ -3066,7 +3069,8 @@ def _get_provider_ids_matching(ctx, resources, required_traits,
first = True
for rc_id, amount in resources.items():
rc_name = _RC_CACHE.string_from_id(rc_id)
provs_with_resource = _get_providers_with_resource(ctx, rc_id, amount)
provs_with_resource = _get_providers_with_resource(
ctx, rc_id, amount, tree_root_id=tree_root_id)
LOG.debug("found %d providers with available %d %s",
len(provs_with_resource), amount, rc_name)
if not provs_with_resource:
@ -3111,13 +3115,16 @@ def _get_provider_ids_matching(ctx, resources, required_traits,
@db_api.placement_context_manager.reader
def _get_providers_with_resource(ctx, rc_id, amount):
def _get_providers_with_resource(ctx, rc_id, amount, tree_root_id=None):
"""Returns a set of tuples of (provider ID, root provider ID) of providers
that satisfy the request for a single resource class.
:param ctx: Session context to use
:param rc_id: Internal ID of resource class to check inventory for
:param amount: Amount of resource being requested
:param tree_root_id: An optional root provider ID. If provided, the results
are limited to the resource providers under the given
root resource provider.
"""
# SELECT rp.id, rp.root_provider_id
# FROM resource_providers AS rp
@ -3150,7 +3157,14 @@ def _get_providers_with_resource(ctx, rc_id, amount):
inv.c.resource_provider_id == usage.c.resource_provider_id)
sel = sa.select([rpt.c.id, rpt.c.root_provider_id])
sel = sel.select_from(inv_to_usage)
sel = sel.where(_capacity_check_clause(amount, usage, inv_tbl=inv))
where_conds = _capacity_check_clause(amount, usage, inv_tbl=inv)
if tree_root_id is not None:
where_conds = sa.and_(
# TODO(tetsuro): Bug#1799892: Remove this "or" condition in Train
sa.or_(rpt.c.root_provider_id == tree_root_id,
rpt.c.id == tree_root_id),
where_conds)
sel = sel.where(where_conds)
res = ctx.session.execute(sel).fetchall()
res = set((r[0], r[1]) for r in res)
# TODO(tetsuro): Bug#1799892: We could have old providers with no root
@ -3254,9 +3268,10 @@ def _get_trees_with_traits(ctx, rp_ids, required_traits, forbidden_traits):
return [(rp_id, root_id) for rp_id, root_id in res]
# TODO(tetsuro): Add debug log to this method
@db_api.placement_context_manager.reader
def _get_trees_matching_all(ctx, resources, required_traits, forbidden_traits,
sharing, member_of):
sharing, member_of, tree_root_id):
"""Returns a list of two-tuples (provider internal ID, root provider
internal ID) for providers that satisfy the request for resources.
@ -3298,6 +3313,9 @@ def _get_trees_matching_all(ctx, resources, required_traits, forbidden_traits,
provided, the allocation_candidates returned will only be
for resource providers that are members of one or more of
the supplied aggregates in each aggregate UUID list.
:param tree_root_id: An optional root provider ID. If provided, the results
are limited to the resource providers under the given
root resource provider.
"""
# We first grab the provider trees that have nodes that meet the request
# for each resource class. Once we have this information, we'll then do a
@ -3311,7 +3329,8 @@ def _get_trees_matching_all(ctx, resources, required_traits, forbidden_traits,
trees_with_inv = set()
for rc_id, amount in resources.items():
rc_provs_with_inv = _get_providers_with_resource(ctx, rc_id, amount)
rc_provs_with_inv = _get_providers_with_resource(
ctx, rc_id, amount, tree_root_id=tree_root_id)
if not rc_provs_with_inv:
# If there's no providers that have one of the resource classes,
# then we can short-circuit
@ -3320,11 +3339,13 @@ def _get_trees_matching_all(ctx, resources, required_traits, forbidden_traits,
provs_with_inv |= set((p[0], p[1], rc_id) for p in rc_provs_with_inv)
sharing_providers = sharing.get(rc_id)
if sharing_providers:
if sharing_providers and tree_root_id is None:
# There are sharing providers for this resource class, so we
# should also get combinations of (sharing provider, anchor root)
# in addition to (non-sharing provider, anchor root) we already
# have.
# in addition to (non-sharing provider, anchor root) we've just
# got via _get_providers_with_resource() above. We must skip this
# process if tree_root_id is provided via the ?in_tree=<rp_uuid>
# queryparam, because it restricts resources from another tree.
rc_provs_with_inv = _anchors_for_sharing_providers(
ctx, sharing_providers, get_id=True)
rc_provs_with_inv = set(
@ -4107,6 +4128,7 @@ class AllocationCandidates(object):
trait_map.update(_trait_ids_from_names(context, traits))
member_of = request.member_of
tree_root_id = None
any_sharing = any(sharing_providers.values())
if not request.use_same_provider and (has_trees or any_sharing):
@ -4127,7 +4149,7 @@ class AllocationCandidates(object):
return [], []
rp_tuples = _get_trees_matching_all(context, resources,
required_trait_map, forbidden_trait_map,
sharing_providers, member_of)
sharing_providers, member_of, tree_root_id)
return _alloc_candidates_multiple_providers(context, resources,
required_trait_map, forbidden_trait_map, rp_tuples)
@ -4138,7 +4160,8 @@ class AllocationCandidates(object):
# allocation requests.
rp_tuples = _get_provider_ids_matching(context, resources,
required_trait_map,
forbidden_trait_map, member_of)
forbidden_trait_map, member_of,
tree_root_id)
return _alloc_candidates_single_provider(context, resources, rp_tuples)
@classmethod

View File

@ -143,9 +143,15 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
orc.STANDARDS.index(orc.DISK_GB): 1500
}
empty_req_traits = {}
empty_forbidden_traits = {}
empty_agg = []
empty_root_id = None
# Run it!
res = rp_obj._get_provider_ids_matching(
self.ctx, resources, required_traits={}, forbidden_traits={})
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
empty_req_traits, empty_forbidden_traits, empty_agg,
empty_root_id)
# We should get all the incl_* RPs
expected = [incl_biginv_noalloc, incl_extra_full]
@ -160,8 +166,7 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
# arguments maps, keyed by trait name, of the trait internal ID
req_traits = {os_traits.HW_CPU_X86_AVX2: avx2_t.id}
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
required_traits=req_traits,
forbidden_traits={})
req_traits, empty_forbidden_traits, empty_agg, empty_root_id)
self.assertEqual([], res)
@ -169,20 +174,32 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
# This should result in no results returned as well.
excl_big_md_noalloc.set_traits([avx2_t])
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
required_traits=req_traits,
forbidden_traits={})
req_traits, empty_forbidden_traits, empty_agg, empty_root_id)
self.assertEqual([], res)
# OK, now add the trait to one of the incl_* providers and verify that
# provider now shows up in our results
incl_biginv_noalloc.set_traits([avx2_t])
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
required_traits=req_traits,
forbidden_traits={})
req_traits, empty_forbidden_traits, empty_agg, empty_root_id)
rp_ids = [r[0] for r in res]
self.assertEqual([incl_biginv_noalloc.id], rp_ids)
# Let's see if the tree_root_id filter works
root_id = incl_biginv_noalloc.id
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
empty_req_traits, empty_forbidden_traits, empty_agg, root_id)
rp_ids = [r[0] for r in res]
self.assertEqual([incl_biginv_noalloc.id], rp_ids)
# We don't get anything if the specified tree doesn't satisfy the
# requirements in the first place
root_id = excl_allused.id
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
empty_req_traits, empty_forbidden_traits, empty_agg, root_id)
self.assertEqual([], res)
def test_get_provider_ids_matching_with_multiple_forbidden(self):
rp1 = self._create_provider('rp1', uuids.agg1)
tb.add_inventory(rp1, orc.VCPU, 64)
@ -196,12 +213,14 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
tb.add_inventory(rp3, orc.VCPU, 64)
resources = {orc.STANDARDS.index(orc.VCPU): 4}
res = rp_obj._get_provider_ids_matching(
self.ctx, resources,
required_traits={},
forbidden_traits={trait_two.name: trait_two.id,
trait_three.name: trait_three.id},
member_of=[[uuids.agg1]])
empty_req_traits = {}
forbidden_traits = {trait_two.name: trait_two.id,
trait_three.name: trait_three.id}
member_of = [[uuids.agg1]]
empty_root_id = None
res = rp_obj._get_provider_ids_matching(self.ctx, resources,
empty_req_traits, forbidden_traits, member_of, empty_root_id)
self.assertEqual({(rp1.id, rp1.id)}, set(res))
def test_get_provider_ids_having_all_traits(self):
@ -2222,6 +2241,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
rp_ids = set([r[0] for r in conn.execute(sel)])
return rp_ids
# TODO(tetsuro): refactor and split this function into smaller pieces
def test_trees_matching_all(self):
"""Creates a few provider trees having different inventories and
allocations and tests the _get_trees_matching_all_resources() utility
@ -2239,11 +2259,12 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
forbidden_traits = {}
member_of = []
sharing = {}
tree_root_id = None
# Before we even set up any providers, verify that the short-circuits
# work to return empty lists
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
self.assertEqual([], trees)
# We are setting up 3 trees of providers that look like this:
@ -2286,8 +2307,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# has inventory we will use...
tb.set_traits(cn, os_traits.HW_NIC_OFFLOAD_GENEVE)
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
# trees is a list of two-tuples of (provider ID, root provider ID)
tree_root_ids = set(p[1] for p in trees)
expect_root_ids = self._get_rp_ids_matching_names(cn_names)
@ -2301,6 +2322,21 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
expect_provider_ids = self._get_rp_ids_matching_names(provider_names)
self.assertEqual(expect_provider_ids, provider_ids)
# Let's see if the tree_root_id filter works
tree_root_id = self.get_provider_id_by_name('cn1')
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
tree_root_ids = set(p[1] for p in trees)
self.assertEqual(1, len(tree_root_ids))
# let's validate providers in tree as well
provider_ids = set(p[0] for p in trees)
provider_names = ['cn1', 'cn1_numa0_pf0', 'cn1_numa1_pf1']
expect_provider_ids = self._get_rp_ids_matching_names(provider_names)
self.assertEqual(expect_provider_ids, provider_ids)
tree_root_id = None
# OK, now consume all the VFs in the second compute node and verify
# only the first and third computes are returned as root providers from
# _get_trees_matching_all()
@ -2312,8 +2348,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
uuids.cn2_numa1_pf1)
self.allocate_from_provider(cn2_pf1, orc.SRIOV_NET_VF, 8)
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
tree_root_ids = set(p[1] for p in trees)
self.assertEqual(2, len(tree_root_ids))
@ -2341,8 +2377,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
req_traits = {
geneve_t.name: geneve_t.id,
}
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
tree_root_ids = set(p[1] for p in trees)
self.assertEqual(1, len(tree_root_ids))
@ -2372,8 +2408,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
geneve_t.name: geneve_t.id,
avx2_t.name: avx2_t.id,
}
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
tree_root_ids = set(p[1] for p in trees)
self.assertEqual(0, len(tree_root_ids))
@ -2385,8 +2421,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
forbidden_traits = {
avx2_t.name: avx2_t.id,
}
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
tree_root_ids = set(p[1] for p in trees)
self.assertEqual(1, len(tree_root_ids))
@ -2424,8 +2460,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
uuids.cn3_numa1_pf1)
self.allocate_from_provider(cn3_pf1, orc.SRIOV_NET_VF, 8)
trees = rp_obj._get_trees_matching_all(self.ctx,
resources, req_traits, forbidden_traits, sharing, member_of)
trees = rp_obj._get_trees_matching_all(self.ctx, resources, req_traits,
forbidden_traits, sharing, member_of, tree_root_id)
self.assertEqual([], trees)
def test_simple_tree_with_shared_provider(self):

View File

@ -101,12 +101,20 @@ class PlacementDbBaseTestCase(base.TestCase):
# For debugging purposes, populated by _create_provider and used by
# _validate_allocation_requests to make failure results more readable.
self.rp_uuid_to_name = {}
self.rp_id_to_name = {}
def _create_provider(self, name, *aggs, **kwargs):
rp = create_provider(self.ctx, name, *aggs, **kwargs)
self.rp_uuid_to_name[rp.uuid] = name
self.rp_id_to_name[rp.id] = name
return rp
def get_provider_id_by_name(self, name):
rp_ids = [k for k, v in self.rp_id_to_name.items() if v == name]
if not len(rp_ids) == 1:
raise Exception
return rp_ids[0]
def allocate_from_provider(self, rp, rc, used, consumer_id=None,
consumer=None):
if consumer is None: