diff --git a/nova/compute/api.py b/nova/compute/api.py index c8b7a13dfe7f..dd32e589ed34 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -952,6 +952,48 @@ class API(base.Base): br.create() im.create() + def _validate_host_or_node(self, context, host, hypervisor_hostname): + """Check whether compute nodes exist by validating the host + and/or the hypervisor_hostname. There are three cases: + 1. If only host is supplied, we can lookup the HostMapping in + the API DB. + 2. If only node is supplied, we can query a resource provider + with that name in placement. + 3. If both host and node are supplied, we can get the cell from + HostMapping and from that lookup the ComputeNode with the + given cell. + + :param context: The API request context. + :param host: Target host. + :param hypervisor_hostname: Target node. + :raises: ComputeHostNotFound if we find no compute nodes with host + and/or hypervisor_hostname. + """ + + if host: + # When host is specified. + try: + host_mapping = objects.HostMapping.get_by_host(context, host) + except exception.HostMappingNotFound: + LOG.warning('No host-to-cell mapping found for host ' + '%(host)s.', {'host': host}) + raise exception.ComputeHostNotFound(host=host) + # When both host and node are specified. + if hypervisor_hostname: + cell = host_mapping.cell_mapping + with nova_context.target_cell(context, cell) as cctxt: + # Here we only do an existence check, so we don't + # need to store the return value into a variable. + objects.ComputeNode.get_by_host_and_nodename( + cctxt, host, hypervisor_hostname) + elif hypervisor_hostname: + # When only node is specified. + try: + self.placementclient.get_provider_by_name( + context, hypervisor_hostname) + except exception.ResourceProviderNotFound: + raise exception.ComputeHostNotFound(host=hypervisor_hostname) + def _provision_instances(self, context, instance_type, min_count, max_count, base_options, boot_meta, security_groups, block_device_mapping, shutdown_terminate, diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 38e938f83b06..cae95a07e582 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -6308,6 +6308,130 @@ class _ComputeAPIUnitTestMixIn(object): self.context, ids, rebuild=True) get_min_version.assert_called_once_with(self.context, 'nova-compute') + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_host( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm): + host = 'fake-host' + node = None + + self.compute_api._validate_host_or_node(self.context, host, node) + mock_get_hm.assert_called_once_with(self.context, 'fake-host') + mock_get_host_node.assert_not_called() + mock_get_provider_by_name.assert_not_called() + + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_invalid_host( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm): + host = 'fake-host' + node = None + + mock_get_hm.side_effect = exception.HostMappingNotFound(name=host) + self.assertRaises(exception.ComputeHostNotFound, + self.compute_api._validate_host_or_node, + self.context, host, node) + mock_get_hm.assert_called_once_with(self.context, 'fake-host') + mock_get_host_node.assert_not_called() + mock_get_provider_by_name.assert_not_called() + + @mock.patch('nova.context.target_cell') + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_host_and_node( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm, + mock_target_cell): + host = 'fake-host' + node = 'fake-host' + + self.compute_api._validate_host_or_node(self.context, host, node) + mock_get_host_node.assert_called_once_with( + mock_target_cell.return_value.__enter__.return_value, + 'fake-host', 'fake-host') + mock_get_hm.assert_called_once_with(self.context, 'fake-host') + mock_get_provider_by_name.assert_not_called() + + @mock.patch('nova.context.target_cell') + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_invalid_host_and_node( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm, + mock_target_cell): + host = 'fake-host' + node = 'fake-host' + + mock_get_host_node.side_effect = ( + exception.ComputeHostNotFound(host=host)) + self.assertRaises(exception.ComputeHostNotFound, + self.compute_api._validate_host_or_node, + self.context, host, node) + mock_get_host_node.assert_called_once_with( + mock_target_cell.return_value.__enter__.return_value, + 'fake-host', 'fake-host') + mock_get_hm.assert_called_once_with(self.context, 'fake-host') + mock_get_provider_by_name.assert_not_called() + + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_node( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm): + host = None + node = 'fake-host' + + self.compute_api._validate_host_or_node(self.context, host, node) + mock_get_provider_by_name.assert_called_once_with( + self.context, 'fake-host') + mock_get_host_node.assert_not_called() + mock_get_hm.assert_not_called() + + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_invalid_node( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm): + host = None + node = 'fake-host' + + mock_get_provider_by_name.side_effect = ( + exception.ResourceProviderNotFound(name_or_uuid=node)) + self.assertRaises(exception.ComputeHostNotFound, + self.compute_api._validate_host_or_node, + self.context, host, node) + mock_get_provider_by_name.assert_called_once_with( + self.context, 'fake-host') + mock_get_host_node.assert_not_called() + mock_get_hm.assert_not_called() + + @mock.patch('nova.objects.HostMapping.get_by_host') + @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename') + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'get_provider_by_name') + def test__validate_host_or_node_with_rp_500_exception( + self, mock_get_provider_by_name, mock_get_host_node, mock_get_hm): + host = None + node = 'fake-host' + + mock_get_provider_by_name.side_effect = ( + exception.PlacementAPIConnectFailure()) + self.assertRaises(exception.PlacementAPIConnectFailure, + self.compute_api._validate_host_or_node, + self.context, host, node) + mock_get_provider_by_name.assert_called_once_with( + self.context, 'fake-host') + mock_get_host_node.assert_not_called() + mock_get_hm.assert_not_called() + class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): def setUp(self):