diff options
Diffstat (limited to 'nova/tests/unit/conductor/test_conductor.py')
-rw-r--r-- | nova/tests/unit/conductor/test_conductor.py | 176 |
1 files changed, 173 insertions, 3 deletions
diff --git a/nova/tests/unit/conductor/test_conductor.py b/nova/tests/unit/conductor/test_conductor.py index 9445db1b62..971570dfb5 100644 --- a/nova/tests/unit/conductor/test_conductor.py +++ b/nova/tests/unit/conductor/test_conductor.py @@ -16,9 +16,12 @@ """Tests for the conductor service.""" import copy +import ddt +from unittest import mock -import mock +from keystoneauth1 import exceptions as ks_exc from oslo_db import exception as db_exc +from oslo_limit import exception as limit_exceptions import oslo_messaging as messaging from oslo_serialization import jsonutils from oslo_utils.fixture import uuidsentinel as uuids @@ -44,12 +47,14 @@ from nova.db.api import models as api_models from nova.db.main import api as main_db_api from nova import exception as exc from nova.image import glance as image_api +from nova.limit import placement as placement_limit from nova import objects from nova.objects import base as obj_base from nova.objects import block_device as block_device_obj from nova.objects import fields from nova.objects import request_spec from nova.scheduler.client import query +from nova.scheduler.client import report from nova.scheduler import utils as scheduler_utils from nova import test from nova.tests import fixtures @@ -383,7 +388,9 @@ class _BaseTaskTestCase(object): 'on_shared_storage': False, 'preserve_ephemeral': False, 'host': 'compute-host', - 'request_spec': None} + 'request_spec': None, + 'reimage_boot_volume': False, + 'target_state': None} if update_args: rebuild_args.update(update_args) compute_rebuild_args = copy.deepcopy(rebuild_args) @@ -2261,6 +2268,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase): rs.instance_group = None rs.retry = None rs.limits = None + rs.is_bfv = False rs.create() params['request_specs'] = [rs] params['image'] = {'fake_data': 'should_pass_silently'} @@ -2399,7 +2407,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase): '1', None, None, dp_name) arq_uuid = arq_in_list[0]['uuid'] - # muliti device request + # multi device request mock_create.return_value = [arq_in_list[0], arq_in_list[0]] rp_map = {"request_group_0" + str(port_id): rp_uuid} request_tuples = [('123', '1.2.3.4', port_id, @@ -2871,6 +2879,74 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase): 'image'): self.assertIn(key, request_spec_dict) + @mock.patch.object(placement_limit, 'enforce_num_instances_and_flavor') + @mock.patch('nova.compute.utils.notify_about_compute_task_error') + @mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations') + def test_schedule_and_build_over_quota_during_recheck_ul(self, mock_select, + mock_notify, + mock_enforce): + self.flags(driver="nova.quota.UnifiedLimitsDriver", + cores=1, + group="quota") + mock_select.return_value = [[fake_selection1]] + # Simulate a race where the first check passes and the recheck fails. + # First check occurs in compute/api. + project_id = self.params['context'].project_id + mock_enforce.side_effect = limit_exceptions.ProjectOverLimit( + project_id, [limit_exceptions.OverLimitInfo('cores', 2, 3, 0)]) + + original_save = objects.Instance.save + + def fake_save(inst, *args, **kwargs): + # Make sure the context is targeted to the cell that the instance + # was created in. + self.assertIsNotNone( + inst._context.db_connection, 'Context is not targeted') + original_save(inst, *args, **kwargs) + + self.stub_out('nova.objects.Instance.save', fake_save) + + # This is needed to register the compute node in a cell. + self.start_service('compute', host='host1') + self.assertRaises( + limit_exceptions.ProjectOverLimit, + self.conductor.schedule_and_build_instances, **self.params) + + mock_enforce.assert_called_once_with( + self.params['context'], project_id, mock.ANY, False, 0, 0) + + # Verify we set the instance to ERROR state and set the fault message. + instances = objects.InstanceList.get_all(self.ctxt) + self.assertEqual(1, len(instances)) + instance = instances[0] + self.assertEqual(vm_states.ERROR, instance.vm_state) + self.assertIsNone(instance.task_state) + self.assertIn('ProjectOverLimit', instance.fault.message) + # Verify we removed the build objects. + build_requests = objects.BuildRequestList.get_all(self.ctxt) + # Verify that the instance is mapped to a cell + inst_mapping = objects.InstanceMapping.get_by_instance_uuid( + self.ctxt, instance.uuid) + self.assertIsNotNone(inst_mapping.cell_mapping) + + self.assertEqual(0, len(build_requests)) + + @api_db_api.context_manager.reader + def request_spec_get_all(context): + return context.session.query(api_models.RequestSpec).all() + + request_specs = request_spec_get_all(self.ctxt) + self.assertEqual(0, len(request_specs)) + + mock_notify.assert_called_once_with( + test.MatchType(context.RequestContext), 'build_instances', + instance.uuid, test.MatchType(dict), 'error', + test.MatchType(limit_exceptions.ProjectOverLimit)) + request_spec_dict = mock_notify.call_args_list[0][0][3] + for key in ('instance_type', 'num_instances', 'instance_properties', + 'image'): + self.assertIn(key, request_spec_dict) + @mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance') @mock.patch('nova.objects.quotas.Quotas.check_deltas') @mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations') @@ -4676,6 +4752,68 @@ class ConductorTaskRPCAPITestCase(_BaseTaskTestCase, mock.sentinel.migration) can_send_version.assert_called_once_with('1.23') + def test_evacuate_old_rpc_with_target_state(self): + inst_obj = self._create_fake_instance_obj() + rebuild_args, compute_args = self._prepare_rebuild_args( + {'host': inst_obj.host, + 'target_state': 'stopped'}) + with mock.patch.object( + self.conductor.client, 'can_send_version', return_value=False): + self.assertRaises(exc.UnsupportedRPCVersion, + self.conductor.rebuild_instance, + self.context, inst_obj, **rebuild_args) + + def test_evacuate_old_rpc_without_target_state(self): + inst_obj = self._create_fake_instance_obj() + rebuild_args, compute_args = self._prepare_rebuild_args( + {'host': inst_obj.host, + 'target_state': None}) + with mock.patch.object( + self.conductor.client, 'can_send_version', + return_value=False) as can_send_version: + self.conductor.rebuild_instance( + self.context, inst_obj, **rebuild_args) + can_send_version.assert_has_calls([ + mock.call('1.25'), mock.call('1.24'), + mock.call('1.12')]) + + def test_rebuild_instance_volume_backed(self): + inst_obj = self._create_fake_instance_obj() + version = '1.25' + cctxt_mock = mock.MagicMock() + rebuild_args, compute_args = self._prepare_rebuild_args( + {'host': inst_obj.host}) + rebuild_args['reimage_boot_volume'] = True + + @mock.patch.object(self.conductor.client, 'prepare', + return_value=cctxt_mock) + @mock.patch.object(self.conductor.client, 'can_send_version', + return_value=True) + def _test(mock_can_send_ver, prepare_mock): + self.conductor.rebuild_instance( + self.context, inst_obj, **rebuild_args) + prepare_mock.assert_called_once_with(version=version) + kw = {'instance': inst_obj, **rebuild_args} + cctxt_mock.cast.assert_called_once_with( + self.context, 'rebuild_instance', **kw) + _test() + + def test_rebuild_instance_volume_backed_old_service(self): + """Tests rebuild_instance_volume_backed when the service is too old""" + inst_obj = mock.MagicMock() + rebuild_args, compute_args = self._prepare_rebuild_args( + {'host': inst_obj.host}) + rebuild_args['reimage_boot_volume'] = True + with mock.patch.object( + self.conductor.client, 'can_send_version', + return_value=False) as can_send_version: + self.assertRaises(exc.NovaException, + self.conductor.rebuild_instance, + self.context, inst_obj, + **rebuild_args) + can_send_version.assert_has_calls([mock.call('1.25'), + mock.call('1.24')]) + class ConductorTaskAPITestCase(_BaseTaskTestCase, test_compute.BaseTestCase): """Compute task API Tests.""" @@ -4798,3 +4936,35 @@ class ConductorTaskAPITestCase(_BaseTaskTestCase, test_compute.BaseTestCase): logtext) self.assertIn('host3\' because it is not up', logtext) self.assertIn('image1 failed 1 times', logtext) + + +@ddt.ddt +class TestConductorTaskManager(test.NoDBTestCase): + def test_placement_client_startup(self): + self.assertIsNone(report.PLACEMENTCLIENT) + conductor_manager.ComputeTaskManager() + self.assertIsNotNone(report.PLACEMENTCLIENT) + + @ddt.data(ks_exc.MissingAuthPlugin, + ks_exc.Unauthorized, + test.TestingException) + def test_placement_client_startup_fatals(self, exc): + self.assertRaises(exc, + self._test_placement_client_startup_exception, exc) + + @ddt.data(ks_exc.EndpointNotFound, + ks_exc.DiscoveryFailure, + ks_exc.RequestTimeout, + ks_exc.GatewayTimeout, + ks_exc.ConnectFailure) + def test_placement_client_startup_non_fatal(self, exc): + self._test_placement_client_startup_exception(exc) + + @mock.patch.object(report, 'LOG') + def _test_placement_client_startup_exception(self, exc, mock_log): + with mock.patch.object(report.SchedulerReportClient, '_create_client', + side_effect=exc): + try: + conductor_manager.ComputeTaskManager() + finally: + mock_log.error.assert_called_once() |