Skip to content

Commit

Permalink
Handle compute node not available for live migration
Browse files Browse the repository at this point in the history
This patch handles exception.ComputeServiceUnavailable by restoring
instance's vm_state and instance's task_state after live migration
failure caused by unavailable source/dest compute node.

Raises detailed HTTPBadRequest explanation for this exception.

Fixes bug 973393 and bug 1051881

Change-Id: If825b61fad9c4e3030f2e6c5002907255eaf3661
  • Loading branch information
Jian Wen committed Jan 31, 2013
1 parent b7e0c9d commit be62d6a
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 12 deletions.
2 changes: 2 additions & 0 deletions nova/api/openstack/compute/contrib/admin_actions.py
Expand Up @@ -282,6 +282,8 @@ def _migrate_live(self, req, id, body):
instance = self.compute_api.get(context, id)
self.compute_api.live_migrate(context, instance, block_migration,
disk_over_commit, host)
except exception.ComputeServiceUnavailable as ex:
raise exc.HTTPBadRequest(explanation=str(ex))
except Exception:
msg = _("Live migration of instance %(id)s to host %(host)s"
" failed") % locals()
Expand Down
2 changes: 1 addition & 1 deletion nova/exception.py
Expand Up @@ -358,7 +358,7 @@ class ComputeResourcesUnavailable(ServiceUnavailable):


class ComputeServiceUnavailable(ServiceUnavailable):
message = _("Compute service is unavailable at this time.")
message = _("Compute service of %(host)s is unavailable at this time.")


