Support live-migrate of instances in PAUSED state
Live migration enables one to migrate an active VM that consumes system resources such as memory and CPU. Nova currently allows only those in ACTIVE vm_state to migrate. This patch adds support for PAUSED vm_state. Essentially a paused state is identical to a running state except the stopped CPUs. Co-Author: "Matthew Gilliard <matthew.gilliard@hp.com>" Partial-Bug: #1305062 Change-Id: Ib38eaf412cb51a9cbfc443c5ec15c5797265ddae
This commit is contained in:
parent
7885332a11
commit
22dde6e56b
|
@ -103,6 +103,7 @@ _STATE_MAP = {
|
|||
},
|
||||
vm_states.PAUSED: {
|
||||
'default': 'PAUSED',
|
||||
task_states.MIGRATING: 'MIGRATING',
|
||||
},
|
||||
vm_states.SUSPENDED: {
|
||||
'default': 'SUSPENDED',
|
||||
|
|
|
@ -347,7 +347,6 @@ class AdminActionsController(wsgi.Controller):
|
|||
exception.InvalidLocalStorage,
|
||||
exception.InvalidSharedStorage,
|
||||
exception.HypervisorUnavailable,
|
||||
exception.InstanceNotRunning,
|
||||
exception.MigrationPreCheckError,
|
||||
exception.LiveMigrationWithOldNovaNotSafe) as ex:
|
||||
raise exc.HTTPBadRequest(explanation=ex.format_message())
|
||||
|
|
|
@ -93,7 +93,6 @@ class MigrateServerController(wsgi.Controller):
|
|||
exception.InvalidLocalStorage,
|
||||
exception.InvalidSharedStorage,
|
||||
exception.HypervisorUnavailable,
|
||||
exception.InstanceNotRunning,
|
||||
exception.MigrationPreCheckError,
|
||||
exception.LiveMigrationWithOldNovaNotSafe) as ex:
|
||||
raise exc.HTTPBadRequest(explanation=ex.format_message())
|
||||
|
|
|
@ -3230,7 +3230,7 @@ class API(base.Base):
|
|||
|
||||
@check_instance_lock
|
||||
@check_instance_cell
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE])
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED])
|
||||
def live_migrate(self, context, instance, block_migration,
|
||||
disk_over_commit, host_name):
|
||||
"""Migrate a server lively to a new host."""
|
||||
|
|
|
@ -5261,7 +5261,6 @@ class ComputeManager(manager.Manager):
|
|||
finally:
|
||||
instance.host = self.host
|
||||
instance.power_state = current_power_state
|
||||
instance.vm_state = vm_states.ACTIVE
|
||||
instance.task_state = None
|
||||
instance.node = node_name
|
||||
instance.save(expected_task_state=task_states.MIGRATING)
|
||||
|
@ -5288,7 +5287,6 @@ class ComputeManager(manager.Manager):
|
|||
if not none, contains implementation specific data.
|
||||
|
||||
"""
|
||||
instance.vm_state = vm_states.ACTIVE
|
||||
instance.task_state = None
|
||||
instance.save(expected_task_state=[task_states.MIGRATING])
|
||||
|
||||
|
|
|
@ -478,7 +478,7 @@ class ComputeTaskManager(base.Base):
|
|||
exception.InvalidLocalStorage,
|
||||
exception.InvalidSharedStorage,
|
||||
exception.HypervisorUnavailable,
|
||||
exception.InstanceNotRunning,
|
||||
exception.InstanceInvalidState,
|
||||
exception.MigrationPreCheckError,
|
||||
exception.LiveMigrationWithOldNovaNotSafe,
|
||||
exception.UnsupportedPolicyException)
|
||||
|
@ -614,7 +614,7 @@ class ComputeTaskManager(base.Base):
|
|||
exception.InvalidLocalStorage,
|
||||
exception.InvalidSharedStorage,
|
||||
exception.HypervisorUnavailable,
|
||||
exception.InstanceNotRunning,
|
||||
exception.InstanceInvalidState,
|
||||
exception.MigrationPreCheckError,
|
||||
exception.LiveMigrationWithOldNovaNotSafe) as ex:
|
||||
with excutils.save_and_reraise_exception():
|
||||
|
|
|
@ -52,7 +52,7 @@ class LiveMigrationTask(object):
|
|||
self.image_api = image.API()
|
||||
|
||||
def execute(self):
|
||||
self._check_instance_is_running()
|
||||
self._check_instance_is_active()
|
||||
self._check_host_is_up(self.source)
|
||||
|
||||
if not self.destination:
|
||||
|
@ -77,10 +77,14 @@ class LiveMigrationTask(object):
|
|||
# rollback call right now.
|
||||
raise NotImplementedError()
|
||||
|
||||
def _check_instance_is_running(self):
|
||||
if self.instance.power_state != power_state.RUNNING:
|
||||
raise exception.InstanceNotRunning(
|
||||
instance_id=self.instance.uuid)
|
||||
def _check_instance_is_active(self):
|
||||
if self.instance.power_state not in (power_state.RUNNING,
|
||||
power_state.PAUSED):
|
||||
raise exception.InstanceInvalidState(
|
||||
instance_uuid = self.instance.uuid,
|
||||
attr = 'power_state',
|
||||
state = self.instance.power_state,
|
||||
method = 'live migrate')
|
||||
|
||||
def _check_host_is_up(self, host):
|
||||
try:
|
||||
|
|
|
@ -240,9 +240,12 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests):
|
|||
self._test_migrate_live_failed_with_exception(
|
||||
exception.HypervisorUnavailable(host=""))
|
||||
|
||||
def test_migrate_live_instance_not_running(self):
|
||||
def test_migrate_live_instance_not_active(self):
|
||||
self._test_migrate_live_failed_with_exception(
|
||||
exception.InstanceNotRunning(instance_id=""))
|
||||
exception.InstanceInvalidState(
|
||||
instance_uuid='', state='', attr='', method=''),
|
||||
expected_status_code=409,
|
||||
check_response=False)
|
||||
|
||||
def test_migrate_live_pre_check_error(self):
|
||||
self._test_migrate_live_failed_with_exception(
|
||||
|
|
|
@ -438,6 +438,13 @@ class MiscFunctionsTest(test.TestCase):
|
|||
expected = 'REBUILD'
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_status_migrating_from_state(self):
|
||||
for vm_state in (vm_states.ACTIVE, vm_states.PAUSED):
|
||||
task_state = task_states.MIGRATING
|
||||
actual = common.status_from_state(vm_state, task_state)
|
||||
expected = 'MIGRATING'
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_task_and_vm_state_from_status(self):
|
||||
fixture1 = ['reboot']
|
||||
actual = common.task_and_vm_state_from_status(fixture1)
|
||||
|
|
|
@ -33,6 +33,7 @@ from nova.compute import task_states
|
|||
from nova.compute import utils as compute_utils
|
||||
from nova.compute import vm_mode
|
||||
from nova.compute import vm_states
|
||||
from nova import conductor
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
|
@ -1695,6 +1696,36 @@ class _ComputeAPIUnitTestMixIn(object):
|
|||
self.assertEqual(vm_states.PAUSED, instance.vm_state)
|
||||
self.assertEqual(task_states.UNPAUSING, instance.task_state)
|
||||
|
||||
def test_live_migrate_active_vm_state(self):
|
||||
instance = self._create_instance_obj()
|
||||
self._live_migrate_instance(instance)
|
||||
|
||||
def test_live_migrate_paused_vm_state(self):
|
||||
paused_state = dict(vm_state=vm_states.PAUSED)
|
||||
instance = self._create_instance_obj(params=paused_state)
|
||||
self._live_migrate_instance(instance)
|
||||
|
||||
@mock.patch.object(objects.Instance, 'save')
|
||||
@mock.patch.object(objects.InstanceAction, 'action_start')
|
||||
def _live_migrate_instance(self, instance, _save, _action):
|
||||
# TODO(gilliard): This logic is upside-down (different
|
||||
# behaviour depending on which class this method is mixed-into. Once
|
||||
# we have cellsv2 we can remove this kind of logic from this test
|
||||
if self.cell_type == 'api':
|
||||
api = self.compute_api.cells_rpcapi
|
||||
else:
|
||||
api = conductor.api.ComputeTaskAPI
|
||||
with mock.patch.object(api, 'live_migrate_instance') as task:
|
||||
self.compute_api.live_migrate(self.context, instance,
|
||||
block_migration=True,
|
||||
disk_over_commit=True,
|
||||
host_name='fake_dest_host')
|
||||
self.assertEqual(task_states.MIGRATING, instance.task_state)
|
||||
task.assert_called_once_with(self.context, instance,
|
||||
'fake_dest_host',
|
||||
block_migration=True,
|
||||
disk_over_commit=True)
|
||||
|
||||
def test_swap_volume_volume_api_usage(self):
|
||||
# This test ensures that volume_id arguments are passed to volume_api
|
||||
# and that volumes return to previous states in case of error.
|
||||
|
|
|
@ -85,13 +85,14 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
|
|||
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_active_passes_when_paused(self):
|
||||
self.task.instance['power_state'] = power_state.PAUSED
|
||||
self.task._check_instance_is_active()
|
||||
|
||||
def test_check_instance_is_running_fails_when_shutdown(self):
|
||||
def test_check_instance_is_active_fails_when_shutdown(self):
|
||||
self.task.instance['power_state'] = power_state.SHUTDOWN
|
||||
self.assertRaises(exception.InstanceNotRunning,
|
||||
self.task._check_instance_is_running)
|
||||
self.assertRaises(exception.InstanceInvalidState,
|
||||
self.task._check_instance_is_active)
|
||||
|
||||
def test_check_instance_host_is_up(self):
|
||||
self.mox.StubOutWithMock(objects.Service, 'get_by_compute_host')
|
||||
|
|
|
@ -1801,32 +1801,10 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
|||
{'host': 'destination'}, True, False, None, 'block_migration',
|
||||
'disk_over_commit')
|
||||
|
||||
@mock.patch.object(scheduler_utils, 'set_vm_state_and_notify')
|
||||
@mock.patch.object(live_migrate, 'execute')
|
||||
def test_migrate_server_deals_with_instancenotrunning_exception(self,
|
||||
mock_live_migrate, mock_set_state):
|
||||
inst = fake_instance.fake_db_instance()
|
||||
inst_obj = objects.Instance._from_db_object(
|
||||
self.context, objects.Instance(), inst, [])
|
||||
|
||||
error = exc.InstanceNotRunning(instance_id="fake")
|
||||
mock_live_migrate.side_effect = error
|
||||
|
||||
self.conductor = utils.ExceptionHelper(self.conductor)
|
||||
|
||||
self.assertRaises(exc.InstanceNotRunning,
|
||||
self.conductor.migrate_server, self.context, inst_obj,
|
||||
{'host': 'destination'}, True, False, None,
|
||||
'block_migration', 'disk_over_commit')
|
||||
|
||||
request_spec = self._build_request_spec(inst_obj)
|
||||
mock_set_state.assert_called_once_with(self.context, inst_obj.uuid,
|
||||
'compute_task',
|
||||
'migrate_server',
|
||||
dict(vm_state=inst_obj.vm_state,
|
||||
task_state=None,
|
||||
expected_task_state=task_states.MIGRATING),
|
||||
error, request_spec, self.conductor_manager.db)
|
||||
def test_migrate_server_deals_with_InstanceInvalidState(self):
|
||||
ex = exc.InstanceInvalidState(instance_uuid="fake", attr='',
|
||||
state='', method='')
|
||||
self._test_migrate_server_deals_with_expected_exceptions(ex)
|
||||
|
||||
def test_migrate_server_deals_with_DestinationHypervisorTooOld(self):
|
||||
ex = exc.DestinationHypervisorTooOld()
|
||||
|
|
Loading…
Reference in New Issue