New-style _set_inventory_for_provider
Given the UUID of a provider, set the inventory records for the provider to the supplied dict of resources. Compare and contrast with the existing set_inventory_for_provider, which was created for the simpler get_inventory code path from the resource tracker. This one is specially formulated for use by update_from_provider_tree, because from there, whereas we DO need to _ensure_resource_class(), we ALSO want to short out if inv_data matches what's already cached, but we DON'T want to: - _ensure_resource_provider - refresh_and_get_inventory - DELETE if inventory is empty, else PUT - retry - invalidate the cache on failure This version is more in the spirit of set_traits_for_provider. Change-Id: I45f1df6ca5618f76337319ba225493625a5ee2d6 blueprint: update-provider-tree
This commit is contained in:
parent
a0dc24dc1a
commit
3921cbc9ec
|
@ -1030,6 +1030,113 @@ class SchedulerReportClient(object):
|
|||
# when we invoke the DELETE. See bug #1746374.
|
||||
self._update_inventory(context, rp_uuid, inv_data)
|
||||
|
||||
def _set_inventory_for_provider(self, context, rp_uuid, inv_data):
|
||||
"""Given the UUID of a provider, set the inventory records for the
|
||||
provider to the supplied dict of resources.
|
||||
|
||||
Compare and contrast with set_inventory_for_provider above. This one
|
||||
is specially formulated for use by update_from_provider_tree. Like the
|
||||
other method, we DO need to _ensure_resource_class - i.e. automatically
|
||||
create new resource classes specified in the inv_data. However, UNLIKE
|
||||
the other method:
|
||||
- We don't use the DELETE API when inventory is empty, because that guy
|
||||
doesn't return content, and we need to update the cached provider
|
||||
tree with the new generation.
|
||||
- We raise exceptions (rather than returning a boolean) which are
|
||||
handled in a consistent fashion by update_from_provider_tree.
|
||||
- We don't invalidate the cache on failure. That's controlled at a
|
||||
broader scope (based on errors from ANY of the set_*_for_provider
|
||||
methods, etc.) by update_from_provider_tree.
|
||||
- We don't retry. In this code path, retries happen at the level of
|
||||
the resource tracker on the next iteration.
|
||||
- We take advantage of the cache and no-op if inv_data isn't different
|
||||
from what we have locally. This is an optimization, not essential.
|
||||
- We don't _ensure_resource_provider or refresh_and_get_inventory,
|
||||
because that's already been done in the code paths leading up to
|
||||
update_from_provider_tree (by get_provider_tree). This is an
|
||||
optimization, not essential.
|
||||
|
||||
In short, this version is more in the spirit of set_traits_for_provider
|
||||
and set_aggregates_for_provider.
|
||||
|
||||
:param context: The security context
|
||||
:param rp_uuid: The UUID of the provider whose inventory is to be
|
||||
updated.
|
||||
:param inv_data: Dict, keyed by resource class name, of inventory data
|
||||
to set for the provider. Use None or the empty dict
|
||||
to remove all inventory for the provider.
|
||||
:raises: InventoryInUse if inv_data indicates removal of inventory in a
|
||||
resource class which has active allocations for this provider.
|
||||
:raises: InvalidResourceClass if inv_data contains a resource class
|
||||
which cannot be created.
|
||||
:raises: ResourceProviderUpdateConflict if the provider's generation
|
||||
doesn't match the generation in the cache. Callers may choose
|
||||
to retrieve the provider and its associations afresh and
|
||||
redrive this operation.
|
||||
:raises: ResourceProviderUpdateFailed on any other placement API
|
||||
failure.
|
||||
"""
|
||||
# TODO(efried): Consolidate/refactor to one set_inventory_for_provider.
|
||||
|
||||
# NOTE(efried): This is here because _ensure_resource_class already has
|
||||
# @safe_connect, so we don't want to decorate this whole method with it
|
||||
@safe_connect
|
||||
def do_put(url, payload):
|
||||
return self.put(url, payload, global_request_id=context.global_id)
|
||||
|
||||
# If not different from what we've got, short out
|
||||
if not self._provider_tree.has_inventory_changed(rp_uuid, inv_data):
|
||||
return
|
||||
|
||||
# Ensure non-standard resource classes exist, creating them if needed.
|
||||
self._ensure_resource_classes(context, set(inv_data))
|
||||
|
||||
url = '/resource_providers/%s/inventories' % rp_uuid
|
||||
inv_data = inv_data or {}
|
||||
generation = self._provider_tree.data(rp_uuid).generation
|
||||
payload = {
|
||||
'resource_provider_generation': generation,
|
||||
'inventories': inv_data,
|
||||
}
|
||||
resp = do_put(url, payload)
|
||||
|
||||
if resp.status_code == 200:
|
||||
json = resp.json()
|
||||
self._provider_tree.update_inventory(
|
||||
rp_uuid, json['inventories'],
|
||||
generation=json['resource_provider_generation'])
|
||||
return
|
||||
|
||||
# Some error occurred; log it
|
||||
msg = ("[%(placement_req_id)s] Failed to update inventory to "
|
||||
"[%(inv_data)s] for resource provider with UUID %(uuid)s. Got "
|
||||
"%(status_code)d: %(err_text)s")
|
||||
args = {
|
||||
'placement_req_id': get_placement_request_id(resp),
|
||||
'uuid': rp_uuid,
|
||||
'inv_data': str(inv_data),
|
||||
'status_code': resp.status_code,
|
||||
'err_text': resp.text,
|
||||
}
|
||||
LOG.error(msg, args)
|
||||
|
||||
if resp.status_code == 409:
|
||||
# If a conflict attempting to remove inventory in a resource class
|
||||
# with active allocations, raise InventoryInUse
|
||||
match = _RE_INV_IN_USE.search(resp.text)
|
||||
if match:
|
||||
rc = match.group(1)
|
||||
raise exception.InventoryInUse(
|
||||
resource_classes=rc,
|
||||
resource_provider=rp_uuid,
|
||||
)
|
||||
# Other conflicts are generation mismatch: raise conflict exception
|
||||
raise exception.ResourceProviderUpdateConflict(
|
||||
uuid=rp_uuid, generation=generation, error=resp.text)
|
||||
|
||||
# Otherwise, raise generic exception
|
||||
raise exception.ResourceProviderUpdateFailed(url=url, error=resp.text)
|
||||
|
||||
@safe_connect
|
||||
def _ensure_traits(self, context, traits):
|
||||
"""Make sure all specified traits exist in the placement service.
|
||||
|
|
|
@ -449,3 +449,240 @@ class SchedulerReportClientTests(test.TestCase):
|
|||
uuids.sbw, [uuids.agg_bw]))
|
||||
self.assertFalse(prov_tree.have_aggregates_changed(
|
||||
self.compute_uuid, [uuids.agg_disk_1, uuids.agg_disk_2]))
|
||||
|
||||
def test__set_inventory_for_provider(self):
|
||||
"""Tests for SchedulerReportClient._set_inventory_for_provider, NOT
|
||||
set_inventory_for_provider.
|
||||
"""
|
||||
with self._interceptor():
|
||||
inv = {
|
||||
fields.ResourceClass.SRIOV_NET_VF: {
|
||||
'total': 24,
|
||||
'reserved': 1,
|
||||
'min_unit': 1,
|
||||
'max_unit': 24,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
}
|
||||
# Provider doesn't exist in our cache
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.client._set_inventory_for_provider,
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertIsNone(self.client._get_inventory(
|
||||
self.context, uuids.cn))
|
||||
|
||||
# Create the provider
|
||||
self.client._ensure_resource_provider(self.context, uuids.cn)
|
||||
# Still no inventory, but now we don't get a 404
|
||||
self.assertEqual(
|
||||
{},
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Now set the inventory
|
||||
self.client._set_inventory_for_provider(
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Make sure we can change it
|
||||
inv = {
|
||||
fields.ResourceClass.SRIOV_NET_VF: {
|
||||
'total': 24,
|
||||
'reserved': 1,
|
||||
'min_unit': 1,
|
||||
'max_unit': 24,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
fields.ResourceClass.IPV4_ADDRESS: {
|
||||
'total': 128,
|
||||
'reserved': 0,
|
||||
'min_unit': 1,
|
||||
'max_unit': 8,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
}
|
||||
self.client._set_inventory_for_provider(
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Create custom resource classes on the fly
|
||||
self.assertFalse(
|
||||
self.client.get('/resource_classes/CUSTOM_BANDWIDTH'))
|
||||
inv = {
|
||||
fields.ResourceClass.SRIOV_NET_VF: {
|
||||
'total': 24,
|
||||
'reserved': 1,
|
||||
'min_unit': 1,
|
||||
'max_unit': 24,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
fields.ResourceClass.IPV4_ADDRESS: {
|
||||
'total': 128,
|
||||
'reserved': 0,
|
||||
'min_unit': 1,
|
||||
'max_unit': 8,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
'CUSTOM_BANDWIDTH': {
|
||||
'total': 1250000,
|
||||
'reserved': 10000,
|
||||
'min_unit': 5000,
|
||||
'max_unit': 250000,
|
||||
'step_size': 5000,
|
||||
'allocation_ratio': 8.0,
|
||||
},
|
||||
}
|
||||
self.client._set_inventory_for_provider(
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
# The custom resource class got created.
|
||||
self.assertTrue(
|
||||
self.client.get('/resource_classes/CUSTOM_BANDWIDTH'))
|
||||
|
||||
# Creating a bogus resource class raises the appropriate exception.
|
||||
bogus_inv = dict(inv)
|
||||
bogus_inv['CUSTOM_BOGU$$'] = {
|
||||
'total': 1,
|
||||
'reserved': 1,
|
||||
'min_unit': 1,
|
||||
'max_unit': 1,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
}
|
||||
self.assertRaises(
|
||||
exception.InvalidResourceClass,
|
||||
self.client._set_inventory_for_provider,
|
||||
self.context, uuids.cn, bogus_inv)
|
||||
self.assertFalse(
|
||||
self.client.get('/resource_classes/BOGUS'))
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Create a generation conflict by doing an "out of band" update
|
||||
oob_inv = {
|
||||
fields.ResourceClass.IPV4_ADDRESS: {
|
||||
'total': 128,
|
||||
'reserved': 0,
|
||||
'min_unit': 1,
|
||||
'max_unit': 8,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
}
|
||||
gen = self.client._provider_tree.data(uuids.cn).generation
|
||||
self.assertTrue(
|
||||
self.client.put(
|
||||
'/resource_providers/%s/inventories' % uuids.cn,
|
||||
{'resource_provider_generation': gen,
|
||||
'inventories': oob_inv}))
|
||||
self.assertEqual(
|
||||
oob_inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Now try to update again.
|
||||
inv = {
|
||||
fields.ResourceClass.SRIOV_NET_VF: {
|
||||
'total': 24,
|
||||
'reserved': 1,
|
||||
'min_unit': 1,
|
||||
'max_unit': 24,
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
},
|
||||
'CUSTOM_BANDWIDTH': {
|
||||
'total': 1250000,
|
||||
'reserved': 10000,
|
||||
'min_unit': 5000,
|
||||
'max_unit': 250000,
|
||||
'step_size': 5000,
|
||||
'allocation_ratio': 8.0,
|
||||
},
|
||||
}
|
||||
# Cached generation is off, so this will bounce with a conflict.
|
||||
self.assertRaises(
|
||||
exception.ResourceProviderUpdateConflict,
|
||||
self.client._set_inventory_for_provider,
|
||||
self.context, uuids.cn, inv)
|
||||
# Inventory still corresponds to the out-of-band update
|
||||
self.assertEqual(
|
||||
oob_inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
# Force refresh to get the latest generation
|
||||
self.client._refresh_and_get_inventory(self.context, uuids.cn)
|
||||
# Now the update should work
|
||||
self.client._set_inventory_for_provider(
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Now set up an InventoryInUse case by creating a VF allocation...
|
||||
self.assertTrue(
|
||||
self.client.put_allocations(
|
||||
self.context, uuids.cn, uuids.consumer,
|
||||
{fields.ResourceClass.SRIOV_NET_VF: 1},
|
||||
uuids.proj, uuids.user))
|
||||
# ...and trying to delete the provider's VF inventory
|
||||
bad_inv = {
|
||||
'CUSTOM_BANDWIDTH': {
|
||||
'total': 1250000,
|
||||
'reserved': 10000,
|
||||
'min_unit': 5000,
|
||||
'max_unit': 250000,
|
||||
'step_size': 5000,
|
||||
'allocation_ratio': 8.0,
|
||||
},
|
||||
}
|
||||
# Allocation bumped the generation, so refresh to get the latest
|
||||
self.client._refresh_and_get_inventory(self.context, uuids.cn)
|
||||
self.assertRaises(
|
||||
exception.InventoryInUse,
|
||||
self.client._set_inventory_for_provider,
|
||||
self.context, uuids.cn, bad_inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Same result if we try to clear all the inventory
|
||||
bad_inv = {}
|
||||
self.assertRaises(
|
||||
exception.InventoryInUse,
|
||||
self.client._set_inventory_for_provider,
|
||||
self.context, uuids.cn, bad_inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
||||
# Remove the allocation to make it work
|
||||
self.client.delete('/allocations/' + uuids.consumer)
|
||||
# Force refresh to get the latest generation
|
||||
self.client._refresh_and_get_inventory(self.context, uuids.cn)
|
||||
inv = {}
|
||||
self.client._set_inventory_for_provider(
|
||||
self.context, uuids.cn, inv)
|
||||
self.assertEqual(
|
||||
inv,
|
||||
self.client._get_inventory(
|
||||
self.context, uuids.cn)['inventories'])
|
||||
|
|
Loading…
Reference in New Issue