diff --git a/nova/objects/instance.py b/nova/objects/instance.py index 8d86be0e49..efc73b04d5 100644 --- a/nova/objects/instance.py +++ b/nova/objects/instance.py @@ -1043,8 +1043,29 @@ class InstanceV1(_BaseInstance): getattr(self, attrname), ftype) +@base.NovaObjectRegistry.register +class InstanceV2(_BaseInstance): + # Version 2.0: Initial version + VERSION = '2.0' + + def obj_make_compatible(self, primitive, target_version): + if target_version.startswith('1.'): + # NOTE(danms): Special case to backport to 1.x. Serialize + # ourselves, change the version, deserialize that, and get + # that to continue the backport of this primitive to + # whatever 1.x version was actually requested. We can get + # away with this because InstanceV2 is structurally a + # subset of V1. + # FIXME(danms): Remove this when we drop v1.x compatibility + my_prim = self.obj_to_primitive() + my_prim['nova_object.version'] = InstanceV1.VERSION + instv1 = InstanceV1.obj_from_primitive(my_prim) + return instv1.obj_make_compatible(primitive, target_version) + super(InstanceV2, self).obj_make_compatible(primitive, target_version) + + # NOTE(danms): For the unit tests... -Instance = InstanceV1 +Instance = InstanceV2 def _make_instance_list(context, inst_list, db_inst_list, expected_attrs): @@ -1270,5 +1291,21 @@ class InstanceListV1(_BaseInstanceList): } +@base.NovaObjectRegistry.register +class InstanceListV2(_BaseInstanceList): + # Version 2.0: Initial version + VERSION = '2.0' + + NOVA_OBJ_INSTANCE_CLS = InstanceV2 + + def obj_make_compatible(self, primitive, target_version): + if target_version.startswith('1.'): + my_prim = self.obj_to_primitive() + my_prim['nova_object.version'] = InstanceListV1.VERSION + instv1 = InstanceListV1.obj_from_primitive(my_prim) + return instv1.obj_make_compatible(primitive, target_version) + super(InstanceListV2, self).obj_make_compatible(primitive, + target_version) + # NOTE(danms): For the unit tests... -InstanceList = InstanceListV1 +InstanceList = InstanceListV2 diff --git a/nova/tests/unit/fake_instance.py b/nova/tests/unit/fake_instance.py index a675991e5b..e41540215e 100644 --- a/nova/tests/unit/fake_instance.py +++ b/nova/tests/unit/fake_instance.py @@ -99,7 +99,9 @@ def fake_db_instance(**updates): return db_instance -def fake_instance_obj(context, **updates): +def fake_instance_obj(context, obj_instance_class=None, **updates): + if obj_instance_class is None: + obj_instance_class = objects.Instance expected_attrs = updates.pop('expected_attrs', None) flavor = updates.pop('flavor', None) if not flavor: @@ -114,8 +116,8 @@ def fake_instance_obj(context, **updates): extra_specs={}, projects=[]) flavor.obj_reset_changes() - inst = objects.Instance._from_db_object(context, - objects.Instance(), fake_db_instance(**updates), + inst = obj_instance_class._from_db_object(context, + obj_instance_class(), fake_db_instance(**updates), expected_attrs=expected_attrs) if flavor: inst.flavor = flavor diff --git a/nova/tests/unit/objects/test_instance.py b/nova/tests/unit/objects/test_instance.py index 535c5efa12..f487237f7e 100644 --- a/nova/tests/unit/objects/test_instance.py +++ b/nova/tests/unit/objects/test_instance.py @@ -14,6 +14,7 @@ import datetime +import fixtures import mock from mox3 import mox import netaddr @@ -426,17 +427,6 @@ class _TestInstanceObject(object): inst.save() self.assertTrue(save_mock.called) - @mock.patch('nova.db.instance_update_and_get_original') - @mock.patch.object(instance._BaseInstance, '_from_db_object') - def test_save_skip_scheduled_at(self, mock_fdo, mock_update): - mock_update.return_value = None, None - inst = objects.Instance(context=self.context, id=123) - inst.uuid = 'foo' - inst.scheduled_at = None - inst.save() - self.assertNotIn('scheduled_at', - mock_update.call_args_list[0][0][2]) - @mock.patch('nova.db.instance_update_and_get_original') @mock.patch.object(instance._BaseInstance, '_from_db_object') def test_save_does_not_refresh_pci_devices(self, mock_fdo, mock_update): @@ -1049,40 +1039,6 @@ class _TestInstanceObject(object): expected_attrs=['info_cache']) self.assertIs(info_cache, inst.info_cache) - def test_compat_strings(self): - unicode_attributes = ['user_id', 'project_id', 'image_ref', - 'kernel_id', 'ramdisk_id', 'hostname', - 'key_name', 'key_data', 'host', 'node', - 'user_data', 'availability_zone', - 'display_name', 'display_description', - 'launched_on', 'locked_by', 'os_type', - 'architecture', 'vm_mode', 'root_device_name', - 'default_ephemeral_device', - 'default_swap_device', 'config_drive', - 'cell_name'] - inst = objects.Instance() - expected = {} - for key in unicode_attributes: - inst[key] = u'\u2603' - expected[key] = b'?' - primitive = inst.obj_to_primitive(target_version='1.6') - self.assertJsonEqual(expected, primitive['nova_object.data']) - self.assertEqual('1.6', primitive['nova_object.version']) - - def test_compat_pci_devices(self): - inst = objects.Instance() - inst.pci_devices = pci_device.PciDeviceList() - primitive = inst.obj_to_primitive(target_version='1.5') - self.assertNotIn('pci_devices', primitive) - - def test_compat_info_cache(self): - inst = objects.Instance() - inst.info_cache = instance_info_cache.InstanceInfoCache() - primitive = inst.obj_to_primitive(target_version='1.9') - self.assertEqual( - '1.4', - primitive['nova_object.data']['info_cache']['nova_object.version']) - @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid') def test_get_with_pci_requests(self, mock_get): mock_get.return_value = objects.InstancePCIRequests() @@ -1286,16 +1242,6 @@ class _TestInstanceObject(object): self.context, uuid, expected_attrs=['pci_requests']) self.assertTrue(inst.obj_attr_is_set('pci_requests')) - def test_backport_flavor(self): - flavor = flavors.get_default_flavor() - inst = objects.Instance(context=self.context, flavor=flavor, - system_metadata={'foo': 'bar'}, - new_flavor=None, - old_flavor=None) - primitive = inst.obj_to_primitive(target_version='1.17') - self.assertIn('instance_type_id', - primitive['nova_object.data']['system_metadata']) - class TestInstanceObject(test_objects._LocalTest, _TestInstanceObject): @@ -1325,7 +1271,95 @@ class TestInstanceObject(test_objects._LocalTest, class TestRemoteInstanceObject(test_objects._RemoteTest, _TestInstanceObject): - pass + def setUp(self): + super(TestRemoteInstanceObject, self).setUp() + self.useFixture(fixtures.MonkeyPatch('nova.objects.Instance', + instance.InstanceV2)) + + +class TestInstanceV1RemoteObject(test_objects._RemoteTest, + _TestInstanceObject): + def setUp(self): + super(TestInstanceV1RemoteObject, self).setUp() + self.useFixture(fixtures.MonkeyPatch('nova.objects.Instance', + instance.InstanceV1)) + + @mock.patch('nova.db.instance_update_and_get_original') + @mock.patch.object(instance._BaseInstance, '_from_db_object') + def test_save_skip_scheduled_at(self, mock_fdo, mock_update): + mock_update.return_value = None, None + inst = objects.Instance(context=self.context, id=123) + inst.uuid = 'foo' + inst.scheduled_at = None + inst.save() + self.assertNotIn('scheduled_at', + mock_update.call_args_list[0][0][2]) + + def test_backport_flavor(self): + flavor = flavors.get_default_flavor() + inst = objects.Instance(context=self.context, flavor=flavor, + system_metadata={'foo': 'bar'}, + new_flavor=None, + old_flavor=None) + primitive = inst.obj_to_primitive(target_version='1.17') + self.assertIn('instance_type_id', + primitive['nova_object.data']['system_metadata']) + + def test_compat_strings(self): + unicode_attributes = ['user_id', 'project_id', 'image_ref', + 'kernel_id', 'ramdisk_id', 'hostname', + 'key_name', 'key_data', 'host', 'node', + 'user_data', 'availability_zone', + 'display_name', 'display_description', + 'launched_on', 'locked_by', 'os_type', + 'architecture', 'vm_mode', 'root_device_name', + 'default_ephemeral_device', + 'default_swap_device', 'config_drive', + 'cell_name'] + inst = objects.Instance() + expected = {} + for key in unicode_attributes: + inst[key] = u'\u2603' + expected[key] = b'?' + primitive = inst.obj_to_primitive(target_version='1.6') + self.assertJsonEqual(expected, primitive['nova_object.data']) + self.assertEqual('1.6', primitive['nova_object.version']) + + def test_compat_pci_devices(self): + inst = objects.Instance() + inst.pci_devices = pci_device.PciDeviceList() + primitive = inst.obj_to_primitive(target_version='1.5') + self.assertNotIn('pci_devices', primitive) + + def test_compat_info_cache(self): + inst = objects.Instance() + inst.info_cache = instance_info_cache.InstanceInfoCache() + primitive = inst.obj_to_primitive(target_version='1.9') + self.assertEqual( + '1.4', + primitive['nova_object.data']['info_cache']['nova_object.version']) + + def test_backport_v2_to_v1(self): + inst2 = fake_instance.fake_instance_obj( + self.context, obj_instance_class=instance.InstanceV2) + inst1 = instance.InstanceV1.obj_from_primitive( + inst2.obj_to_primitive(target_version=instance.InstanceV1.VERSION)) + self.assertEqual(instance.InstanceV1.VERSION, inst1.VERSION) + self.assertIsInstance(inst1, instance.InstanceV1) + self.assertEqual(inst2.uuid, inst1.uuid) + + def test_backport_list_v2_to_v1(self): + inst2 = fake_instance.fake_instance_obj( + self.context, obj_instance_class=instance.InstanceV2) + list2 = instance.InstanceListV2(objects=[inst2]) + list1 = instance.InstanceListV1.obj_from_primitive( + list2.obj_to_primitive( + target_version=instance.InstanceListV1.VERSION)) + self.assertEqual(instance.InstanceListV1.VERSION, list1.VERSION) + self.assertEqual(instance.InstanceV1.VERSION, list1[0].VERSION) + self.assertIsInstance(list1, instance.InstanceListV1) + self.assertIsInstance(list1[0], instance.InstanceV1) + self.assertEqual(list2[0].uuid, list1[0].uuid) class _TestInstanceListObject(object): diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 7d40989853..5f2d61f34d 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1186,7 +1186,8 @@ object_data = { 'HVSpec': '1.1-6b4f7c0f688cbd03e24142a44eb9010d', 'ImageMeta': '1.6-642d1b2eb3e880a367f37d72dd76162d', 'ImageMetaProps': '1.6-07a6d9f3576c4927220331584661ce45', - 'Instance': '1.23-4e68422207667f4abff5fa730a5edc98', + 'Instance': '2.0-ff56804dce87d81d9a04834d4bd1e3d2', + 'Instance1': '1.23-4e68422207667f4abff5fa730a5edc98', 'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914', 'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33', 'InstanceActionEventList': '1.1-13d92fb953030cdbfee56481756e02be', @@ -1197,7 +1198,8 @@ object_data = { 'InstanceGroup': '1.10-1a0c8c7447dc7ecb9da53849430c4a5f', 'InstanceGroupList': '1.7-be18078220513316abd0ae1b2d916873', 'InstanceInfoCache': '1.5-cd8b96fefe0fc8d4d337243ba0bf0e1e', - 'InstanceList': '1.22-6c8ba6147cca3082b1e4643f795068bf', + 'InstanceList': '2.0-6c8ba6147cca3082b1e4643f795068bf', + 'InstanceList1': '1.22-6c8ba6147cca3082b1e4643f795068bf', 'InstanceMapping': '1.0-47ef26034dfcbea78427565d9177fe50', 'InstanceMappingList': '1.0-9e982e3de1613b9ada85e35f69b23d47', 'InstanceNUMACell': '1.2-535ef30e0de2d6a0d26a71bd58ecafc4', @@ -1380,7 +1382,7 @@ class TestObjectVersions(test.NoDBTestCase): # a 2.0 version while calculating the old-style relationship # mapping. Once we drop all the 1.x versions, we can drop this # relationship test altogether. - new_objects = [] + new_objects = ['Instance', 'InstanceList'] versions = base.NovaObjectRegistry.obj_classes()[name] if len(versions) > 1 and name in new_objects: