Added cold VM migration support

Cold migration enables migrating some of the VMs which are not in active state (e.g. stopped). Cold migration can also be used for migrating active VM, although VM is shut down and hence unaccessible while migrating.

Change-Id: I89ad0a04d41282431c9773f6ae7feb41573368e3
Closes-Bug: #1564297
This commit is contained in:
cima
2016-03-31 15:13:43 +02:00
parent 99ff6d3348
commit 388ef9f11c
2 changed files with 79 additions and 13 deletions

View File

@@ -31,27 +31,24 @@ LOG = log.getLogger(__name__)
class Migrate(base.BaseAction):
"""Live-Migrates a server to a destination nova-compute host
"""Migrates a server to a destination nova-compute host
This action will allow you to migrate a server to another compute
destination host. As of now, only live migration can be performed using
this action.
.. If either host uses shared storage, you can use ``live``
.. as ``migration_type``. If both source and destination hosts provide
.. local disks, you can set the block_migration parameter to True (not
.. supported for yet).
destination host.
Migration type 'live' can only be used for migrating active VMs.
Migration type 'cold' can be used for migrating non-active VMs
as well active VMs, which will be shut down while migrating.
The action schema is::
schema = Schema({
'resource_id': str, # should be a UUID
'migration_type': str, # choices -> "live" only
'migration_type': str, # choices -> "live", "cold"
'dst_hypervisor': str,
'src_hypervisor': str,
})
The `resource_id` is the UUID of the server to migrate. Only live migration
is supported.
The `resource_id` is the UUID of the server to migrate.
The `src_hypervisor` and `dst_hypervisor` parameters are respectively the
source and the destination compute hostname (list of available compute
hosts is returned by this command: ``nova service-list --binary
@@ -61,6 +58,7 @@ class Migrate(base.BaseAction):
# input parameters constants
MIGRATION_TYPE = 'migration_type'
LIVE_MIGRATION = 'live'
COLD_MIGRATION = 'cold'
DST_HYPERVISOR = 'dst_hypervisor'
SRC_HYPERVISOR = 'src_hypervisor'
@@ -77,7 +75,8 @@ class Migrate(base.BaseAction):
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
voluptuous.Required(self.MIGRATION_TYPE,
default=self.LIVE_MIGRATION):
voluptuous.Any(*[self.LIVE_MIGRATION]),
voluptuous.Any(*[self.LIVE_MIGRATION,
self.COLD_MIGRATION]),
voluptuous.Required(self.DST_HYPERVISOR):
voluptuous.All(voluptuous.Any(*six.string_types),
voluptuous.Length(min=1)),
@@ -127,14 +126,30 @@ class Migrate(base.BaseAction):
return result
def _cold_migrate_instance(self, nova, destination):
result = None
try:
result = nova.watcher_non_live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination)
except Exception as exc:
LOG.exception(exc)
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,
destination)
instance = nova.find_instance(self.instance_uuid)
if instance:
if self.migration_type == 'live':
if self.migration_type == self.LIVE_MIGRATION:
return self._live_migrate_instance(nova, destination)
elif self.migration_type == self.COLD_MIGRATION:
return self._cold_migrate_instance(nova, destination)
else:
raise exception.Invalid(
message=(_('Migration of type %(migration_type)s is not '

View File

@@ -61,6 +61,15 @@ class TestMigration(base.TestCase):
self.action = migration.Migrate()
self.action.input_parameters = self.input_parameters
self.input_parameters_cold = {
"migration_type": "cold",
"src_hypervisor": "hypervisor1-hostname",
"dst_hypervisor": "hypervisor2-hostname",
baction.BaseAction.RESOURCE_ID: self.INSTANCE_UUID,
}
self.action_cold = migration.Migrate()
self.action_cold.input_parameters = self.input_parameters_cold
def test_parameters(self):
params = {baction.BaseAction.RESOURCE_ID:
self.INSTANCE_UUID,
@@ -70,6 +79,15 @@ class TestMigration(base.TestCase):
self.action.input_parameters = params
self.assertEqual(True, self.action.validate_parameters())
def test_parameters_cold(self):
params = {baction.BaseAction.RESOURCE_ID:
self.INSTANCE_UUID,
self.action.MIGRATION_TYPE: 'cold',
self.action.DST_HYPERVISOR: 'compute-2',
self.action.SRC_HYPERVISOR: 'compute-3'}
self.action_cold.input_parameters = params
self.assertEqual(True, self.action_cold.validate_parameters())
def test_parameters_exception_empty_fields(self):
parameters = {baction.BaseAction.RESOURCE_ID: None,
'migration_type': None,
@@ -87,7 +105,7 @@ class TestMigration(base.TestCase):
def test_parameters_exception_migration_type(self):
parameters = {baction.BaseAction.RESOURCE_ID:
self.INSTANCE_UUID,
'migration_type': 'cold',
'migration_type': 'unknown',
'src_hypervisor': 'compute-2',
'dst_hypervisor': 'compute-3'}
self.action.input_parameters = parameters
@@ -154,6 +172,13 @@ class TestMigration(base.TestCase):
self.m_helper.find_instance.assert_called_once_with(self.INSTANCE_UUID)
self.assertEqual(self.INSTANCE_UUID, exc.kwargs["name"])
def test_execute_cold_migration_invalid_instance(self):
self.m_helper.find_instance.return_value = None
exc = self.assertRaises(
exception.InstanceNotFound, self.action_cold.execute)
self.m_helper.find_instance.assert_called_once_with(self.INSTANCE_UUID)
self.assertEqual(self.INSTANCE_UUID, exc.kwargs["name"])
def test_execute_live_migration(self):
self.m_helper.find_instance.return_value = self.INSTANCE_UUID
@@ -166,6 +191,20 @@ class TestMigration(base.TestCase):
instance_id=self.INSTANCE_UUID,
dest_hostname="hypervisor2-hostname")
def test_execute_cold_migration(self):
self.m_helper.find_instance.return_value = self.INSTANCE_UUID
try:
self.action_cold.execute()
except Exception as exc:
self.fail(exc)
self.m_helper.watcher_non_live_migrate_instance.\
assert_called_once_with(
instance_id=self.INSTANCE_UUID,
dest_hostname="hypervisor2-hostname"
)
def test_revert_live_migration(self):
self.m_helper.find_instance.return_value = self.INSTANCE_UUID
@@ -177,6 +216,18 @@ class TestMigration(base.TestCase):
dest_hostname="hypervisor1-hostname"
)
def test_revert_cold_migration(self):
self.m_helper.find_instance.return_value = self.INSTANCE_UUID
self.action_cold.revert()
self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
self.m_helper.watcher_non_live_migrate_instance.\
assert_called_once_with(
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