diff options
author | John Garbutt <john@johngarbutt.com> | 2013-05-14 10:44:13 +0100 |
---|---|---|
committer | John Garbutt <john.garbutt@rackspace.com> | 2013-06-24 10:49:07 +0100 |
commit | 2d7beddc0bb7830c2a8bf893b9221c0de568c55d (patch) | |
tree | c1d5c31d289d13eb7d0aa625ecb2699ecd874a65 /nova/tests/conductor | |
parent | facf42d0bab7a4f97c654a5724189609ad185559 (diff) | |
download | nova-2d7beddc0bb7830c2a8bf893b9221c0de568c55d.tar.gz nova-2d7beddc0bb7830c2a8bf893b9221c0de568c55d.tar.xz nova-2d7beddc0bb7830c2a8bf893b9221c0de568c55d.zip |
Extract live-migration scheduler logic from the scheduler driver
Before moving the control of live-migration into the conductor,
extract the live-migration control logic into a separate class.
The callback to select_hosts will be replaced by a new
scheduler rpc method in a later changeset.
Part of blueprint live-migration-to-conductor
Change-Id: I6de33ada6dc377e20f8df07da92244f2c150b9fe
Diffstat (limited to 'nova/tests/conductor')
-rw-r--r-- | nova/tests/conductor/tasks/__init__.py | 11 | ||||
-rw-r--r-- | nova/tests/conductor/tasks/test_live_migrate.py | 311 |
2 files changed, 322 insertions, 0 deletions
diff --git a/nova/tests/conductor/tasks/__init__.py b/nova/tests/conductor/tasks/__init__.py new file mode 100644 index 000000000..94e731d20 --- /dev/null +++ b/nova/tests/conductor/tasks/__init__.py @@ -0,0 +1,11 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/nova/tests/conductor/tasks/test_live_migrate.py b/nova/tests/conductor/tasks/test_live_migrate.py new file mode 100644 index 000000000..c54e53b1a --- /dev/null +++ b/nova/tests/conductor/tasks/test_live_migrate.py @@ -0,0 +1,311 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.compute import flavors +from nova.compute import power_state +from nova.conductor.tasks import live_migrate +from nova import db +from nova import exception +from nova import test + + +class LiveMigrationTaskTestCase(test.TestCase): + def setUp(self): + super(LiveMigrationTaskTestCase, self).setUp() + self.context = "context" + self.instance_host = "host" + self.instance_uuid = "uuid" + self.instance_image = "image_ref" + self.instance = { + "host": self.instance_host, + "uuid": self.instance_uuid, + "power_state": power_state.RUNNING, + "memory_mb": 512, + "image_ref": self.instance_image} + self.destination = "destination" + self.block_migration = "bm" + self.disk_over_commit = "doc" + self.select_hosts_callback = self._select_hosts_callback + self._generate_task() + + def _generate_task(self): + self.task = live_migrate.LiveMigrationTask(self.context, + self.instance, self.destination, self.block_migration, + self.disk_over_commit, self.select_hosts_callback) + + def _select_hosts_callback(self, *args): + return ["host1"] + + def test_execute_with_destination(self): + self.mox.StubOutWithMock(self.task, '_check_host_is_up') + self.mox.StubOutWithMock(self.task, '_check_requested_destination') + self.mox.StubOutWithMock(self.task.compute_rpcapi, 'live_migration') + + self.task._check_host_is_up(self.instance_host) + self.task._check_requested_destination() + self.task.compute_rpcapi.live_migration(self.context, + host=self.instance_host, + instance=self.instance, + dest=self.destination, + block_migration=self.block_migration, + migrate_data=None).AndReturn("bob") + + self.mox.ReplayAll() + self.assertEqual("bob", self.task.execute()) + + def test_execute_without_destination(self): + self.destination = None + self._generate_task() + self.assertEqual(None, self.task.destination) + + self.mox.StubOutWithMock(self.task, '_check_host_is_up') + self.mox.StubOutWithMock(self.task, '_find_destination') + self.mox.StubOutWithMock(self.task.compute_rpcapi, 'live_migration') + + self.task._check_host_is_up(self.instance_host) + self.task._find_destination().AndReturn("found_host") + self.task.compute_rpcapi.live_migration(self.context, + host=self.instance_host, + instance=self.instance, + dest="found_host", + block_migration=self.block_migration, + migrate_data=None).AndReturn("bob") + + self.mox.ReplayAll() + self.assertEqual("bob", self.task.execute()) + + def test_check_instance_is_running_passes(self): + self.task._check_instance_is_running() + + def test_check_instance_is_running_fails_when_shutdown(self): + self.task.instance['power_state'] = power_state.SHUTDOWN + self.assertRaises(exception.InstanceNotRunning, + self.task._check_instance_is_running) + + def test_check_instance_host_is_up(self): + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + self.mox.StubOutWithMock(self.task.servicegroup_api, 'service_is_up') + + db.service_get_by_compute_host(self.context, + "host").AndReturn("service") + self.task.servicegroup_api.service_is_up("service").AndReturn(True) + + self.mox.ReplayAll() + self.task._check_host_is_up("host") + + def test_check_instance_host_is_up_fails_if_not_up(self): + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + self.mox.StubOutWithMock(self.task.servicegroup_api, 'service_is_up') + + db.service_get_by_compute_host(self.context, + "host").AndReturn("service") + self.task.servicegroup_api.service_is_up("service").AndReturn(False) + + self.mox.ReplayAll() + self.assertRaises(exception.ComputeServiceUnavailable, + self.task._check_host_is_up, "host") + + def test_check_instance_host_is_up_fails_if_not_found(self): + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + + db.service_get_by_compute_host(self.context, + "host").AndRaise(exception.NotFound) + + self.mox.ReplayAll() + self.assertRaises(exception.ComputeServiceUnavailable, + self.task._check_host_is_up, "host") + + def test_check_requested_destination(self): + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + self.mox.StubOutWithMock(self.task, '_get_compute_info') + self.mox.StubOutWithMock(self.task.servicegroup_api, 'service_is_up') + self.mox.StubOutWithMock(self.task.compute_rpcapi, + 'check_can_live_migrate_destination') + + db.service_get_by_compute_host(self.context, + self.destination).AndReturn("service") + self.task.servicegroup_api.service_is_up("service").AndReturn(True) + hypervisor_details = { + "hypervisor_type": "a", + "hypervisor_version": 6.1, + "free_ram_mb": 513 + } + self.task._get_compute_info(self.destination)\ + .AndReturn(hypervisor_details) + self.task._get_compute_info(self.instance_host)\ + .AndReturn(hypervisor_details) + self.task._get_compute_info(self.destination)\ + .AndReturn(hypervisor_details) + + self.task.compute_rpcapi.check_can_live_migrate_destination( + self.context, self.instance, self.destination, + self.block_migration, self.disk_over_commit).AndReturn( + "migrate_data") + + self.mox.ReplayAll() + self.task._check_requested_destination() + self.assertEqual("migrate_data", self.task.migrate_data) + + def test_check_requested_destination_fails_with_same_dest(self): + self.task.destination = "same" + self.task.source = "same" + self.assertRaises(exception.UnableToMigrateToSelf, + self.task._check_requested_destination) + + def test_check_requested_destination_fails_when_destination_is_up(self): + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + + db.service_get_by_compute_host(self.context, + self.destination).AndRaise(exception.NotFound) + + self.mox.ReplayAll() + self.assertRaises(exception.ComputeServiceUnavailable, + self.task._check_requested_destination) + + def test_check_requested_destination_fails_with_not_enough_memory(self): + self.mox.StubOutWithMock(self.task, '_check_host_is_up') + self.mox.StubOutWithMock(db, 'service_get_by_compute_host') + + self.task._check_host_is_up(self.destination) + db.service_get_by_compute_host(self.context, + self.destination).AndReturn({ + "compute_node": [{"free_ram_mb": 511}] + }) + + self.mox.ReplayAll() + self.assertRaises(exception.MigrationPreCheckError, + self.task._check_requested_destination) + + def test_check_requested_destination_fails_with_hypervisor_diff(self): + self.mox.StubOutWithMock(self.task, '_check_host_is_up') + self.mox.StubOutWithMock(self.task, + '_check_destination_has_enough_memory') + self.mox.StubOutWithMock(self.task, '_get_compute_info') + + self.task._check_host_is_up(self.destination) + self.task._check_destination_has_enough_memory() + self.task._get_compute_info(self.instance_host).AndReturn({ + "hypervisor_type": "b" + }) + self.task._get_compute_info(self.destination).AndReturn({ + "hypervisor_type": "a" + }) + + self.mox.ReplayAll() + self.assertRaises(exception.InvalidHypervisorType, + self.task._check_requested_destination) + + def test_check_requested_destination_fails_with_hypervisor_too_old(self): + self.mox.StubOutWithMock(self.task, '_check_host_is_up') + self.mox.StubOutWithMock(self.task, + '_check_destination_has_enough_memory') + self.mox.StubOutWithMock(self.task, '_get_compute_info') + + self.task._check_host_is_up(self.destination) + self.task._check_destination_has_enough_memory() + self.task._get_compute_info(self.instance_host).AndReturn({ + "hypervisor_type": "a", + "hypervisor_version": 7 + }) + self.task._get_compute_info(self.destination).AndReturn({ + "hypervisor_type": "a", + "hypervisor_version": 6 + }) + + self.mox.ReplayAll() + self.assertRaises(exception.DestinationHypervisorTooOld, + self.task._check_requested_destination) + + def test_find_destination_works(self): + self.mox.StubOutWithMock(self.task.image_service, 'show') + self.mox.StubOutWithMock(flavors, 'extract_flavor') + self.mox.StubOutWithMock(self.task, + '_check_compatible_with_source_hypervisor') + self.mox.StubOutWithMock(self.task, '_call_livem_checks_on_host') + + self.task.image_service.show(self.context, + self.instance_image).AndReturn("image") + flavors.extract_flavor(self.instance).AndReturn("inst_type") + self.task._check_compatible_with_source_hypervisor("host1") + self.task._call_livem_checks_on_host("host1") + + self.mox.ReplayAll() + self.assertEqual("host1", self.task._find_destination()) + + def _test_find_destination_retry_hypervisor_raises(self, error): + self.mox.StubOutWithMock(self.task.image_service, 'show') + self.mox.StubOutWithMock(flavors, 'extract_flavor') + self.mox.StubOutWithMock(self.task, + '_check_compatible_with_source_hypervisor') + self.mox.StubOutWithMock(self.task, '_call_livem_checks_on_host') + + self.task.image_service.show(self.context, + self.instance_image).AndReturn("image") + flavors.extract_flavor(self.instance).AndReturn("inst_type") + self.task._check_compatible_with_source_hypervisor("host1")\ + .AndRaise(error) + + self.task._check_compatible_with_source_hypervisor("host1") + self.task._call_livem_checks_on_host("host1") + + self.mox.ReplayAll() + self.assertEqual("host1", self.task._find_destination()) + + def test_find_destination_retry_with_old_hypervisor(self): + self._test_find_destination_retry_hypervisor_raises( + exception.DestinationHypervisorTooOld) + + def test_find_destination_retry_with_invalid_hypervisor_type(self): + self._test_find_destination_retry_hypervisor_raises( + exception.InvalidHypervisorType) + + def test_find_destination_retry_with_invalid_livem_checks(self): + self.mox.StubOutWithMock(self.task.image_service, 'show') + self.mox.StubOutWithMock(flavors, 'extract_flavor') + self.mox.StubOutWithMock(self.task, + '_check_compatible_with_source_hypervisor') + self.mox.StubOutWithMock(self.task, '_call_livem_checks_on_host') + + self.task.image_service.show(self.context, + self.instance_image).AndReturn("image") + flavors.extract_flavor(self.instance).AndReturn("inst_type") + self.task._check_compatible_with_source_hypervisor("host1") + self.task._call_livem_checks_on_host("host1")\ + .AndRaise(exception.Invalid) + + self.task._check_compatible_with_source_hypervisor("host1") + self.task._call_livem_checks_on_host("host1") + + self.mox.ReplayAll() + self.assertEqual("host1", self.task._find_destination()) + + def test_find_destination_retry_exceeds_max(self): + self.flags(scheduler_max_attempts=1) + self.mox.StubOutWithMock(self.task.image_service, 'show') + self.mox.StubOutWithMock(flavors, 'extract_flavor') + self.mox.StubOutWithMock(self.task, + '_check_compatible_with_source_hypervisor') + self.mox.StubOutWithMock(self.task, '_call_livem_checks_on_host') + + self.task.image_service.show(self.context, + self.instance_image).AndReturn("image") + flavors.extract_flavor(self.instance).AndReturn("inst_type") + self.task._check_compatible_with_source_hypervisor("host1")\ + .AndRaise(exception.DestinationHypervisorTooOld) + + self.mox.ReplayAll() + self.assertRaises(exception.NoValidHost, self.task._find_destination) + + def test_not_implemented_rollback(self): + self.assertRaises(NotImplementedError, self.task.rollback) |