Scheduler set_inventory_for_provider does nested

SchedulerReportClient.set_inventory_for_provider and its SchedulerClient
wrapper now accept a parent_provider_uuid kwarg, which must be specified
for any provider that isn't a root.  If the method winds up creating the
provider, and parent_provider_uuid is None (the default), the provider
is created as a root - this is the previous behavior.  If
parent_provider_uuid is specified, and the method winds up creating the
provider, it is created as a child of the provider indicated.

Change-Id: I7cfbc80a9a41e97623950deaab9a7b0604fa487d
blueprint: nested-resource-providers
This commit is contained in:
Eric Fried 2017-11-16 09:47:54 -06:00
parent a0ff3e2936
commit b75b35f482
4 changed files with 132 additions and 11 deletions

View File

@ -57,11 +57,13 @@ class SchedulerClient(object):
def delete_aggregate(self, context, aggregate):
self.queryclient.delete_aggregate(context, aggregate)
def set_inventory_for_provider(self, rp_uuid, rp_name, inv_data):
def set_inventory_for_provider(self, rp_uuid, rp_name, inv_data,
parent_provider_uuid=None):
self.reportclient.set_inventory_for_provider(
rp_uuid,
rp_name,
inv_data,
parent_provider_uuid=parent_provider_uuid,
)
def update_compute_node(self, compute_node):

View File

