From 038dfd672f5b2be5ebe30d85bd00d09bae2993fc Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 19 Nov 2015 09:38:09 -0800 Subject: [PATCH] Add transitional support for migrate data objects to compute manager 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 --- nova/compute/manager.py | 15 ++++-- nova/objects/migrate_data.py | 41 +++++++++++++++ nova/tests/unit/compute/test_compute_mgr.py | 42 ++++++++++++++++ nova/tests/unit/objects/test_migrate_data.py | 53 ++++++++++++++++++++ nova/virt/hyperv/livemigrationops.py | 3 +- 5 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 nova/objects/migrate_data.py create mode 100644 nova/tests/unit/objects/test_migrate_data.py diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 161810867ce..779f42793b8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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 @@ -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.\ @@ -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 @@ -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, diff --git a/nova/objects/migrate_data.py b/nova/objects/migrate_data.py new file mode 100644 index 00000000000..2f89f7f26c5 --- /dev/null +++ b/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'] diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 6a7155bc237..8c162faf647 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -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 @@ -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() diff --git a/nova/tests/unit/objects/test_migrate_data.py b/nova/tests/unit/objects/test_migrate_data.py new file mode 100644 index 00000000000..687c57cefbe --- /dev/null +++ b/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 diff --git a/nova/virt/hyperv/livemigrationops.py b/nova/virt/hyperv/livemigrationops.py index 2c5b678ab89..4dc42f40e7f 100644 --- a/nova/virt/hyperv/livemigrationops.py +++ b/nova/virt/hyperv/livemigrationops.py @@ -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 @@ -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,