Report client: get_allocations_for_provider_tree
The reshaper path needs to pass all the allocations related to the compute node's provider tree to update_provider_tree so it can shuffle those allocations appropriately. This patch adds a new get_allocations_for_provider_tree method to the report client for this purpose. Blueprint: reshape-provider-tree Change-Id: I73811f3e3bf19dec3a240e1f1f8c69f4c98d677c
This commit is contained in:
parent
176d1d90fd
commit
25b852efd7
|
@ -1997,6 +1997,75 @@ class SchedulerReportClient(object):
|
|||
data = resp.json()
|
||||
return ProviderAllocInfo(allocations=data['allocations'])
|
||||
|
||||
def get_allocations_for_provider_tree(self, context, nodename):
|
||||
"""Retrieve allocation records associated with all providers in the
|
||||
provider tree.
|
||||
|
||||
This method uses the cache exclusively to discover providers. The
|
||||
caller must ensure that the cache is populated.
|
||||
|
||||
This method is (and should remain) used exclusively in the reshaper
|
||||
flow by the resource tracker.
|
||||
|
||||
:param context: The security context
|
||||
:param nodename: The name of a node for whose tree we are getting
|
||||
allocations.
|
||||
:returns: A dict, keyed by consumer UUID, of allocation records:
|
||||
{ $CONSUMER_UUID: {
|
||||
# The shape of each "allocations" dict below is identical
|
||||
# to the return from GET /allocations/{consumer_uuid}
|
||||
"allocations": {
|
||||
$RP_UUID: {
|
||||
"generation": $RP_GEN,
|
||||
"resources": {
|
||||
$RESOURCE_CLASS: $AMOUNT,
|
||||
...
|
||||
},
|
||||
},
|
||||
...
|
||||
},
|
||||
"project_id": $PROJ_ID,
|
||||
"user_id": $USER_ID,
|
||||
"consumer_generation": $CONSUMER_GEN,
|
||||
},
|
||||
...
|
||||
}
|
||||
:raises: keystoneauth1.exceptions.ClientException if placement API
|
||||
communication fails.
|
||||
:raises: ResourceProviderAllocationRetrievalFailed if a placement API
|
||||
call fails.
|
||||
:raises: ValueError if there's no provider with the specified nodename.
|
||||
"""
|
||||
# NOTE(efried): Despite our best efforts, there are some scenarios
|
||||
# (e.g. mid-evacuate) where we can still wind up returning allocations
|
||||
# against providers belonging to other hosts. We count on the consumer
|
||||
# of this information (i.e. the reshaper flow of a virt driver's
|
||||
# update_provider_tree) to ignore allocations associated with any
|
||||
# provider it is not reshaping - and it should never be reshaping
|
||||
# providers belonging to other hosts.
|
||||
|
||||
# We can't get *all* allocations for associated sharing providers
|
||||
# because some of those will belong to consumers on other hosts. So we
|
||||
# have to discover all the consumers associated with the providers in
|
||||
# the "local" tree (we use the nodename to figure out which providers
|
||||
# are "local").
|
||||
# All we want to do at this point is accumulate the set of consumers we
|
||||
# care about.
|
||||
consumers = set()
|
||||
# TODO(efried): This could be more efficient if placement offered an
|
||||
# operation like GET /allocations?rp_uuid=in:<list>
|
||||
for u in self._provider_tree.get_provider_uuids(name_or_uuid=nodename):
|
||||
alloc_info = self.get_allocations_for_resource_provider(context, u)
|
||||
# The allocations dict is keyed by consumer UUID
|
||||
consumers.update(alloc_info.allocations)
|
||||
|
||||
# Now get all the allocations (which will include allocations on
|
||||
# sharing providers) for each of these consumers to build the result.
|
||||
# TODO(efried): This could be more efficient if placement offered an
|
||||
# operation like GET /allocations?consumer_uuid=in:<list>
|
||||
return {consumer: self.get_allocs_for_consumer(context, consumer)
|
||||
for consumer in consumers}
|
||||
|
||||
def delete_resource_provider(self, context, compute_node, cascade=False):
|
||||
"""Deletes the ResourceProvider record for the compute_node.
|
||||
|
||||
|
|
|
@ -1032,3 +1032,128 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
|
|||
with self._interceptor():
|
||||
self.client.get_allocation_candidates(
|
||||
self.context, utils.ResourceRequest())
|
||||
|
||||
def test_get_allocations_for_provider_tree(self):
|
||||
with self._interceptor():
|
||||
# When the provider tree cache is empty (or we otherwise supply a
|
||||
# bogus node name), we get ValueError.
|
||||
self.assertRaises(ValueError,
|
||||
self.client.get_allocations_for_provider_tree,
|
||||
self.context, 'bogus')
|
||||
|
||||
# get_provider_tree_and_ensure_root creates a resource provider
|
||||
# record for us
|
||||
ptree = self.client.get_provider_tree_and_ensure_root(
|
||||
self.context, self.compute_uuid, name=self.compute_name)
|
||||
ptree.update_inventory(self.compute_uuid,
|
||||
{'MEMORY_MB': {'total': 2048}})
|
||||
ptree.update_aggregates(self.compute_uuid, [uuids.agg1])
|
||||
|
||||
# These are part of the compute node's tree
|
||||
ptree.new_child('numa1', self.compute_uuid, uuid=uuids.numa1)
|
||||
ptree.update_inventory('numa1', {'VCPU': {'total': 8},
|
||||
'CUSTOM_PCPU': {'total': 8}})
|
||||
ptree.new_child('numa2', self.compute_uuid, uuid=uuids.numa2)
|
||||
ptree.update_inventory('numa2', {'VCPU': {'total': 8},
|
||||
'CUSTOM_PCPU': {'total': 8}})
|
||||
|
||||
# A sharing provider that's not part of the compute node's tree.
|
||||
# We avoid the report client's convenience methods to get bonus
|
||||
# coverage of the subsequent update_from_provider_tree pulling it
|
||||
# into the cache for us.
|
||||
resp = self.client.post(
|
||||
'/resource_providers',
|
||||
{'uuid': uuids.ssp, 'name': 'ssp'}, version='1.20')
|
||||
resp = self.client.put(
|
||||
'/resource_providers/%s/inventories' % uuids.ssp,
|
||||
{'inventories': {'DISK_GB': {'total': 500}},
|
||||
'resource_provider_generation': resp.json()['generation']})
|
||||
# Part of the shared storage aggregate
|
||||
resp = self.client.put(
|
||||
'/resource_providers/%s/aggregates' % uuids.ssp,
|
||||
{'aggregates': [uuids.agg1],
|
||||
'resource_provider_generation':
|
||||
resp.json()['resource_provider_generation']},
|
||||
version='1.19')
|
||||
self.client.put(
|
||||
'/resource_providers/%s/traits' % uuids.ssp,
|
||||
{'traits': ['MISC_SHARES_VIA_AGGREGATE'],
|
||||
'resource_provider_generation':
|
||||
resp.json()['resource_provider_generation']})
|
||||
|
||||
self.client.update_from_provider_tree(self.context, ptree)
|
||||
|
||||
# Another unrelated compute node. We don't use the report client's
|
||||
# convenience methods because we don't want this guy in the cache.
|
||||
resp = self.client.post(
|
||||
'/resource_providers',
|
||||
{'uuid': uuids.othercn, 'name': 'othercn'}, version='1.20')
|
||||
resp = self.client.put(
|
||||
'/resource_providers/%s/inventories' % uuids.othercn,
|
||||
{'inventories': {'VCPU': {'total': 8},
|
||||
'MEMORY_MB': {'total': 1024}},
|
||||
'resource_provider_generation': resp.json()['generation']})
|
||||
# Part of the shared storage aggregate
|
||||
self.client.put(
|
||||
'/resource_providers/%s/aggregates' % uuids.othercn,
|
||||
{'aggregates': [uuids.agg1],
|
||||
'resource_provider_generation':
|
||||
resp.json()['resource_provider_generation']},
|
||||
version='1.19')
|
||||
|
||||
# At this point, there are no allocations
|
||||
self.assertEqual({}, self.client.get_allocations_for_provider_tree(
|
||||
self.context, self.compute_name))
|
||||
|
||||
# Create some allocations on our compute (with sharing)
|
||||
cn_inst1_allocs = {
|
||||
'allocations': {
|
||||
self.compute_uuid: {'resources': {'MEMORY_MB': 512}},
|
||||
uuids.numa1: {'resources': {'VCPU': 2, 'CUSTOM_PCPU': 2}},
|
||||
uuids.ssp: {'resources': {'DISK_GB': 100}}
|
||||
},
|
||||
'consumer_generation': None,
|
||||
'project_id': uuids.proj,
|
||||
'user_id': uuids.user,
|
||||
}
|
||||
self.client.put('/allocations/' + uuids.cn_inst1, cn_inst1_allocs)
|
||||
cn_inst2_allocs = {
|
||||
'allocations': {
|
||||
self.compute_uuid: {'resources': {'MEMORY_MB': 256}},
|
||||
uuids.numa2: {'resources': {'CUSTOM_PCPU': 1}},
|
||||
uuids.ssp: {'resources': {'DISK_GB': 50}}
|
||||
},
|
||||
'consumer_generation': None,
|
||||
'project_id': uuids.proj,
|
||||
'user_id': uuids.user,
|
||||
}
|
||||
self.client.put('/allocations/' + uuids.cn_inst2, cn_inst2_allocs)
|
||||
# And on the other compute (with sharing)
|
||||
self.client.put(
|
||||
'/allocations/' + uuids.othercn_inst,
|
||||
{'allocations': {
|
||||
uuids.othercn: {'resources': {'VCPU': 2, 'MEMORY_MB': 64}},
|
||||
uuids.ssp: {'resources': {'DISK_GB': 30}}
|
||||
},
|
||||
'consumer_generation': None,
|
||||
'project_id': uuids.proj,
|
||||
'user_id': uuids.user,
|
||||
})
|
||||
|
||||
# And now we should get all the right allocations. Note that we see
|
||||
# nothing from othercn_inst.
|
||||
expected = {
|
||||
uuids.cn_inst1: cn_inst1_allocs,
|
||||
uuids.cn_inst2: cn_inst2_allocs,
|
||||
}
|
||||
actual = self.client.get_allocations_for_provider_tree(
|
||||
self.context, self.compute_name)
|
||||
# We don't care about the generations, and don't want to bother
|
||||
# figuring out the right ones, so just remove those fields before
|
||||
# checking equality
|
||||
for allocs in list(expected.values()) + list(actual.values()):
|
||||
del allocs['consumer_generation']
|
||||
for alloc in allocs['allocations'].values():
|
||||
if 'generation' in alloc:
|
||||
del alloc['generation']
|
||||
self.assertEqual(expected, actual)
|
||||
|
|
Loading…
Reference in New Issue