Browse Source

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
tags/2015.1.0b3
Loganathan Parthipan 5 years ago
parent
commit
22dde6e56b
12 changed files with 66 additions and 45 deletions
  1. +1
    -0
      nova/api/openstack/common.py
  2. +0
    -1
      nova/api/openstack/compute/contrib/admin_actions.py
  3. +0
    -1
      nova/api/openstack/compute/plugins/v3/migrate_server.py
  4. +1
    -1
      nova/compute/api.py
  5. +0
    -2
      nova/compute/manager.py
  6. +2
    -2
      nova/conductor/manager.py
  7. +9
    -5
      nova/conductor/tasks/live_migrate.py
  8. +5
    -2
      nova/tests/unit/api/openstack/compute/contrib/test_migrate_server.py
  9. +7
    -0
      nova/tests/unit/api/openstack/test_common.py
  10. +31
    -0
      nova/tests/unit/compute/test_compute_api.py
  11. +6
    -5
      nova/tests/unit/conductor/tasks/test_live_migrate.py
  12. +4
    -26
      nova/tests/unit/conductor/test_conductor.py

+ 1
- 0
nova/api/openstack/common.py View File

@@ -103,6 +103,7 @@ _STATE_MAP = {
},
vm_states.PAUSED: {
'default': 'PAUSED',
task_states.MIGRATING: 'MIGRATING',
},
vm_states.SUSPENDED: {
'default': 'SUSPENDED',

+ 0
- 1
nova/api/openstack/compute/contrib/admin_actions.py View File

@@ -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())

+ 0
- 1
nova/api/openstack/compute/plugins/v3/migrate_server.py View File

@@ -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())

+ 1
- 1
nova/compute/api.py View File

@@ -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."""

+ 0
- 2
nova/compute/manager.py View File

@@ -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])


+ 2
- 2
nova/conductor/manager.py View File

@@ -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():

+ 9
- 5
nova/conductor/tasks/live_migrate.py View File

@@ -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:

+ 5
- 2
nova/tests/unit/api/openstack/compute/contrib/test_migrate_server.py View File

@@ -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(

+ 7
- 0
nova/tests/unit/api/openstack/test_common.py View File

@@ -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)

+ 31
- 0
nova/tests/unit/compute/test_compute_api.py View File

@@ -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.

+ 6
- 5
nova/tests/unit/conductor/tasks/test_live_migrate.py View File

@@ -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')

+ 4
- 26
nova/tests/unit/conductor/test_conductor.py View File

@@ -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…
Cancel
Save