class UnableToMigrateToSelf(Invalid):
Expand Down
5 changes: 4 additions & 1 deletion nova/scheduler/driver.py
Expand Up @@ -209,7 +209,10 @@ def _live_migration_dest_check(self, context, instance_ref, dest):
"""

# Checking dest exists and compute node.
dservice_ref = db.service_get_by_compute_host(context, dest)
try:
dservice_ref = db.service_get_by_compute_host(context, dest)
except exception.NotFound:
raise exception.ComputeServiceUnavailable(host=dest)

# Checking dest host is alive.
if not self.servicegroup_api.service_is_up(dservice_ref):
Expand Down
11 changes: 11 additions & 0 deletions nova/scheduler/manager.py
Expand Up @@ -24,6 +24,7 @@
import sys

from nova.compute import rpcapi as compute_rpcapi
from nova.compute import task_states
from nova.compute import utils as compute_utils
from nova.compute import vm_states
from nova.conductor import api as conductor_api
Expand Down Expand Up @@ -92,6 +93,16 @@ def live_migration(self, context, instance, dest,
return self.driver.schedule_live_migration(
context, instance, dest,
block_migration, disk_over_commit)
except exception.ComputeServiceUnavailable as ex:
request_spec = {'instance_properties': {
'uuid': instance['uuid'], },
}
with excutils.save_and_reraise_exception():
self._set_vm_state_and_notify('live_migration',
dict(vm_state=instance['vm_state'],
task_state=None,
expected_task_state=task_states.MIGRATING,),
context, ex, request_spec)
except Exception as ex:
with excutils.save_and_reraise_exception():
self._set_vm_state_and_notify('live_migration',
Expand Down
57 changes: 47 additions & 10 deletions nova/tests/api/openstack/compute/contrib/test_admin_actions.py
Expand Up @@ -64,13 +64,6 @@ def fake_compute_api_get(self, context, instance_id):
'task_state': None}


def fake_scheduler_api_live_migration(self, context, dest,
block_migration=False,
disk_over_commit=False, instance=None,
instance_id=None, topic=None):
return None


class AdminActionsTest(test.TestCase):

_actions = ('pause', 'unpause', 'suspend', 'resume', 'migrate',
Expand All @@ -93,9 +86,6 @@ def setUp(self):
self.UUID = uuid.uuid4()
for _method in self._methods:
self.stubs.Set(compute_api.API, _method, fake_compute_api)
self.stubs.Set(scheduler_rpcapi.SchedulerAPI,
'live_migration',
fake_scheduler_api_live_migration)
self.flags(
osapi_compute_extension=[
'nova.api.openstack.compute.contrib.select_extensions'],
Expand Down Expand Up @@ -150,7 +140,16 @@ def fake_update(inst, context, instance,
task_state, expected_task_state):
return None

def fake_scheduler_api_live_migration(self, context, dest,
block_migration=False,
disk_over_commit=False, instance=None,
instance_id=None, topic=None):
return None

self.stubs.Set(compute_api.API, 'update', fake_update)
self.stubs.Set(scheduler_rpcapi.SchedulerAPI,
'live_migration',
fake_scheduler_api_live_migration)

res = req.get_response(app)
self.assertEqual(res.status_int, 202)
Expand All @@ -174,6 +173,44 @@ def test_migrate_live_missing_dict_param(self):
res = req.get_response(app)
self.assertEqual(res.status_int, 400)

def test_migrate_live_compute_service_unavailable(self):
ctxt = context.get_admin_context()
ctxt.user_id = 'fake'
ctxt.project_id = 'fake'
ctxt.is_admin = True
app = fakes.wsgi_app(fake_auth_context=ctxt, init_only=('servers',))
req = webob.Request.blank('/v2/fake/servers/%s/action' % self.UUID)
req.method = 'POST'
req.body = jsonutils.dumps({
'os-migrateLive': {
'host': 'hostname',
'block_migration': False,
'disk_over_commit': False,
}
})
req.content_type = 'application/json'

def fake_update(inst, context, instance,
task_state, expected_task_state):
return None

def fake_scheduler_api_live_migration(context, dest,
block_migration=False,
disk_over_commit=False, instance=None,
instance_id=None, topic=None):
raise exception.ComputeServiceUnavailable(host='host')

self.stubs.Set(compute_api.API, 'update', fake_update)
self.stubs.Set(scheduler_rpcapi.SchedulerAPI,
'live_migration',
fake_scheduler_api_live_migration)

res = req.get_response(app)
self.assertEqual(res.status_int, 400)
self.assertIn(
unicode(exception.ComputeServiceUnavailable(host='host')),
res.body)


class CreateBackupTests(test.TestCase):

Expand Down
56 changes: 56 additions & 0 deletions nova/tests/scheduler/test_scheduler.py
Expand Up @@ -24,6 +24,7 @@
from nova.compute import api as compute_api
from nova.compute import power_state
from nova.compute import rpcapi as compute_rpcapi
from nova.compute import task_states
from nova.compute import utils as compute_utils
from nova.compute import vm_states
from nova.conductor import api as conductor_api
Expand Down Expand Up @@ -197,6 +198,38 @@ def test_run_instance_exception_puts_instance_in_error_state(self):
self.manager.run_instance(self.context, request_spec,
None, None, None, None, {})

def test_live_migration_compute_service_notavailable(self):
inst = {"uuid": "fake-instance-id",
"vm_state": vm_states.ACTIVE,
"task_state": task_states.MIGRATING, }

dest = 'fake_host'
block_migration = False
disk_over_commit = False

self._mox_schedule_method_helper('schedule_live_migration')
self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')

self.manager.driver.schedule_live_migration(self.context,
inst, dest, block_migration, disk_over_commit).AndRaise(
exception.ComputeServiceUnavailable(host="src"))
db.instance_update_and_get_original(self.context, inst["uuid"],
{"vm_state": inst['vm_state'],
"task_state": None,
"expected_task_state": task_states.MIGRATING,
}).AndReturn((inst, inst))
compute_utils.add_instance_fault_from_exc(self.context,
mox.IsA(conductor_api.LocalAPI), inst,
mox.IsA(exception.ComputeServiceUnavailable),
mox.IgnoreArg())

self.mox.ReplayAll()
self.assertRaises(exception.ComputeServiceUnavailable,
self.manager.live_migration,
self.context, inst, dest, block_migration,
disk_over_commit)

def test_prep_resize_no_valid_host_back_in_active_state(self):
fake_instance_uuid = 'fake-instance-id'
inst = {"vm_state": "", "task_state": ""}
Expand Down Expand Up @@ -502,6 +535,29 @@ def test_live_migration_compute_src_not_alive(self):
block_migration=block_migration,
disk_over_commit=disk_over_commit)

def test_live_migration_compute_dest_not_exist(self):
# Raise exception when dest compute node does not exist.

self.mox.StubOutWithMock(self.driver, '_live_migration_src_check')
self.mox.StubOutWithMock(db, 'service_get_by_compute_host')

dest = 'fake_host2'
block_migration = False
disk_over_commit = False
instance = self._live_migration_instance()

self.driver._live_migration_src_check(self.context, instance)
# Compute down
db.service_get_by_compute_host(self.context,
dest).AndRaise(exception.NotFound())

self.mox.ReplayAll()
self.assertRaises(exception.ComputeServiceUnavailable,
self.driver.schedule_live_migration, self.context,
instance=instance, dest=dest,
block_migration=block_migration,
disk_over_commit=disk_over_commit)

def test_live_migration_compute_dest_not_alive(self):
# Raise exception when dest compute node is not alive.

Expand Down

0 comments on commit be62d6a

Please sign in to comment.