Skip to content

Commit

Permalink
Add transitional support for migrate data objects to compute manager
Browse files Browse the repository at this point in the history
This introduces a base live migration data object and makes compute
manager convert any migrate data objects received from the driver
back to dicts. This is transitional as we build support for using
these objects, as well as infrastructure around handling the
conversion to them over the next RPC major version.

The goal here will be to unify all of the random dicts that we pass
around between live migration steps with one type of object (which
will be subclassed per driver). Right now, we have dest_check_data,
migrate_data, and pre_live_migration_result which are all weird
semi-overlapping versions of each other. But, in order to this in
a graceful way, we need to support generating the old dict format,
which is even weirder when it comes from something uniform. Hang on,
it's going to be a bumpy ride, but better in the long run.

In this patch, we're setting the stage for per-driver conversions to
come afterwards. This converts the hyperv driver in-place, because
it's trivial.

Related to blueprint objectify-live-migrate-data

Change-Id: Id4bbb818fb7032a3f8fe48d6bd39b4d52d6117a2
  • Loading branch information
kk7ds committed Dec 16, 2015
1 parent a1143e1 commit 038dfd6
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 4 deletions.
15 changes: 12 additions & 3 deletions nova/compute/manager.py
Expand Up @@ -84,6 +84,7 @@
from nova import objects
from nova.objects import base as obj_base
from nova.objects import instance as obj_instance
from nova.objects import migrate_data as migrate_data_obj
from nova import paths
from nova import rpc
from nova import safe_utils
Expand Down Expand Up @@ -5008,6 +5009,8 @@ def _do_check_can_live_migrate_destination(self, ctxt, instance,
dest_check_data = self.driver.check_can_live_migrate_destination(ctxt,
instance, src_compute_info, dst_compute_info,
block_migration, disk_over_commit)
if isinstance(dest_check_data, migrate_data_obj.LiveMigrateData):
dest_check_data = dest_check_data.to_legacy_dict()
migrate_data = {}
try:
migrate_data = self.compute_rpcapi.\
Expand Down Expand Up @@ -5039,9 +5042,12 @@ def check_can_live_migrate_source(self, ctxt, instance, dest_check_data):
dest_check_data['is_volume_backed'] = is_volume_backed
block_device_info = self._get_instance_block_device_info(
ctxt, instance, refresh_conn_info=True)
return self.driver.check_can_live_migrate_source(ctxt, instance,
dest_check_data,
block_device_info)
result = self.driver.check_can_live_migrate_source(ctxt, instance,
dest_check_data,
block_device_info)
if isinstance(result, migrate_data_obj.LiveMigrateData):
result = result.to_legacy_dict()
return result

@wrap_exception()
@wrap_instance_event
Expand Down Expand Up @@ -5072,6 +5078,9 @@ def pre_live_migration(self, context, instance, block_migration, disk,
network_info,
disk,
migrate_data)
if isinstance(pre_live_migration_data,
migrate_data_obj.LiveMigrateData):
pre_live_migration_data = pre_live_migration_data.to_legacy_dict()

# NOTE(tr3buchet): setup networks on destination host
self.network_api.setup_networks_on_host(context, instance,
Expand Down
41 changes: 41 additions & 0 deletions nova/objects/migrate_data.py
@@ -0,0 +1,41 @@
# Copyright 2015 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from nova.objects import base as obj_base
from nova.objects import fields


@obj_base.NovaObjectRegistry.register_if(False)
class LiveMigrateData(obj_base.NovaObject):
fields = {
'is_volume_backed': fields.BooleanField(),
'migration': fields.ObjectField('Migration'),
}

def to_legacy_dict(self, pre_migration_result=False):
legacy = {}
if self.obj_attr_is_set('is_volume_backed'):
legacy['is_volume_backed'] = self.is_volume_backed
if self.obj_attr_is_set('migration'):
legacy['migration'] = self.migration
if pre_migration_result:
legacy['pre_live_migration_result'] = {}

return legacy

def from_legacy_dict(self, legacy):
if 'is_volume_backed' in legacy:
self.is_volume_backed = legacy['is_volume_backed']
if 'migration' in legacy:
self.migration = legacy['migration']
42 changes: 42 additions & 0 deletions nova/tests/unit/compute/test_compute_mgr.py
Expand Up @@ -42,6 +42,7 @@
from nova.network import model as network_model
from nova import objects
from nova.objects import block_device as block_device_obj
from nova.objects import migrate_data as migrate_data_obj
from nova import test
from nova.tests.unit.compute import fake_resource_tracker
from nova.tests.unit import fake_block_device
Expand Down Expand Up @@ -4264,3 +4265,44 @@ def test_max_concurrent_live_semaphore_negative(self):
self.assertEqual(0, compute._live_migration_semaphore.balance)
self.assertIsInstance(compute._live_migration_semaphore,
compute_utils.UnlimitedSemaphore)

def test_check_migrate_source_converts_object(self):
# NOTE(danms): Make sure that we legacy-ify any data objects
# the drivers give us back, until we're ready for them
data = migrate_data_obj.LiveMigrateData(is_volume_backed=False)
compute = manager.ComputeManager()

@mock.patch.object(compute.driver, 'check_can_live_migrate_source')
@mock.patch.object(compute, '_get_instance_block_device_info')
@mock.patch.object(compute.compute_api, 'is_volume_backed_instance')
def _test(mock_ivbi, mock_gibdi, mock_cclms):
mock_cclms.return_value = data
self.assertIsInstance(
compute.check_can_live_migrate_source(
self.context, {'uuid': 'foo'}, {}),
dict)

_test()

def test_check_migrate_destination_converts_object(self):
# NOTE(danms): Make sure that we legacy-ify any data objects
# the drivers give us back, until we're ready for them
data = migrate_data_obj.LiveMigrateData(is_volume_backed=False)
inst = objects.Instance(id=1, uuid='foo', host='bar')
compute = manager.ComputeManager()

@mock.patch.object(compute.driver,
'check_can_live_migrate_destination')
@mock.patch.object(compute.compute_rpcapi,
'check_can_live_migrate_source')
@mock.patch.object(compute, '_get_compute_info')
def _test(mock_gci, mock_cclms, mock_cclmd):
mock_gci.return_value = inst
mock_cclmd.return_value = data
mock_cclms.return_value = {}
result = compute.check_can_live_migrate_destination(
self.context, inst, False, False)
self.assertIsInstance(mock_cclms.call_args_list[0][0][2], dict)
self.assertIsInstance(result, dict)

_test()
53 changes: 53 additions & 0 deletions nova/tests/unit/objects/test_migrate_data.py
@@ -0,0 +1,53 @@
# Copyright 2015 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from nova import objects
from nova.objects import migrate_data
from nova.tests.unit.objects import test_objects


class _TestLiveMigrateData(object):
def test_to_legacy_dict(self):
obj = migrate_data.LiveMigrateData(is_volume_backed=False)
self.assertEqual({'is_volume_backed': False},
obj.to_legacy_dict())

def test_from_legacy_dict(self):
obj = migrate_data.LiveMigrateData()
obj.from_legacy_dict({'is_volume_backed': False, 'ignore': 'foo'})
self.assertEqual(False, obj.is_volume_backed)

def test_from_legacy_dict_migration(self):
migration = objects.Migration()
obj = migrate_data.LiveMigrateData()
obj.from_legacy_dict({'is_volume_backed': False, 'ignore': 'foo',
'migration': migration})
self.assertEqual(False, obj.is_volume_backed)
self.assertIsInstance(obj.migration, objects.Migration)

def test_legacy_with_pre_live_migration_result(self):
obj = migrate_data.LiveMigrateData(is_volume_backed=False)
self.assertEqual({'pre_live_migration_result': {},
'is_volume_backed': False},
obj.to_legacy_dict(pre_migration_result=True))


class TestLiveMigrateData(test_objects._LocalTest,
_TestLiveMigrateData):
pass


class TestRemoteLiveMigrateData(test_objects._RemoteTest,
_TestLiveMigrateData):
pass
3 changes: 2 additions & 1 deletion nova/virt/hyperv/livemigrationops.py
Expand Up @@ -24,6 +24,7 @@
from oslo_utils import excutils

from nova.i18n import _
from nova.objects import migrate_data as migrate_data_obj
from nova.virt.hyperv import imagecache
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vmops
Expand Down Expand Up @@ -116,7 +117,7 @@ def check_can_live_migrate_destination(self, ctxt, instance_ref,
block_migration=False,
disk_over_commit=False):
LOG.debug("check_can_live_migrate_destination called", instance_ref)
return {}
return migrate_data_obj.LiveMigrateData()

@check_os_version_requirement
def check_can_live_migrate_destination_cleanup(self, ctxt,
Expand Down

0 comments on commit 038dfd6

Please sign in to comment.