Skip to content

Commit

Permalink
Add Instance and InstanceList v2.0 objects
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kk7ds committed Sep 4, 2015
1 parent 452053e commit 713d8cb
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 64 deletions.
41 changes: 39 additions & 2 deletions nova/objects/instance.py
Expand Up @@ -1043,8 +1043,29 @@ def obj_make_compatible(self, primitive, target_version):
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):
Expand Down Expand Up @@ -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
8 changes: 5 additions & 3 deletions nova/tests/unit/fake_instance.py
Expand Up @@ -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:
Expand All @@ -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
Expand Down
146 changes: 90 additions & 56 deletions nova/tests/unit/objects/test_instance.py
Expand Up @@ -14,6 +14,7 @@

import datetime

import fixtures
import mock
from mox3 import mox
import netaddr
Expand Down Expand Up @@ -426,17 +427,6 @@ def test_save_related_object_if_none(self):
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):
Expand Down Expand Up @@ -1049,40 +1039,6 @@ def test_from_db_object_not_overwrite_info_cache(self):
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()
Expand Down Expand Up @@ -1286,16 +1242,6 @@ def test_get_with_extras(self):
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):
Expand Down Expand Up @@ -1325,7 +1271,95 @@ def _test(mock_is_set, mock_save_field):

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):
Expand Down
8 changes: 5 additions & 3 deletions nova/tests/unit/objects/test_objects.py
Expand Up @@ -1186,7 +1186,8 @@ def obj_name(cls):
'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',
Expand All @@ -1197,7 +1198,8 @@ def obj_name(cls):
'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',
Expand Down Expand Up @@ -1380,7 +1382,7 @@ def _get_obj_cls(self, name):
# 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:
Expand Down

0 comments on commit 713d8cb

Please sign in to comment.