From 3ad82c696f8a6275843499f6e99ff0c117615a42 Mon Sep 17 00:00:00 2001 From: Tetsuro Nakamura Date: Sun, 7 Apr 2019 03:25:08 +0000 Subject: [PATCH] Pass target host to RequestGroup.in_tree This patch retrieves target host info from RequestSpec object, looks up the database to translate it into the compute node uuid, and pass it to the new `RequestGroup.in_tree` field. Change-Id: If7ea02df42d220c5042947efdef4777509492a0b Blueprint: use-placement-in-tree --- nova/scheduler/manager.py | 3 +- nova/scheduler/utils.py | 59 ++++++++-- nova/tests/unit/scheduler/test_utils.py | 146 ++++++++++++++++++++---- 3 files changed, 178 insertions(+), 30 deletions(-) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index f02254470bc6..dc6644f2bb24 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -133,7 +133,8 @@ class SchedulerManager(manager.Manager): except exception.RequestFilterFailed as e: raise exception.NoValidHost(reason=e.message) - resources = utils.resources_from_request_spec(spec_obj) + resources = utils.resources_from_request_spec( + ctxt, spec_obj, self.driver.host_manager) res = self.placement_client.get_allocation_candidates(ctxt, resources) if res is None: diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index ebe4bc4df077..ae49f92351bf 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -412,9 +412,16 @@ def resources_from_flavor(instance, flavor): return resources -def resources_from_request_spec(spec_obj): +def resources_from_request_spec(ctxt, spec_obj, host_manager): """Given a RequestSpec object, returns a ResourceRequest of the resources, traits, and aggregates it represents. + + :param context: The request context. + :param spec_obj: A RequestSpec object. + :param host_manager: A HostManager object. + + :return: A ResourceRequest object. + :raises NoValidHost: If the specified host/node is not found in the DB. """ spec_resources = { orc.VCPU: spec_obj.vcpus, @@ -478,18 +485,54 @@ def resources_from_request_spec(spec_obj): for group in requested_resources: res_req.add_request_group(group) + # values to get the destination target compute uuid + target_host = None + target_node = None + target_cell = None + if 'requested_destination' in spec_obj: destination = spec_obj.requested_destination - if destination and destination.aggregates: - grp = res_req.get_request_group(None) - grp.aggregates = [ored.split(',') - for ored in destination.aggregates] + if destination: + if 'host' in destination: + target_host = destination.host + if 'node' in destination: + target_node = destination.node + if 'cell' in destination: + target_cell = destination.cell + if destination.aggregates: + grp = res_req.get_request_group(None) + grp.aggregates = [ored.split(',') + for ored in destination.aggregates] - # Don't limit allocation candidates when using force_hosts or force_nodes. if 'force_hosts' in spec_obj and spec_obj.force_hosts: - res_req._limit = None + # Prioritize the value from requested_destination just in case + # so that we don't inadvertently overwrite it to the old value + # of force_hosts persisted in the DB + target_host = target_host or spec_obj.force_hosts[0] + if 'force_nodes' in spec_obj and spec_obj.force_nodes: - res_req._limit = None + # Prioritize the value from requested_destination just in case + # so that we don't inadvertently overwrite it to the old value + # of force_nodes persisted in the DB + target_node = target_node or spec_obj.force_nodes[0] + + if target_host or target_node: + nodes = host_manager.get_compute_nodes_by_host_or_node( + ctxt, target_host, target_node, cell=target_cell) + if not nodes: + reason = (_('No such host - host: %(host)s node: %(node)s ') % + {'host': target_host, 'node': target_node}) + raise exception.NoValidHost(reason=reason) + if len(nodes) == 1: + grp = res_req.get_request_group(None) + grp.in_tree = nodes[0].uuid + else: + # Multiple nodes are found when a target host is specified + # without a specific node. Since placement doesn't support + # multiple uuids in the `in_tree` queryparam, what we can do here + # is to remove the limit from the `GET /a_c` query to prevent + # the found nodes from being filtered out in placement. + res_req._limit = None return res_req diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py index dc3184c06e44..5bc3238ea191 100644 --- a/nova/tests/unit/scheduler/test_utils.py +++ b/nova/tests/unit/scheduler/test_utils.py @@ -29,6 +29,7 @@ class TestUtils(test.NoDBTestCase): def setUp(self): super(TestUtils, self).setUp() self.context = nova_context.get_admin_context() + self.mock_host_manager = mock.Mock() def assertResourceRequestsEqual(self, expected, observed): self.assertEqual(expected._limit, observed._limit) @@ -54,7 +55,8 @@ class TestUtils(test.NoDBTestCase): def _test_resources_from_request_spec(self, expected, flavor, image=objects.ImageMeta()): fake_spec = objects.RequestSpec(flavor=flavor, image=image) - resources = utils.resources_from_request_spec(fake_spec) + resources = utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) self.assertResourceRequestsEqual(expected, resources) return resources @@ -232,7 +234,8 @@ class TestUtils(test.NoDBTestCase): "resources:DOESNT_EXIST": 0}) fake_spec = objects.RequestSpec(flavor=flavor) with mock.patch("nova.scheduler.utils.LOG.warning") as mock_log: - utils.resources_from_request_spec(fake_spec) + utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) mock_log.assert_called_once() args = mock_log.call_args[0] self.assertEqual(args[0], "Received an invalid ResourceClass " @@ -321,12 +324,14 @@ class TestUtils(test.NoDBTestCase): requested_destination=destination) destination.require_aggregates(['foo', 'bar']) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([['foo', 'bar']], req.get_request_group(None).aggregates) destination.require_aggregates(['baz']) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([['foo', 'bar'], ['baz']], req.get_request_group(None).aggregates) @@ -336,19 +341,23 @@ class TestUtils(test.NoDBTestCase): swap=0) reqspec = objects.RequestSpec(flavor=flavor) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([], req.get_request_group(None).aggregates) reqspec.requested_destination = None - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([], req.get_request_group(None).aggregates) reqspec.requested_destination = objects.Destination() - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([], req.get_request_group(None).aggregates) reqspec.requested_destination.aggregates = None - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual([], req.get_request_group(None).aggregates) @mock.patch("nova.scheduler.utils.ResourceRequest.from_extra_specs") @@ -360,7 +369,8 @@ class TestUtils(test.NoDBTestCase): swap=0, extra_specs={"resources:CUSTOM_TEST_CLASS": 1}) fake_spec = objects.RequestSpec(flavor=flavor) - utils.resources_from_request_spec(fake_spec) + utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) mock_proc.assert_called_once() @mock.patch("nova.scheduler.utils.ResourceRequest.from_extra_specs") @@ -371,7 +381,8 @@ class TestUtils(test.NoDBTestCase): ephemeral_gb=5, swap=0) fake_spec = objects.RequestSpec(flavor=flavor) - utils.resources_from_request_spec(fake_spec) + utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) mock_proc.assert_not_called() def test_process_missing_extra_specs_value(self): @@ -383,7 +394,8 @@ class TestUtils(test.NoDBTestCase): swap=0, extra_specs={"resources:CUSTOM_TEST_CLASS": ""}) fake_spec = objects.RequestSpec(flavor=flavor) - utils.resources_from_request_spec(fake_spec) + utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) def test_process_no_force_hosts_or_force_nodes(self): flavor = objects.Flavor(vcpus=1, @@ -408,6 +420,11 @@ class TestUtils(test.NoDBTestCase): self.assertEqual(expected_querystring, rr.to_querystring()) def test_process_use_force_nodes(self): + fake_nodes = objects.ComputeNodeList(objects=[ + objects.ComputeNode(host='fake-host', + uuid='12345678-1234-1234-1234-123456789012')]) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + return_value = fake_nodes flavor = objects.Flavor(vcpus=1, memory_mb=1024, root_gb=15, @@ -422,16 +439,58 @@ class TestUtils(test.NoDBTestCase): 'MEMORY_MB': 1024, 'DISK_GB': 15, }, + in_tree='12345678-1234-1234-1234-123456789012', ) - expected._limit = None - resources = utils.resources_from_request_spec(fake_spec) + resources = utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) self.assertResourceRequestsEqual(expected, resources) expected_querystring = ( - 'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1' - ) + 'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1') self.assertEqual(expected_querystring, resources.to_querystring()) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + assert_called_once_with(self.context, None, 'test', cell=None) def test_process_use_force_hosts(self): + fake_nodes = objects.ComputeNodeList(objects=[ + objects.ComputeNode(host='fake-host', + uuid='12345678-1234-1234-1234-123456789012') + ]) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + return_value = fake_nodes + flavor = objects.Flavor(vcpus=1, + memory_mb=1024, + root_gb=15, + ephemeral_gb=0, + swap=0) + fake_spec = objects.RequestSpec(flavor=flavor, force_hosts=['test']) + expected = utils.ResourceRequest() + expected._rg_by_id[None] = objects.RequestGroup( + use_same_provider=False, + resources={ + 'VCPU': 1, + 'MEMORY_MB': 1024, + 'DISK_GB': 15, + }, + in_tree='12345678-1234-1234-1234-123456789012', + ) + resources = utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) + self.assertResourceRequestsEqual(expected, resources) + expected_querystring = ( + 'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1') + self.assertEqual(expected_querystring, resources.to_querystring()) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + assert_called_once_with(self.context, 'test', None, cell=None) + + def test_process_use_force_hosts_multinodes_found(self): + fake_nodes = objects.ComputeNodeList(objects=[ + objects.ComputeNode(host='fake-host', + uuid='12345678-1234-1234-1234-123456789012'), + objects.ComputeNode(host='fake-host', + uuid='87654321-4321-4321-4321-210987654321'), + ]) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + return_value = fake_nodes flavor = objects.Flavor(vcpus=1, memory_mb=1024, root_gb=15, @@ -447,13 +506,55 @@ class TestUtils(test.NoDBTestCase): 'DISK_GB': 15, }, ) + # Validate that the limit is unset expected._limit = None - resources = utils.resources_from_request_spec(fake_spec) + + resources = utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) + self.assertResourceRequestsEqual(expected, resources) + # Validate that the limit is unset + expected_querystring = ( + 'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1') + self.assertEqual(expected_querystring, resources.to_querystring()) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + assert_called_once_with(self.context, 'test', None, cell=None) + + def test_process_use_requested_destination(self): + fake_cell = objects.CellMapping(uuid=uuids.cell1, name='foo') + destination = objects.Destination( + host='fake-host', node='fake-node', cell=fake_cell) + fake_nodes = objects.ComputeNodeList(objects=[ + objects.ComputeNode(host='fake-host', + uuid='12345678-1234-1234-1234-123456789012') + ]) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + return_value = fake_nodes + flavor = objects.Flavor(vcpus=1, + memory_mb=1024, + root_gb=15, + ephemeral_gb=0, + swap=0) + fake_spec = objects.RequestSpec( + flavor=flavor, requested_destination=destination) + expected = utils.ResourceRequest() + expected._rg_by_id[None] = objects.RequestGroup( + use_same_provider=False, + resources={ + 'VCPU': 1, + 'MEMORY_MB': 1024, + 'DISK_GB': 15, + }, + in_tree='12345678-1234-1234-1234-123456789012', + ) + resources = utils.resources_from_request_spec( + self.context, fake_spec, self.mock_host_manager) self.assertResourceRequestsEqual(expected, resources) expected_querystring = ( - 'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1' - ) + 'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1') self.assertEqual(expected_querystring, resources.to_querystring()) + self.mock_host_manager.get_compute_nodes_by_host_or_node.\ + assert_called_once_with( + self.context, 'fake-host', 'fake-node', cell=fake_cell) def test_resources_from_request_spec_having_requested_resources(self): flavor = objects.Flavor( @@ -466,7 +567,8 @@ class TestUtils(test.NoDBTestCase): rg2 = objects.RequestGroup() reqspec = objects.RequestSpec(flavor=flavor, requested_resources=[rg1, rg2]) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1}, req.get_request_group(None).resources) self.assertIs(rg1, req.get_request_group(1)) @@ -480,13 +582,15 @@ class TestUtils(test.NoDBTestCase): ephemeral_gb=5, swap=0) reqspec = objects.RequestSpec(flavor=flavor) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1}, req.get_request_group(None).resources) self.assertEqual(1, len(list(req.resource_groups()))) reqspec = objects.RequestSpec(flavor=flavor, requested_resources=[]) - req = utils.resources_from_request_spec(reqspec) + req = utils.resources_from_request_spec( + self.context, reqspec, self.mock_host_manager) self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1}, req.get_request_group(None).resources) self.assertEqual(1, len(list(req.resource_groups())))