Merge "Restrict live migration to same cell"

This commit is contained in:
Jenkins 2017-08-23 15:19:33 +00:00 committed by Gerrit Code Review
commit b76291269e
2 changed files with 114 additions and 0 deletions

View File

@ -117,6 +117,16 @@ class LiveMigrationTask(base.TaskBase):
source_node, dest_node = self._check_compatible_with_source_hypervisor( source_node, dest_node = self._check_compatible_with_source_hypervisor(
self.destination) self.destination)
self._call_livem_checks_on_host(self.destination) self._call_livem_checks_on_host(self.destination)
# Make sure the forced destination host is in the same cell that the
# instance currently lives in.
# NOTE(mriedem): This can go away if/when the forced destination host
# case calls select_destinations.
source_cell_mapping = self._get_source_cell_mapping()
dest_cell_mapping = self._get_destination_cell_mapping()
if source_cell_mapping.uuid != dest_cell_mapping.uuid:
raise exception.MigrationPreCheckError(
reason=(_('Unable to force live migrate instance %s '
'across cells.') % self.instance.uuid))
return source_node, dest_node return source_node, dest_node
def _claim_resources_on_destination(self, source_node, dest_node): def _claim_resources_on_destination(self, source_node, dest_node):
@ -246,6 +256,36 @@ class LiveMigrationTask(base.TaskBase):
"%s") % destination "%s") % destination
raise exception.MigrationPreCheckError(msg) raise exception.MigrationPreCheckError(msg)
def _get_source_cell_mapping(self):
"""Returns the CellMapping for the cell in which the instance lives
:returns: nova.objects.CellMapping record for the cell where
the instance currently lives.
:raises: MigrationPreCheckError - in case a mapping is not found
"""
try:
return objects.InstanceMapping.get_by_instance_uuid(
self.context, self.instance.uuid).cell_mapping
except exception.InstanceMappingNotFound:
raise exception.MigrationPreCheckError(
reason=(_('Unable to determine in which cell '
'instance %s lives.') % self.instance.uuid))
def _get_destination_cell_mapping(self):
"""Returns the CellMapping for the destination host
:returns: nova.objects.CellMapping record for the cell where
the destination host is mapped.
:raises: MigrationPreCheckError - in case a mapping is not found
"""
try:
return objects.HostMapping.get_by_host(
self.context, self.destination).cell_mapping
except exception.HostMappingNotFound:
raise exception.MigrationPreCheckError(
reason=(_('Unable to determine in which cell '
'destination host %s lives.') % self.destination))
def _find_destination(self): def _find_destination(self):
# TODO(johngarbutt) this retry loop should be shared # TODO(johngarbutt) this retry loop should be shared
attempted_hosts = [self.source] attempted_hosts = [self.source]
@ -269,6 +309,21 @@ class LiveMigrationTask(base.TaskBase):
# is not forced to be the original host # is not forced to be the original host
request_spec.reset_forced_destinations() request_spec.reset_forced_destinations()
scheduler_utils.setup_instance_group(self.context, request_spec) scheduler_utils.setup_instance_group(self.context, request_spec)
# We currently only support live migrating to hosts in the same
# cell that the instance lives in, so we need to tell the scheduler
# to limit the applicable hosts based on cell.
cell_mapping = self._get_source_cell_mapping()
LOG.debug('Requesting cell %(cell)s while live migrating',
{'cell': cell_mapping.identity},
instance=self.instance)
if ('requested_destination' in request_spec and
request_spec.requested_destination):
request_spec.requested_destination.cell = cell_mapping
else:
request_spec.requested_destination = objects.Destination(
cell=cell_mapping)
host = None host = None
while host is None: while host is None:
self._check_not_over_max_retries(attempted_hosts) self._check_not_over_max_retries(attempted_hosts)

View File

@ -12,6 +12,7 @@
import mock import mock
import oslo_messaging as messaging import oslo_messaging as messaging
import six
from nova.compute import power_state from nova.compute import power_state
from nova.compute import rpcapi as compute_rpcapi from nova.compute import rpcapi as compute_rpcapi
@ -246,6 +247,33 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock.call(self.destination)], mock.call(self.destination)],
mock_get_info.call_args_list) mock_get_info.call_args_list)
@mock.patch.object(objects.Service, 'get_by_compute_host')
@mock.patch.object(live_migrate.LiveMigrationTask, '_get_compute_info')
@mock.patch.object(servicegroup.API, 'service_is_up')
@mock.patch.object(compute_rpcapi.ComputeAPI,
'check_can_live_migrate_destination')
@mock.patch.object(objects.HostMapping, 'get_by_host',
return_value=objects.HostMapping(
cell_mapping=objects.CellMapping(
uuid=uuids.different)))
def test_check_requested_destination_fails_different_cells(
self, mock_get_host_mapping, mock_check, mock_is_up,
mock_get_info, mock_get_host):
mock_get_host.return_value = "service"
mock_is_up.return_value = True
hypervisor_details = objects.ComputeNode(
hypervisor_type="a",
hypervisor_version=6.1,
free_ram_mb=513,
memory_mb=512,
ram_allocation_ratio=1.0)
mock_get_info.return_value = hypervisor_details
mock_check.return_value = "migrate_data"
ex = self.assertRaises(exception.MigrationPreCheckError,
self.task._check_requested_destination)
self.assertIn('across cells', six.text_type(ex))
def test_find_destination_works(self): def test_find_destination_works(self):
self.mox.StubOutWithMock(utils, 'get_image_from_system_metadata') self.mox.StubOutWithMock(utils, 'get_image_from_system_metadata')
self.mox.StubOutWithMock(scheduler_utils, 'setup_instance_group') self.mox.StubOutWithMock(scheduler_utils, 'setup_instance_group')
@ -271,6 +299,10 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
self.assertEqual("host1", self.task._find_destination()) self.assertEqual("host1", self.task._find_destination())
# Make sure the request_spec was updated to include the cell
# mapping.
self.assertIsNotNone(self.fake_spec.requested_destination.cell)
def test_find_destination_works_with_no_request_spec(self): def test_find_destination_works_with_no_request_spec(self):
task = live_migrate.LiveMigrationTask( task = live_migrate.LiveMigrationTask(
self.context, self.instance, self.destination, self.context, self.instance, self.destination,
@ -300,6 +332,9 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
setup_ig.assert_called_once_with(self.context, another_spec) setup_ig.assert_called_once_with(self.context, another_spec)
select_dest.assert_called_once_with(self.context, another_spec, select_dest.assert_called_once_with(self.context, another_spec,
[self.instance.uuid]) [self.instance.uuid])
# Make sure the request_spec was updated to include the cell
# mapping.
self.assertIsNotNone(another_spec.requested_destination.cell)
check_compat.assert_called_once_with("host1") check_compat.assert_called_once_with("host1")
call_livem_checks.assert_called_once_with("host1") call_livem_checks.assert_called_once_with("host1")
do_test() do_test()
@ -577,3 +612,27 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
self.instance.project_id, self.instance.user_id) self.instance.project_id, self.instance.user_id)
test() test()
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
side_effect=exception.InstanceMappingNotFound(
uuid=uuids.instance))
def test_get_source_cell_mapping_not_found(self, mock_get):
"""Negative test where InstanceMappingNotFound is raised and converted
to MigrationPreCheckError.
"""
self.assertRaises(exception.MigrationPreCheckError,
self.task._get_source_cell_mapping)
mock_get.assert_called_once_with(
self.task.context, self.task.instance.uuid)
@mock.patch.object(objects.HostMapping, 'get_by_host',
side_effect=exception.HostMappingNotFound(
name='destination'))
def test_get_destination_cell_mapping_not_found(self, mock_get):
"""Negative test where HostMappingNotFound is raised and converted
to MigrationPreCheckError.
"""
self.assertRaises(exception.MigrationPreCheckError,
self.task._get_destination_cell_mapping)
mock_get.assert_called_once_with(
self.task.context, self.task.destination)