@ -491,7 +491,8 @@ class SchedulerReportClient(object):
LOG.error(msg, args)
raise exception.ResourceProviderCreationFailed(name=name)
def _ensure_resource_provider(self, uuid, name=None):
def _ensure_resource_provider(self, uuid, name=None,
parent_provider_uuid=None):
"""Ensures that the placement API has a record of a resource provider
with the supplied UUID. If not, creates the resource provider record in
the placement API for the supplied UUID, passing in a name for the
@ -510,7 +511,11 @@ class SchedulerReportClient(object):
:param name: Optional name for the resource provider if the record
does not exist. If empty, the name is set to the UUID
value
:param parent_provider_uuid: Optional UUID of the immediate parent
"""
# NOTE(efried): We currently have no code path where we need to set the
# parent_provider_uuid on a previously-parent-less provider - so we do
# NOT handle that scenario here.
if self._provider_tree.exists(uuid):
self._refresh_aggregate_map(uuid)
return uuid
@ -519,13 +524,25 @@ class SchedulerReportClient(object):
# the placement API.
rp = self._get_resource_provider(uuid)
if rp is None:
rp = self._create_resource_provider(uuid, name or uuid)
rp = self._create_resource_provider(
uuid, name or uuid, parent_provider_uuid=parent_provider_uuid)
# If there had been no resource provider record, force refreshing
# the aggregate map.
self._refresh_aggregate_map(uuid, force=True)
return self._provider_tree.new_root(rp['name'], uuid, rp['generation'])
# If this is a root node (no parent), create it as such
if parent_provider_uuid is None:
return self._provider_tree.new_root(
rp['name'], uuid, rp['generation'])
# Not a root - we have to insert it into the proper place in the tree.
# NOTE(efried): We populate self._provider_tree from the top down, so
# we can count on the parent being in the tree - we don't have to
# retrieve it from placement.
return self._provider_tree.new_child(rp['name'], parent_provider_uuid,
uuid=uuid,
generation=rp['generation'])
def _get_inventory(self, rp_uuid):
url = '/resource_providers/%s/inventories' % rp_uuid
@ -799,7 +816,8 @@ class SchedulerReportClient(object):
msg_args['err'] = r.text
LOG.error(msg, msg_args)
def set_inventory_for_provider(self, rp_uuid, rp_name, inv_data):
def set_inventory_for_provider(self, rp_uuid, rp_name, inv_data,
parent_provider_uuid=None):
"""Given the UUID of a provider, set the inventory records for the
provider to the supplied dict of resources.
@ -808,11 +826,16 @@ class SchedulerReportClient(object):
a record for it in the placement API
:param inv_data: Dict, keyed by resource class name, of inventory data
to set against the provider
:param parent_provider_uuid:
If the provider is not a root, this is required, and represents
the UUID of the immediate parent, which is a provider for which
this method has already been invoked.
:raises: exc.InvalidResourceClass if a supplied custom resource class
name does not meet the placement API's format requirements.
"""
self._ensure_resource_provider(rp_uuid, rp_name)
self._ensure_resource_provider(
rp_uuid, rp_name, parent_provider_uuid=parent_provider_uuid)
# Auto-create custom resource classes coming from a virt driver
list(map(self._ensure_resource_class,

View File

@ -1155,8 +1155,8 @@ class TestProviderOperations(SchedulerReportClientTestCase):
self.client._ensure_resource_provider, uuids.compute_node)
get_rp_mock.assert_called_once_with(uuids.compute_node)
create_rp_mock.assert_called_once_with(uuids.compute_node,
uuids.compute_node)
create_rp_mock.assert_called_once_with(
uuids.compute_node, uuids.compute_node, parent_provider_uuid=None)
self.assertFalse(self.client._provider_tree.exists(uuids.compute_node))
self.assertFalse(get_agg_mock.called)
self.assertEqual({}, self.client._provider_aggregate_map)
@ -1178,7 +1178,11 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'name': 'compute-name',
'generation': 1,
}
self.client._ensure_resource_provider(uuids.compute_node)
self.assertEqual(
uuids.compute_node,
self.client._ensure_resource_provider(uuids.compute_node))
self._validate_provider(uuids.compute_node, name='compute-name',
generation=1, parent_uuid=None)
get_agg_mock.assert_called_once_with(uuids.compute_node)
self.assertIn(uuids.compute_node, self.client._provider_aggregate_map)
@ -1190,16 +1194,77 @@ class TestProviderOperations(SchedulerReportClientTestCase):
create_rp_mock.assert_called_once_with(
uuids.compute_node,
uuids.compute_node, # name param defaults to UUID if None
parent_provider_uuid=None,
)
self.assertTrue(self.client._provider_tree.exists(uuids.compute_node))
create_rp_mock.reset_mock()
self.client._ensure_resource_provider(uuids.compute_node,
'compute-name')
self.assertEqual(
uuids.compute_node,
self.client._ensure_resource_provider(uuids.compute_node))
self._validate_provider(uuids.compute_node, name='compute-name',
generation=1, parent_uuid=None)
# Shouldn't be called now that provider is in cache...
self.assertFalse(create_rp_mock.called)
# Validate the path where we specify a name (don't default to the UUID)
self.client._ensure_resource_provider(uuids.cn2, 'a-name')
create_rp_mock.assert_called_once_with(
uuids.cn2, 'a-name', parent_provider_uuid=None)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_refresh_aggregate_map', new=mock.Mock())
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_create_resource_provider')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_get_resource_provider')
def test_ensure_resource_provider_tree(self, get_rp_mock, create_rp_mock):
"""Test _ensure_resource_provider with a tree of providers."""
def _create_resource_provider(uuid, name, parent_provider_uuid=None):
"""Mock side effect for creating the RP with the specified args."""
return {
'uuid': uuid,
'name': name,
'generation': 0,
'parent_provider_uuid': parent_provider_uuid
}
create_rp_mock.side_effect = _create_resource_provider
# Not initially in the placement database, so we have to create it.
get_rp_mock.return_value = None
# Create the root
root = self.client._ensure_resource_provider(uuids.root)
self.assertEqual(uuids.root, root)
# Now create a child
child1 = self.client._ensure_resource_provider(
uuids.child1, name='junior', parent_provider_uuid=uuids.root)
self.assertEqual(uuids.child1, child1)
# If we re-ensure the child, we get the object from the tree, not a
# newly-created one - i.e. the early .find() works like it should.
self.assertIs(child1,
self.client._ensure_resource_provider(uuids.child1))
# Make sure we can create a grandchild
grandchild = self.client._ensure_resource_provider(
uuids.grandchild, parent_provider_uuid=uuids.child1)
self.assertEqual(uuids.grandchild, grandchild)
# Now create a second child of the root and make sure it doesn't wind
# up in some crazy wrong place like under child1 or grandchild
child2 = self.client._ensure_resource_provider(
uuids.child2, parent_provider_uuid=uuids.root)
self.assertEqual(uuids.child2, child2)
# At this point we should get all the providers.
self.assertEqual(
set([uuids.root, uuids.child1, uuids.child2, uuids.grandchild]),
self.client._provider_tree.get_provider_uuids())
def test_get_allocation_candidates(self):
resp_mock = mock.Mock(status_code=200)
json_data = {
@ -2455,6 +2520,7 @@ There was a conflict when trying to complete your request.
mock_erp.assert_called_once_with(
mock.sentinel.rp_uuid,
mock.sentinel.rp_name,
parent_provider_uuid=None,
)
# No custom resource classes to ensure...
self.assertFalse(mock_erc.called)
@ -2489,6 +2555,7 @@ There was a conflict when trying to complete your request.
mock_erp.assert_called_once_with(
mock.sentinel.rp_uuid,
mock.sentinel.rp_name,
parent_provider_uuid=None,
)
self.assertFalse(mock_gocr.called)
self.assertFalse(mock_erc.called)
@ -2554,6 +2621,7 @@ There was a conflict when trying to complete your request.
mock_erp.assert_called_once_with(
mock.sentinel.rp_uuid,
mock.sentinel.rp_name,
parent_provider_uuid=None,
)
mock_erc.assert_called_once_with('CUSTOM_IRON_SILVER')
mock_upd.assert_called_once_with(
@ -2563,6 +2631,19 @@ There was a conflict when trying to complete your request.
self.assertFalse(mock_gocr.called)
self.assertFalse(mock_del.called)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_delete_inventory', new=mock.Mock())
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_class', new=mock.Mock())
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
def test_set_inventory_for_provider_with_parent(self, mock_erp):
"""Ensure parent UUID is sent through."""
self.client.set_inventory_for_provider(
uuids.child, 'junior', {}, parent_provider_uuid=uuids.parent)
mock_erp.assert_called_once_with(
uuids.child, 'junior', parent_provider_uuid=uuids.parent)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'

View File

@ -114,4 +114,19 @@ class SchedulerClientTestCase(test.NoDBTestCase):
mock.sentinel.rp_uuid,
mock.sentinel.rp_name,
mock.sentinel.inv_data,
parent_provider_uuid=None,
)
# Pass the optional parent_provider_uuid
mock_set.reset_mock()
self.client.set_inventory_for_provider(
mock.sentinel.child_uuid,
mock.sentinel.child_name,
mock.sentinel.inv_data2,
parent_provider_uuid=mock.sentinel.rp_uuid,
)
mock_set.assert_called_once_with(
mock.sentinel.child_uuid,
mock.sentinel.child_name,
mock.sentinel.inv_data2,
parent_provider_uuid=mock.sentinel.rp_uuid,
)