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:
parent
dcb359055e
commit
e7f3b1d59d
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue