Merge "Restrict live migration to same cell"
This commit is contained in:
commit
b76291269e
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user