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
This commit is contained in:
Dan Smith 2015-08-27 09:36:31 -07:00
parent 452053e8c7
commit 713d8cb077
4 changed files with 139 additions and 64 deletions

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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: