From 713d8cb0777afb9fe4f665b9a40cac894b04aacb Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 27 Aug 2015 09:36:31 -0700 Subject: [PATCH] Add Instance and InstanceList v2.0 objects This adds Instance and InstanceList v2.0 and moves v1 compat-related tests into a separate test subclass. Note that since services like api and conductor will be sending v2 objects to older computes, this also adds a backport-to-1.x case to each object for compatibility with kilo computes. Hopefully in the future we'll be able to avoid needing to do that by capping object verisons according to the service version. For now, the structure of instance hasn't diverged enough so this easy approach works. Related to blueprint liberty-bump-object-and-rpcapi-versions Change-Id: Icfd2529962c0430761d2424069f0fd60a1b5c260 --- nova/objects/instance.py | 41 ++++++- nova/tests/unit/fake_instance.py | 8 +- nova/tests/unit/objects/test_instance.py | 146 ++++++++++++++--------- nova/tests/unit/objects/test_objects.py | 8 +- 4 files changed, 139 insertions(+), 64 deletions(-) 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: