From 5bb1b6cbf05ccbd196a86c705025592a9c805260 Mon Sep 17 00:00:00 2001 From: Daniel Pawlik Date: Mon, 29 Feb 2016 16:23:51 +0000 Subject: [PATCH] Added support for live migration on non-shared storage Watcher applier should be able to live migrate instances on any storage type. To do this watcher will catch error 400 returned from nova if we try to live migrate instance which is not on shared storage and live migrate instance using block_migrate. Added unit tests, changed action in watcher applier. Closes-bug: #1549307 Change-Id: I97e583c9b4a0bb9daa1d39e6d652d6474a5aaeb1 --- watcher/applier/actions/migration.py | 30 +++++++++++++++-- .../tests/applier/actions/test_migration.py | 33 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/watcher/applier/actions/migration.py b/watcher/applier/actions/migration.py index 7594d81ab..ce6219f26 100644 --- a/watcher/applier/actions/migration.py +++ b/watcher/applier/actions/migration.py @@ -21,7 +21,7 @@ from oslo_log import log import six import voluptuous -from watcher._i18n import _ +from watcher._i18n import _, _LC from watcher.applier.actions import base from watcher.common import exception from watcher.common import nova_helper @@ -76,6 +76,31 @@ class Migrate(base.BaseAction): def src_hypervisor(self): return self.input_parameters.get(self.SRC_HYPERVISOR) + def _live_migrate_instance(self, nova, destination): + result = None + try: + result = nova.live_migrate_instance(instance_id=self.instance_uuid, + dest_hostname=destination) + except nova_helper.nvexceptions.ClientException as e: + if e.code == 400: + LOG.debug("Live migration of instance %s failed. " + "Trying to live migrate using block migration." + % self.instance_uuid) + result = nova.live_migrate_instance( + instance_id=self.instance_uuid, + dest_hostname=destination, + block_migration=True) + else: + LOG.debug("Nova client exception occured while live migrating " + "instance %s.Exception: %s" % + (self.instance_uuid, e)) + except Exception: + LOG.critical(_LC("Unexpected error occured. Migration failed for" + "instance %s. Leaving instance on previous " + "host."), self.instance_uuid) + + return result + def migrate(self, destination): nova = nova_helper.NovaHelper(osc=self.osc) LOG.debug("Migrate instance %s to %s", self.instance_uuid, @@ -83,8 +108,7 @@ class Migrate(base.BaseAction): instance = nova.find_instance(self.instance_uuid) if instance: if self.migration_type == 'live': - return nova.live_migrate_instance( - instance_id=self.instance_uuid, dest_hostname=destination) + return self._live_migrate_instance(nova, destination) else: raise exception.Invalid( message=(_('Migration of type %(migration_type)s is not ' diff --git a/watcher/tests/applier/actions/test_migration.py b/watcher/tests/applier/actions/test_migration.py index 82409ed00..6bee69c34 100644 --- a/watcher/tests/applier/actions/test_migration.py +++ b/watcher/tests/applier/actions/test_migration.py @@ -157,7 +157,10 @@ class TestMigration(base.TestCase): def test_execute_live_migration(self): self.m_helper.find_instance.return_value = self.INSTANCE_UUID - self.action.execute() + try: + self.action.execute() + except Exception as exc: + self.fail(exc) self.m_helper.live_migrate_instance.assert_called_once_with( instance_id=self.INSTANCE_UUID, @@ -173,3 +176,31 @@ class TestMigration(base.TestCase): instance_id=self.INSTANCE_UUID, dest_hostname="hypervisor1-hostname" ) + + def test_live_migrate_non_shared_storage_instance(self): + self.m_helper.find_instance.return_value = self.INSTANCE_UUID + + self.m_helper.live_migrate_instance.side_effect = [ + nova_helper.nvexceptions.ClientException(400, "BadRequest"), True] + + try: + self.action.execute() + except Exception as exc: + self.fail(exc) + + self.m_helper.live_migrate_instance.assert_has_calls([ + mock.call(instance_id=self.INSTANCE_UUID, + dest_hostname="hypervisor2-hostname"), + mock.call(instance_id=self.INSTANCE_UUID, + dest_hostname="hypervisor2-hostname", + block_migration=True) + ]) + + expected = [mock.call.first(instance_id=self.INSTANCE_UUID, + dest_hostname="hypervisor2-hostname"), + mock.call.second(instance_id=self.INSTANCE_UUID, + dest_hostname="hypervisor2-hostname", + block_migration=True) + ] + self.m_helper.live_migrate_instance.mock_calls == expected + self.assertEqual(2, self.m_helper.live_migrate_instance.call_count)