1# Copyright 2013 IBM Corp. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15import copy 16import datetime 17import jsonschema 18import logging 19import pytz 20from unittest import mock 21 22from oslo_context import context 23from oslo_serialization import jsonutils 24from oslo_utils import timeutils 25import testtools 26from testtools import matchers 27 28from oslo_versionedobjects import base 29from oslo_versionedobjects import exception 30from oslo_versionedobjects import fields 31from oslo_versionedobjects import fixture 32from oslo_versionedobjects import test 33 34 35LOG = logging.getLogger(__name__) 36 37 38def is_test_object(cls): 39 """Return True if class is defined in the tests. 40 41 :param cls: Class to inspect 42 """ 43 return 'oslo_versionedobjects.tests' in cls.__module__ 44 45 46@base.VersionedObjectRegistry.register 47class MyOwnedObject(base.VersionedObject): 48 VERSION = '1.0' 49 fields = {'baz': fields.Field(fields.Integer())} 50 51 52@base.VersionedObjectRegistry.register 53class MyObj(base.VersionedObject, base.VersionedObjectDictCompat): 54 VERSION = '1.6' 55 fields = {'foo': fields.Field(fields.Integer(), default=1), 56 'bar': fields.Field(fields.String()), 57 'missing': fields.Field(fields.String()), 58 'readonly': fields.Field(fields.Integer(), read_only=True), 59 'rel_object': fields.ObjectField('MyOwnedObject', nullable=True), 60 'rel_objects': fields.ListOfObjectsField('MyOwnedObject', 61 nullable=True), 62 'mutable_default': fields.ListOfStringsField(default=[]), 63 'timestamp': fields.DateTimeField(nullable=True), 64 } 65 66 @staticmethod 67 def _from_db_object(context, obj, db_obj): 68 self = MyObj() 69 self.foo = db_obj['foo'] 70 self.bar = db_obj['bar'] 71 self.missing = db_obj['missing'] 72 self.readonly = 1 73 return self 74 75 def obj_load_attr(self, attrname): 76 setattr(self, attrname, 'loaded!') 77 78 @base.remotable_classmethod 79 def query(cls, context): 80 obj = cls(context=context, foo=1, bar='bar') 81 obj.obj_reset_changes() 82 return obj 83 84 @base.remotable 85 def marco(self): 86 return 'polo' 87 88 @base.remotable 89 def _update_test(self): 90 project_id = getattr(context, 'tenant', None) 91 if project_id is None: 92 project_id = getattr(context, 'project_id', None) 93 if project_id == 'alternate': 94 self.bar = 'alternate-context' 95 else: 96 self.bar = 'updated' 97 98 @base.remotable 99 def save(self): 100 self.obj_reset_changes() 101 102 @base.remotable 103 def refresh(self): 104 self.foo = 321 105 self.bar = 'refreshed' 106 self.obj_reset_changes() 107 108 @base.remotable 109 def modify_save_modify(self): 110 self.bar = 'meow' 111 self.save() 112 self.foo = 42 113 self.rel_object = MyOwnedObject(baz=42) 114 115 def obj_make_compatible(self, primitive, target_version): 116 super(MyObj, self).obj_make_compatible(primitive, target_version) 117 # NOTE(danms): Simulate an older version that had a different 118 # format for the 'bar' attribute 119 if target_version == '1.1' and 'bar' in primitive: 120 primitive['bar'] = 'old%s' % primitive['bar'] 121 122 123@base.VersionedObjectRegistry.register 124class MyComparableObj(MyObj, base.ComparableVersionedObject): 125 pass 126 127 128@base.VersionedObjectRegistry.register 129class MyObjDiffVers(MyObj): 130 VERSION = '1.5' 131 132 @classmethod 133 def obj_name(cls): 134 return 'MyObj' 135 136 137@base.VersionedObjectRegistry.register_if(False) 138class MyObj2(base.VersionedObject): 139 @classmethod 140 def obj_name(cls): 141 return 'MyObj' 142 143 @base.remotable_classmethod 144 def query(cls, *args, **kwargs): 145 pass 146 147 148@base.VersionedObjectRegistry.register_if(False) 149class MySensitiveObj(base.VersionedObject): 150 VERSION = '1.0' 151 fields = { 152 'data': fields.SensitiveStringField(nullable=True) 153 } 154 155 @base.remotable_classmethod 156 def query(cls, *args, **kwargs): 157 pass 158 159 160class RandomMixInWithNoFields(object): 161 """Used to test object inheritance using a mixin that has no fields.""" 162 pass 163 164 165@base.VersionedObjectRegistry.register 166class TestSubclassedObject(RandomMixInWithNoFields, MyObj): 167 fields = {'new_field': fields.Field(fields.String())} 168 child_versions = { 169 '1.0': '1.0', 170 '1.1': '1.1', 171 '1.2': '1.1', 172 '1.3': '1.2', 173 '1.4': '1.3', 174 '1.5': '1.4', 175 '1.6': '1.5', 176 '1.7': '1.6', 177 } 178 179 180@base.VersionedObjectRegistry.register 181class MyCompoundObject(base.VersionedObject): 182 fields = { 183 "foo": fields.Field(fields.List(fields.Integer())), 184 "bar": fields.Field(fields.Dict(fields.Integer())), 185 "baz": fields.Field(fields.Set(fields.Integer())) 186 } 187 188 189class TestRegistry(test.TestCase): 190 def test_obj_tracking(self): 191 192 @base.VersionedObjectRegistry.register 193 class NewBaseClass(object): 194 VERSION = '1.0' 195 fields = {} 196 197 @classmethod 198 def obj_name(cls): 199 return cls.__name__ 200 201 @base.VersionedObjectRegistry.register 202 class Fake1TestObj1(NewBaseClass): 203 @classmethod 204 def obj_name(cls): 205 return 'fake1' 206 207 @base.VersionedObjectRegistry.register 208 class Fake1TestObj2(Fake1TestObj1): 209 pass 210 211 @base.VersionedObjectRegistry.register 212 class Fake1TestObj3(Fake1TestObj1): 213 VERSION = '1.1' 214 215 @base.VersionedObjectRegistry.register 216 class Fake2TestObj1(NewBaseClass): 217 @classmethod 218 def obj_name(cls): 219 return 'fake2' 220 221 @base.VersionedObjectRegistry.register 222 class Fake1TestObj4(Fake1TestObj3): 223 VERSION = '1.2' 224 225 @base.VersionedObjectRegistry.register 226 class Fake2TestObj2(Fake2TestObj1): 227 VERSION = '1.1' 228 229 @base.VersionedObjectRegistry.register 230 class Fake1TestObj5(Fake1TestObj1): 231 VERSION = '1.1' 232 233 @base.VersionedObjectRegistry.register_if(False) 234 class ConditionalObj1(NewBaseClass): 235 fields = {'foo': fields.IntegerField()} 236 237 @base.VersionedObjectRegistry.register_if(True) 238 class ConditionalObj2(NewBaseClass): 239 fields = {'foo': fields.IntegerField()} 240 241 # Newest versions first in the list. Duplicate versions take the 242 # newest object. 243 expected = {'fake1': [Fake1TestObj4, Fake1TestObj5, Fake1TestObj2], 244 'fake2': [Fake2TestObj2, Fake2TestObj1]} 245 self.assertEqual(expected['fake1'], 246 base.VersionedObjectRegistry.obj_classes()['fake1']) 247 self.assertEqual(expected['fake2'], 248 base.VersionedObjectRegistry.obj_classes()['fake2']) 249 self.assertEqual( 250 [], 251 base.VersionedObjectRegistry.obj_classes()['ConditionalObj1']) 252 self.assertTrue(hasattr(ConditionalObj1, 'foo')) 253 self.assertEqual( 254 [ConditionalObj2], 255 base.VersionedObjectRegistry.obj_classes()['ConditionalObj2']) 256 self.assertTrue(hasattr(ConditionalObj2, 'foo')) 257 258 def test_field_checking(self): 259 def create_class(field): 260 @base.VersionedObjectRegistry.register 261 class TestField(base.VersionedObject): 262 VERSION = '1.5' 263 fields = {'foo': field()} 264 return TestField 265 266 create_class(fields.DateTimeField) 267 self.assertRaises(exception.ObjectFieldInvalid, 268 create_class, fields.DateTime) 269 self.assertRaises(exception.ObjectFieldInvalid, 270 create_class, int) 271 272 def test_registration_hook(self): 273 class TestObject(base.VersionedObject): 274 VERSION = '1.0' 275 276 class TestObjectNewer(base.VersionedObject): 277 VERSION = '1.1' 278 279 @classmethod 280 def obj_name(cls): 281 return 'TestObject' 282 283 registry = base.VersionedObjectRegistry() 284 with mock.patch.object(registry, 'registration_hook') as mock_hook: 285 registry._register_class(TestObject) 286 mock_hook.assert_called_once_with(TestObject, 0) 287 288 with mock.patch.object(registry, 'registration_hook') as mock_hook: 289 registry._register_class(TestObjectNewer) 290 mock_hook.assert_called_once_with(TestObjectNewer, 0) 291 292 def test_subclassability(self): 293 class MyRegistryOne(base.VersionedObjectRegistry): 294 295 def registration_hook(self, cls, index): 296 cls.reg_to = "one" 297 298 class MyRegistryTwo(base.VersionedObjectRegistry): 299 300 def registration_hook(self, cls, index): 301 cls.reg_to = "two" 302 303 @MyRegistryOne.register 304 class AVersionedObject1(base.VersionedObject): 305 VERSION = '1.0' 306 fields = {'baz': fields.Field(fields.Integer())} 307 308 @MyRegistryTwo.register 309 class AVersionedObject2(base.VersionedObject): 310 VERSION = '1.0' 311 fields = {'baz': fields.Field(fields.Integer())} 312 313 self.assertIn('AVersionedObject1', 314 MyRegistryOne.obj_classes()) 315 self.assertIn('AVersionedObject2', 316 MyRegistryOne.obj_classes()) 317 self.assertIn('AVersionedObject1', 318 MyRegistryTwo.obj_classes()) 319 self.assertIn('AVersionedObject2', 320 MyRegistryTwo.obj_classes()) 321 self.assertIn('AVersionedObject1', 322 base.VersionedObjectRegistry.obj_classes()) 323 self.assertIn('AVersionedObject2', 324 base.VersionedObjectRegistry.obj_classes()) 325 self.assertEqual(AVersionedObject1.reg_to, "one") 326 self.assertEqual(AVersionedObject2.reg_to, "two") 327 328 @mock.patch.object(base.VersionedObjectRegistry, '__new__') 329 def test_register(self, mock_registry): 330 mock_reg_obj = mock.Mock() 331 mock_registry.return_value = mock_reg_obj 332 mock_reg_obj._register_class = mock.Mock() 333 334 class my_class(object): 335 pass 336 337 base.VersionedObjectRegistry.register(my_class) 338 mock_reg_obj._register_class.assert_called_once_with(my_class) 339 340 @mock.patch.object(base.VersionedObjectRegistry, 'register') 341 def test_register_if(self, mock_register): 342 class my_class(object): 343 pass 344 345 base.VersionedObjectRegistry.register_if(True)(my_class) 346 mock_register.assert_called_once_with(my_class) 347 348 @mock.patch.object(base, '_make_class_properties') 349 def test_register_if_false(self, mock_make_props): 350 class my_class(object): 351 pass 352 353 base.VersionedObjectRegistry.register_if(False)(my_class) 354 mock_make_props.assert_called_once_with(my_class) 355 356 @mock.patch.object(base.VersionedObjectRegistry, 'register_if') 357 def test_objectify(self, mock_register_if): 358 mock_reg_callable = mock.Mock() 359 mock_register_if.return_value = mock_reg_callable 360 361 class my_class(object): 362 pass 363 364 base.VersionedObjectRegistry.objectify(my_class) 365 366 mock_register_if.assert_called_once_with(False) 367 mock_reg_callable.assert_called_once_with(my_class) 368 369 370class TestObjMakeList(test.TestCase): 371 372 def test_obj_make_list(self): 373 @base.VersionedObjectRegistry.register 374 class MyList(base.ObjectListBase, base.VersionedObject): 375 fields = { 376 'objects': fields.ListOfObjectsField('MyObj'), 377 } 378 379 db_objs = [{'foo': 1, 'bar': 'baz', 'missing': 'banana'}, 380 {'foo': 2, 'bar': 'bat', 'missing': 'apple'}, 381 ] 382 mylist = base.obj_make_list('ctxt', MyList(), MyObj, db_objs) 383 self.assertEqual(2, len(mylist)) 384 self.assertEqual('ctxt', mylist._context) 385 for index, item in enumerate(mylist): 386 self.assertEqual(db_objs[index]['foo'], item.foo) 387 self.assertEqual(db_objs[index]['bar'], item.bar) 388 self.assertEqual(db_objs[index]['missing'], item.missing) 389 390 391class TestGetSubobjectVersion(test.TestCase): 392 def setUp(self): 393 super(TestGetSubobjectVersion, self).setUp() 394 self.backport_mock = mock.MagicMock() 395 self.rels = [('1.1', '1.0'), ('1.3', '1.1')] 396 397 def test_get_subobject_version_not_existing(self): 398 # Verify that exception is raised if we try backporting 399 # to a version where we did not contain the subobject 400 self.assertRaises(exception.TargetBeforeSubobjectExistedException, 401 base._get_subobject_version, '1.0', self.rels, 402 self.backport_mock) 403 404 def test_get_subobject_version_explicit_version(self): 405 # Verify that we backport to the correct subobject version when the 406 # version we are going back to is explicitly said in the relationships 407 base._get_subobject_version('1.3', self.rels, self.backport_mock) 408 self.backport_mock.assert_called_once_with('1.1') 409 410 def test_get_subobject_version_implicit_version(self): 411 # Verify that we backport to the correct subobject version when the 412 # version backporting to is not explicitly stated in the relationships 413 base._get_subobject_version('1.2', self.rels, self.backport_mock) 414 self.backport_mock.assert_called_once_with('1.0') 415 416 417class TestDoSubobjectBackport(test.TestCase): 418 @base.VersionedObjectRegistry.register 419 class ParentObj(base.VersionedObject): 420 VERSION = '1.1' 421 fields = {'child': fields.ObjectField('ChildObj', nullable=True)} 422 obj_relationships = {'child': [('1.0', '1.0'), ('1.1', '1.1')]} 423 424 @base.VersionedObjectRegistry.register 425 class ParentObjList(base.VersionedObject, base.ObjectListBase): 426 VERSION = '1.1' 427 fields = {'objects': fields.ListOfObjectsField('ChildObj')} 428 obj_relationships = {'objects': [('1.0', '1.0'), ('1.1', '1.1')]} 429 430 @base.VersionedObjectRegistry.register 431 class ChildObj(base.VersionedObject): 432 VERSION = '1.1' 433 fields = {'foo': fields.IntegerField()} 434 435 def test_do_subobject_backport_without_manifest(self): 436 child = self.ChildObj(foo=1) 437 parent = self.ParentObj(child=child) 438 parent_primitive = parent.obj_to_primitive()['versioned_object.data'] 439 primitive = child.obj_to_primitive()['versioned_object.data'] 440 version = '1.0' 441 442 compat_func = 'obj_make_compatible_from_manifest' 443 with mock.patch.object(child, compat_func) as mock_compat: 444 base._do_subobject_backport(version, parent, 'child', 445 parent_primitive) 446 mock_compat.assert_called_once_with(primitive, 447 version, 448 version_manifest=None) 449 450 def test_do_subobject_backport_with_manifest(self): 451 child = self.ChildObj(foo=1) 452 parent = self.ParentObj(child=child) 453 parent_primitive = parent.obj_to_primitive()['versioned_object.data'] 454 primitive = child.obj_to_primitive()['versioned_object.data'] 455 version = '1.0' 456 manifest = {'ChildObj': '1.0'} 457 parent._obj_version_manifest = manifest 458 459 compat_func = 'obj_make_compatible_from_manifest' 460 with mock.patch.object(child, compat_func) as mock_compat: 461 base._do_subobject_backport(version, parent, 'child', 462 parent_primitive) 463 mock_compat.assert_called_once_with(primitive, 464 version, 465 version_manifest=manifest) 466 467 def test_do_subobject_backport_with_manifest_old_parent(self): 468 child = self.ChildObj(foo=1) 469 parent = self.ParentObj(child=child) 470 manifest = {'ChildObj': '1.0'} 471 parent_primitive = parent.obj_to_primitive(target_version='1.1', 472 version_manifest=manifest) 473 child_primitive = parent_primitive['versioned_object.data']['child'] 474 self.assertEqual('1.0', child_primitive['versioned_object.version']) 475 476 def test_do_subobject_backport_list_object(self): 477 child = self.ChildObj(foo=1) 478 parent = self.ParentObjList(objects=[child]) 479 parent_primitive = parent.obj_to_primitive()['versioned_object.data'] 480 primitive = child.obj_to_primitive()['versioned_object.data'] 481 version = '1.0' 482 483 compat_func = 'obj_make_compatible_from_manifest' 484 with mock.patch.object(child, compat_func) as mock_compat: 485 base._do_subobject_backport(version, parent, 'objects', 486 parent_primitive) 487 mock_compat.assert_called_once_with(primitive, 488 version, 489 version_manifest=None) 490 491 def test_do_subobject_backport_list_object_with_manifest(self): 492 child = self.ChildObj(foo=1) 493 parent = self.ParentObjList(objects=[child]) 494 manifest = {'ChildObj': '1.0', 'ParentObjList': '1.0'} 495 parent_primitive = parent.obj_to_primitive(target_version='1.0', 496 version_manifest=manifest) 497 self.assertEqual('1.0', parent_primitive['versioned_object.version']) 498 child_primitive = parent_primitive['versioned_object.data']['objects'] 499 self.assertEqual('1.0', child_primitive[0]['versioned_object.version']) 500 501 def test_do_subobject_backport_null_child(self): 502 parent = self.ParentObj(child=None) 503 parent_primitive = parent.obj_to_primitive()['versioned_object.data'] 504 version = '1.0' 505 506 compat_func = 'obj_make_compatible_from_manifest' 507 with mock.patch.object(self.ChildObj, compat_func) as mock_compat: 508 base._do_subobject_backport(version, parent, 'child', 509 parent_primitive) 510 self.assertFalse(mock_compat.called, 511 "obj_make_compatible_from_manifest() should not " 512 "have been called because the subobject is " 513 "None.") 514 515 def test_to_primitive_calls_make_compatible_manifest(self): 516 obj = self.ParentObj() 517 with mock.patch.object(obj, 'obj_make_compatible_from_manifest') as m: 518 obj.obj_to_primitive(target_version='1.0', 519 version_manifest=mock.sentinel.manifest) 520 m.assert_called_once_with(mock.ANY, '1.0', mock.sentinel.manifest) 521 522 523class _BaseTestCase(test.TestCase): 524 def setUp(self): 525 super(_BaseTestCase, self).setUp() 526 self.user_id = 'fake-user' 527 self.project_id = 'fake-project' 528 self.context = context.RequestContext(self.user_id, self.project_id) 529 530 def json_comparator(self, expected, obj_val): 531 # json-ify an object field for comparison with its db str 532 # equivalent 533 self.assertEqual(expected, jsonutils.dumps(obj_val)) 534 535 def str_comparator(self, expected, obj_val): 536 """Compare a field to a string value 537 538 Compare an object field to a string in the db by performing 539 a simple coercion on the object field value. 540 """ 541 self.assertEqual(expected, str(obj_val)) 542 543 def assertNotIsInstance(self, obj, cls, msg=None): 544 """Python < v2.7 compatibility. Assert 'not isinstance(obj, cls).""" 545 try: 546 f = super(_BaseTestCase, self).assertNotIsInstance 547 except AttributeError: 548 self.assertThat(obj, 549 matchers.Not(matchers.IsInstance(cls)), 550 message=msg or '') 551 else: 552 f(obj, cls, msg=msg) 553 554 555class TestFixture(_BaseTestCase): 556 def test_fake_indirection_takes_serializer(self): 557 ser = mock.MagicMock() 558 iapi = fixture.FakeIndirectionAPI(ser) 559 ser.serialize_entity.return_value = mock.sentinel.serial 560 iapi.object_action(mock.sentinel.context, mock.sentinel.objinst, 561 mock.sentinel.objmethod, (), {}) 562 ser.serialize_entity.assert_called_once_with(mock.sentinel.context, 563 mock.sentinel.objinst) 564 ser.deserialize_entity.assert_called_once_with(mock.sentinel.context, 565 mock.sentinel.serial) 566 567 def test_indirection_fixture_takes_indirection_api(self): 568 iapi = mock.sentinel.iapi 569 self.useFixture(fixture.IndirectionFixture(iapi)) 570 self.assertEqual(iapi, base.VersionedObject.indirection_api) 571 572 def test_indirection_action(self): 573 self.useFixture(fixture.IndirectionFixture()) 574 obj = MyObj(context=self.context) 575 with mock.patch.object(base.VersionedObject.indirection_api, 576 'object_action') as mock_action: 577 mock_action.return_value = ({}, 'foo') 578 obj.marco() 579 mock_action.assert_called_once_with(self.context, 580 obj, 'marco', 581 (), {}) 582 583 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 584 def test_indirection_class_action(self, mock_otgv): 585 mock_otgv.return_value = mock.sentinel.versions 586 self.useFixture(fixture.IndirectionFixture()) 587 with mock.patch.object(base.VersionedObject.indirection_api, 588 'object_class_action_versions') as mock_caction: 589 mock_caction.return_value = 'foo' 590 MyObj.query(self.context) 591 mock_caction.assert_called_once_with(self.context, 592 'MyObj', 'query', 593 mock.sentinel.versions, 594 (), {}) 595 596 def test_fake_indirection_serializes_arguments(self): 597 ser = mock.MagicMock() 598 iapi = fixture.FakeIndirectionAPI(serializer=ser) 599 arg1 = mock.MagicMock() 600 arg2 = mock.MagicMock() 601 iapi.object_action(mock.sentinel.context, mock.sentinel.objinst, 602 mock.sentinel.objmethod, (arg1,), {'foo': arg2}) 603 ser.serialize_entity.assert_any_call(mock.sentinel.context, arg1) 604 ser.serialize_entity.assert_any_call(mock.sentinel.context, arg2) 605 606 def test_get_hashes(self): 607 checker = fixture.ObjectVersionChecker() 608 hashes = checker.get_hashes() 609 # NOTE(danms): If this object's version or hash changes, this needs 610 # to change. Otherwise, leave it alone. 611 self.assertEqual('1.6-fb5f5379168bf08f7f2ce0a745e91027', 612 hashes['TestSubclassedObject']) 613 614 def test_test_hashes(self): 615 checker = fixture.ObjectVersionChecker() 616 hashes = checker.get_hashes() 617 actual_hash = hashes['TestSubclassedObject'] 618 hashes['TestSubclassedObject'] = 'foo' 619 expected, actual = checker.test_hashes(hashes) 620 self.assertEqual(['TestSubclassedObject'], list(expected.keys())) 621 self.assertEqual(['TestSubclassedObject'], list(actual.keys())) 622 self.assertEqual('foo', expected['TestSubclassedObject']) 623 self.assertEqual(actual_hash, actual['TestSubclassedObject']) 624 625 def test_get_dependency_tree(self): 626 checker = fixture.ObjectVersionChecker() 627 tree = checker.get_dependency_tree() 628 629 # NOTE(danms): If this object's dependencies change, this n eeds 630 # to change. Otherwise, leave it alone. 631 self.assertEqual({'MyOwnedObject': '1.0'}, 632 tree['TestSubclassedObject']) 633 634 def test_test_relationships(self): 635 checker = fixture.ObjectVersionChecker() 636 tree = checker.get_dependency_tree() 637 actual = tree['TestSubclassedObject'] 638 tree['TestSubclassedObject']['Foo'] = '9.8' 639 expected, actual = checker.test_relationships(tree) 640 self.assertEqual(['TestSubclassedObject'], list(expected.keys())) 641 self.assertEqual(['TestSubclassedObject'], list(actual.keys())) 642 self.assertEqual({'MyOwnedObject': '1.0', 643 'Foo': '9.8'}, 644 expected['TestSubclassedObject']) 645 self.assertEqual({'MyOwnedObject': '1.0'}, 646 actual['TestSubclassedObject']) 647 648 def test_test_compatibility(self): 649 fake_classes = {mock.sentinel.class_one: [mock.sentinel.impl_one_one, 650 mock.sentinel.impl_one_two], 651 mock.sentinel.class_two: [mock.sentinel.impl_two_one, 652 mock.sentinel.impl_two_two], 653 } 654 checker = fixture.ObjectVersionChecker(fake_classes) 655 656 @mock.patch.object(checker, '_test_object_compatibility') 657 def test(mock_compat): 658 checker.test_compatibility_routines() 659 mock_compat.assert_has_calls( 660 [mock.call(mock.sentinel.impl_one_one, manifest=None, 661 init_args=[], init_kwargs={}), 662 mock.call(mock.sentinel.impl_one_two, manifest=None, 663 init_args=[], init_kwargs={}), 664 mock.call(mock.sentinel.impl_two_one, manifest=None, 665 init_args=[], init_kwargs={}), 666 mock.call(mock.sentinel.impl_two_two, manifest=None, 667 init_args=[], init_kwargs={})], 668 any_order=True) 669 test() 670 671 def test_test_compatibility_checks_obj_to_primitive(self): 672 fake = mock.MagicMock() 673 fake.VERSION = '1.3' 674 675 checker = fixture.ObjectVersionChecker() 676 checker._test_object_compatibility(fake) 677 fake().obj_to_primitive.assert_has_calls( 678 [mock.call(target_version='1.0'), 679 mock.call(target_version='1.1'), 680 mock.call(target_version='1.2'), 681 mock.call(target_version='1.3')]) 682 683 def test_test_relationships_in_order(self): 684 fake_classes = {mock.sentinel.class_one: [mock.sentinel.impl_one_one, 685 mock.sentinel.impl_one_two], 686 mock.sentinel.class_two: [mock.sentinel.impl_two_one, 687 mock.sentinel.impl_two_two], 688 } 689 checker = fixture.ObjectVersionChecker(fake_classes) 690 691 @mock.patch.object(checker, '_test_relationships_in_order') 692 def test(mock_compat): 693 checker.test_relationships_in_order() 694 mock_compat.assert_has_calls( 695 [mock.call(mock.sentinel.impl_one_one), 696 mock.call(mock.sentinel.impl_one_two), 697 mock.call(mock.sentinel.impl_two_one), 698 mock.call(mock.sentinel.impl_two_two)], 699 any_order=True) 700 test() 701 702 def test_test_relationships_in_order_good(self): 703 fake = mock.MagicMock() 704 fake.VERSION = '1.5' 705 fake.fields = {'foo': fields.ObjectField('bar')} 706 fake.obj_relationships = {'foo': [('1.2', '1.0'), 707 ('1.3', '1.2')]} 708 709 checker = fixture.ObjectVersionChecker() 710 checker._test_relationships_in_order(fake) 711 712 def _test_test_relationships_in_order_bad(self, fake_rels): 713 fake = mock.MagicMock() 714 fake.VERSION = '1.5' 715 fake.fields = {'foo': fields.ObjectField('bar')} 716 fake.obj_relationships = fake_rels 717 checker = fixture.ObjectVersionChecker() 718 self.assertRaises(AssertionError, 719 checker._test_relationships_in_order, fake) 720 721 def test_test_relationships_in_order_bad_my_version(self): 722 self._test_test_relationships_in_order_bad( 723 {'foo': [('1.4', '1.1'), ('1.3', '1.2')]}) 724 725 def test_test_relationships_in_order_bad_child_version(self): 726 self._test_test_relationships_in_order_bad( 727 {'foo': [('1.2', '1.3'), ('1.3', '1.2')]}) 728 729 def test_test_relationships_in_order_bad_both_versions(self): 730 self._test_test_relationships_in_order_bad( 731 {'foo': [('1.5', '1.4'), ('1.3', '1.2')]}) 732 733 734class _LocalTest(_BaseTestCase): 735 def setUp(self): 736 super(_LocalTest, self).setUp() 737 self.assertIsNone(base.VersionedObject.indirection_api) 738 739 740class _RemoteTest(_BaseTestCase): 741 def setUp(self): 742 super(_RemoteTest, self).setUp() 743 self.useFixture(fixture.IndirectionFixture()) 744 745 746class _TestObject(object): 747 # def test_object_attrs_in_init(self): 748 # # Spot check a few 749 # objects.Instance 750 # objects.InstanceInfoCache 751 # objects.SecurityGroup 752 # # Now check the test one in this file. Should be newest version 753 # self.assertEqual('1.6', objects.MyObj.VERSION) 754 755 def test_hydration_type_error(self): 756 primitive = {'versioned_object.name': 'MyObj', 757 'versioned_object.namespace': 'versionedobjects', 758 'versioned_object.version': '1.5', 759 'versioned_object.data': {'foo': 'a'}} 760 self.assertRaises(ValueError, MyObj.obj_from_primitive, primitive) 761 762 def test_hydration(self): 763 primitive = {'versioned_object.name': 'MyObj', 764 'versioned_object.namespace': 'versionedobjects', 765 'versioned_object.version': '1.5', 766 'versioned_object.data': {'foo': 1}} 767 real_method = MyObj._obj_from_primitive 768 769 def _obj_from_primitive(*args): 770 return real_method(*args) 771 772 with mock.patch.object(MyObj, '_obj_from_primitive') as ofp: 773 ofp.side_effect = _obj_from_primitive 774 obj = MyObj.obj_from_primitive(primitive) 775 ofp.assert_called_once_with(None, '1.5', primitive) 776 self.assertEqual(obj.foo, 1) 777 778 def test_hydration_version_different(self): 779 primitive = {'versioned_object.name': 'MyObj', 780 'versioned_object.namespace': 'versionedobjects', 781 'versioned_object.version': '1.2', 782 'versioned_object.data': {'foo': 1}} 783 obj = MyObj.obj_from_primitive(primitive) 784 self.assertEqual(obj.foo, 1) 785 self.assertEqual('1.2', obj.VERSION) 786 787 def test_hydration_bad_ns(self): 788 primitive = {'versioned_object.name': 'MyObj', 789 'versioned_object.namespace': 'foo', 790 'versioned_object.version': '1.5', 791 'versioned_object.data': {'foo': 1}} 792 self.assertRaises(exception.UnsupportedObjectError, 793 MyObj.obj_from_primitive, primitive) 794 795 def test_hydration_additional_unexpected_stuff(self): 796 primitive = {'versioned_object.name': 'MyObj', 797 'versioned_object.namespace': 'versionedobjects', 798 'versioned_object.version': '1.5.1', 799 'versioned_object.data': { 800 'foo': 1, 801 'unexpected_thing': 'foobar'}} 802 obj = MyObj.obj_from_primitive(primitive) 803 self.assertEqual(1, obj.foo) 804 self.assertFalse(hasattr(obj, 'unexpected_thing')) 805 # NOTE(danms): If we call obj_from_primitive() directly 806 # with a version containing .z, we'll get that version 807 # in the resulting object. In reality, when using the 808 # serializer, we'll get that snipped off (tested 809 # elsewhere) 810 self.assertEqual('1.5.1', obj.VERSION) 811 812 def test_dehydration(self): 813 expected = {'versioned_object.name': 'MyObj', 814 'versioned_object.namespace': 'versionedobjects', 815 'versioned_object.version': '1.6', 816 'versioned_object.data': {'foo': 1}} 817 obj = MyObj(foo=1) 818 obj.obj_reset_changes() 819 self.assertEqual(obj.obj_to_primitive(), expected) 820 821 def test_dehydration_invalid_version(self): 822 obj = MyObj(foo=1) 823 obj.obj_reset_changes() 824 self.assertRaises(exception.InvalidTargetVersion, 825 obj.obj_to_primitive, 826 target_version='1.7') 827 828 def test_dehydration_same_version(self): 829 expected = {'versioned_object.name': 'MyObj', 830 'versioned_object.namespace': 'versionedobjects', 831 'versioned_object.version': '1.6', 832 'versioned_object.data': {'foo': 1}} 833 obj = MyObj(foo=1) 834 obj.obj_reset_changes() 835 with mock.patch.object(obj, 'obj_make_compatible') as mock_compat: 836 self.assertEqual( 837 obj.obj_to_primitive(target_version='1.6'), expected) 838 self.assertFalse(mock_compat.called) 839 840 def test_object_property(self): 841 obj = MyObj(foo=1) 842 self.assertEqual(obj.foo, 1) 843 844 def test_object_property_type_error(self): 845 obj = MyObj() 846 847 def fail(): 848 obj.foo = 'a' 849 self.assertRaises(ValueError, fail) 850 851 def test_object_dict_syntax(self): 852 obj = MyObj(foo=123, bar=u'text') 853 self.assertEqual(obj['foo'], 123) 854 self.assertIn('bar', obj) 855 self.assertNotIn('missing', obj) 856 self.assertEqual(sorted(iter(obj)), 857 ['bar', 'foo']) 858 self.assertEqual(sorted(obj.keys()), 859 ['bar', 'foo']) 860 self.assertEqual(sorted(obj.values(), key=str), 861 [123, u'text']) 862 self.assertEqual(sorted(obj.items()), 863 [('bar', u'text'), ('foo', 123)]) 864 self.assertEqual(dict(obj), 865 {'foo': 123, 'bar': u'text'}) 866 867 def test_non_dict_remotable(self): 868 @base.VersionedObjectRegistry.register 869 class TestObject(base.VersionedObject): 870 @base.remotable 871 def test_method(self): 872 return 123 873 874 obj = TestObject(context=self.context) 875 self.assertEqual(123, obj.test_method()) 876 877 def test_load(self): 878 obj = MyObj() 879 self.assertEqual(obj.bar, 'loaded!') 880 881 def test_load_in_base(self): 882 @base.VersionedObjectRegistry.register 883 class Foo(base.VersionedObject): 884 fields = {'foobar': fields.Field(fields.Integer())} 885 obj = Foo() 886 with self.assertRaisesRegex(NotImplementedError, ".*foobar.*"): 887 obj.foobar 888 889 def test_loaded_in_primitive(self): 890 obj = MyObj(foo=1) 891 obj.obj_reset_changes() 892 self.assertEqual(obj.bar, 'loaded!') 893 expected = {'versioned_object.name': 'MyObj', 894 'versioned_object.namespace': 'versionedobjects', 895 'versioned_object.version': '1.6', 896 'versioned_object.changes': ['bar'], 897 'versioned_object.data': {'foo': 1, 898 'bar': 'loaded!'}} 899 self.assertEqual(obj.obj_to_primitive(), expected) 900 901 def test_changes_in_primitive(self): 902 obj = MyObj(foo=123) 903 self.assertEqual(obj.obj_what_changed(), set(['foo'])) 904 primitive = obj.obj_to_primitive() 905 self.assertIn('versioned_object.changes', primitive) 906 obj2 = MyObj.obj_from_primitive(primitive) 907 self.assertEqual(obj2.obj_what_changed(), set(['foo'])) 908 obj2.obj_reset_changes() 909 self.assertEqual(obj2.obj_what_changed(), set()) 910 911 def test_obj_class_from_name(self): 912 obj = base.VersionedObject.obj_class_from_name('MyObj', '1.5') 913 self.assertEqual('1.5', obj.VERSION) 914 915 def test_obj_class_from_name_latest_compatible(self): 916 obj = base.VersionedObject.obj_class_from_name('MyObj', '1.1') 917 self.assertEqual('1.6', obj.VERSION) 918 919 def test_unknown_objtype(self): 920 self.assertRaises(exception.UnsupportedObjectError, 921 base.VersionedObject.obj_class_from_name, 922 'foo', '1.0') 923 924 def test_obj_class_from_name_supported_version(self): 925 self.assertRaises(exception.IncompatibleObjectVersion, 926 base.VersionedObject.obj_class_from_name, 927 'MyObj', '1.25') 928 try: 929 base.VersionedObject.obj_class_from_name('MyObj', '1.25') 930 except exception.IncompatibleObjectVersion as error: 931 self.assertEqual('1.6', error.kwargs['supported']) 932 933 def test_orphaned_object(self): 934 obj = MyObj.query(self.context) 935 obj._context = None 936 self.assertRaises(exception.OrphanedObjectError, 937 obj._update_test) 938 939 def test_changed_1(self): 940 obj = MyObj.query(self.context) 941 obj.foo = 123 942 self.assertEqual(obj.obj_what_changed(), set(['foo'])) 943 obj._update_test() 944 self.assertEqual(obj.obj_what_changed(), set(['foo', 'bar'])) 945 self.assertEqual(obj.foo, 123) 946 947 def test_changed_2(self): 948 obj = MyObj.query(self.context) 949 obj.foo = 123 950 self.assertEqual(obj.obj_what_changed(), set(['foo'])) 951 obj.save() 952 self.assertEqual(obj.obj_what_changed(), set([])) 953 self.assertEqual(obj.foo, 123) 954 955 def test_changed_3(self): 956 obj = MyObj.query(self.context) 957 obj.foo = 123 958 self.assertEqual(obj.obj_what_changed(), set(['foo'])) 959 obj.refresh() 960 self.assertEqual(obj.obj_what_changed(), set([])) 961 self.assertEqual(obj.foo, 321) 962 self.assertEqual(obj.bar, 'refreshed') 963 964 def test_changed_4(self): 965 obj = MyObj.query(self.context) 966 obj.bar = 'something' 967 self.assertEqual(obj.obj_what_changed(), set(['bar'])) 968 obj.modify_save_modify() 969 self.assertEqual(obj.obj_what_changed(), set(['foo', 'rel_object'])) 970 self.assertEqual(obj.foo, 42) 971 self.assertEqual(obj.bar, 'meow') 972 self.assertIsInstance(obj.rel_object, MyOwnedObject) 973 974 def test_changed_with_sub_object(self): 975 @base.VersionedObjectRegistry.register 976 class ParentObject(base.VersionedObject): 977 fields = {'foo': fields.IntegerField(), 978 'bar': fields.ObjectField('MyObj'), 979 } 980 obj = ParentObject() 981 self.assertEqual(set(), obj.obj_what_changed()) 982 obj.foo = 1 983 self.assertEqual(set(['foo']), obj.obj_what_changed()) 984 bar = MyObj() 985 obj.bar = bar 986 self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed()) 987 obj.obj_reset_changes() 988 self.assertEqual(set(), obj.obj_what_changed()) 989 bar.foo = 1 990 self.assertEqual(set(['bar']), obj.obj_what_changed()) 991 992 def test_changed_with_bogus_field(self): 993 obj = MyObj() 994 obj.foo = 123 995 # Add a bogus field name to the changed list, as could be the 996 # case if we're sent some broken primitive from another node. 997 obj._changed_fields.add('does_not_exist') 998 self.assertEqual(set(['foo']), obj.obj_what_changed()) 999 self.assertEqual({'foo': 123}, obj.obj_get_changes()) 1000 1001 def test_static_result(self): 1002 obj = MyObj.query(self.context) 1003 self.assertEqual(obj.bar, 'bar') 1004 result = obj.marco() 1005 self.assertEqual(result, 'polo') 1006 1007 def test_updates(self): 1008 obj = MyObj.query(self.context) 1009 self.assertEqual(obj.foo, 1) 1010 obj._update_test() 1011 self.assertEqual(obj.bar, 'updated') 1012 1013 def test_contains(self): 1014 obj = MyOwnedObject() 1015 self.assertNotIn('baz', obj) 1016 obj.baz = 1 1017 self.assertIn('baz', obj) 1018 self.assertNotIn('does_not_exist', obj) 1019 1020 def test_obj_attr_is_set(self): 1021 obj = MyObj(foo=1) 1022 self.assertTrue(obj.obj_attr_is_set('foo')) 1023 self.assertFalse(obj.obj_attr_is_set('bar')) 1024 self.assertRaises(AttributeError, obj.obj_attr_is_set, 'bang') 1025 1026 def test_obj_reset_changes_recursive(self): 1027 obj = MyObj(rel_object=MyOwnedObject(baz=123), 1028 rel_objects=[MyOwnedObject(baz=456)]) 1029 self.assertEqual(set(['rel_object', 'rel_objects']), 1030 obj.obj_what_changed()) 1031 obj.obj_reset_changes() 1032 self.assertEqual(set(['rel_object']), obj.obj_what_changed()) 1033 self.assertEqual(set(['baz']), obj.rel_object.obj_what_changed()) 1034 self.assertEqual(set(['baz']), obj.rel_objects[0].obj_what_changed()) 1035 obj.obj_reset_changes(recursive=True, fields=['foo']) 1036 self.assertEqual(set(['rel_object']), obj.obj_what_changed()) 1037 self.assertEqual(set(['baz']), obj.rel_object.obj_what_changed()) 1038 self.assertEqual(set(['baz']), obj.rel_objects[0].obj_what_changed()) 1039 obj.obj_reset_changes(recursive=True) 1040 self.assertEqual(set([]), obj.rel_object.obj_what_changed()) 1041 self.assertEqual(set([]), obj.obj_what_changed()) 1042 1043 def test_get(self): 1044 obj = MyObj(foo=1) 1045 # Foo has value, should not get the default 1046 self.assertEqual(obj.get('foo', 2), 1) 1047 # Foo has value, should return the value without error 1048 self.assertEqual(obj.get('foo'), 1) 1049 # Bar is not loaded, so we should get the default 1050 self.assertEqual(obj.get('bar', 'not-loaded'), 'not-loaded') 1051 # Bar without a default should lazy-load 1052 self.assertEqual(obj.get('bar'), 'loaded!') 1053 # Bar now has a default, but loaded value should be returned 1054 self.assertEqual(obj.get('bar', 'not-loaded'), 'loaded!') 1055 # Invalid attribute should raise AttributeError 1056 self.assertRaises(AttributeError, obj.get, 'nothing') 1057 # ...even with a default 1058 self.assertRaises(AttributeError, obj.get, 'nothing', 3) 1059 1060 def test_object_inheritance(self): 1061 base_fields = [] 1062 myobj_fields = (['foo', 'bar', 'missing', 1063 'readonly', 'rel_object', 1064 'rel_objects', 'mutable_default', 'timestamp'] + 1065 base_fields) 1066 myobj3_fields = ['new_field'] 1067 self.assertTrue(issubclass(TestSubclassedObject, MyObj)) 1068 self.assertEqual(len(myobj_fields), len(MyObj.fields)) 1069 self.assertEqual(set(myobj_fields), set(MyObj.fields.keys())) 1070 self.assertEqual(len(myobj_fields) + len(myobj3_fields), 1071 len(TestSubclassedObject.fields)) 1072 self.assertEqual(set(myobj_fields) | set(myobj3_fields), 1073 set(TestSubclassedObject.fields.keys())) 1074 1075 def test_obj_as_admin(self): 1076 self.skipTest('oslo.context does not support elevated()') 1077 obj = MyObj(context=self.context) 1078 1079 def fake(*args, **kwargs): 1080 self.assertTrue(obj._context.is_admin) 1081 1082 with mock.patch.object(obj, 'obj_reset_changes') as mock_fn: 1083 mock_fn.side_effect = fake 1084 with obj.obj_as_admin(): 1085 obj.save() 1086 self.assertTrue(mock_fn.called) 1087 1088 self.assertFalse(obj._context.is_admin) 1089 1090 def test_get_changes(self): 1091 obj = MyObj() 1092 self.assertEqual({}, obj.obj_get_changes()) 1093 obj.foo = 123 1094 self.assertEqual({'foo': 123}, obj.obj_get_changes()) 1095 obj.bar = 'test' 1096 self.assertEqual({'foo': 123, 'bar': 'test'}, obj.obj_get_changes()) 1097 obj.obj_reset_changes() 1098 self.assertEqual({}, obj.obj_get_changes()) 1099 1100 timestamp = datetime.datetime(2001, 1, 1, tzinfo=pytz.utc) 1101 with mock.patch.object(timeutils, 'utcnow') as mock_utcnow: 1102 mock_utcnow.return_value = timestamp 1103 obj.timestamp = timeutils.utcnow() 1104 self.assertEqual({'timestamp': timestamp}, obj.obj_get_changes()) 1105 1106 obj.obj_reset_changes() 1107 self.assertEqual({}, obj.obj_get_changes()) 1108 1109 # Timestamp without tzinfo causes mismatch 1110 timestamp = datetime.datetime(2001, 1, 1) 1111 with mock.patch.object(timeutils, 'utcnow') as mock_utcnow: 1112 mock_utcnow.return_value = timestamp 1113 obj.timestamp = timeutils.utcnow() 1114 self.assertRaises(TypeError, obj.obj_get_changes()) 1115 1116 obj.obj_reset_changes() 1117 self.assertEqual({}, obj.obj_get_changes()) 1118 1119 def test_obj_fields(self): 1120 class TestObj(base.VersionedObject): 1121 fields = {'foo': fields.Field(fields.Integer())} 1122 obj_extra_fields = ['bar'] 1123 1124 @property 1125 def bar(self): 1126 return 'this is bar' 1127 1128 obj = TestObj() 1129 self.assertEqual(['foo', 'bar'], obj.obj_fields) 1130 1131 def test_obj_context(self): 1132 class TestObj(base.VersionedObject): 1133 pass 1134 1135 # context is available through the public property 1136 context = mock.Mock() 1137 obj = TestObj(context) 1138 self.assertEqual(context, obj.obj_context) 1139 1140 # ..but it's not available for update 1141 new_context = mock.Mock() 1142 self.assertRaises( 1143 AttributeError, 1144 setattr, obj, 'obj_context', new_context) 1145 1146 def test_obj_constructor(self): 1147 obj = MyObj(context=self.context, foo=123, bar='abc') 1148 self.assertEqual(123, obj.foo) 1149 self.assertEqual('abc', obj.bar) 1150 self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed()) 1151 1152 def test_obj_read_only(self): 1153 obj = MyObj(context=self.context, foo=123, bar='abc') 1154 obj.readonly = 1 1155 self.assertRaises(exception.ReadOnlyFieldError, setattr, 1156 obj, 'readonly', 2) 1157 1158 def test_obj_mutable_default(self): 1159 obj = MyObj(context=self.context, foo=123, bar='abc') 1160 obj.mutable_default = None 1161 obj.mutable_default.append('s1') 1162 self.assertEqual(obj.mutable_default, ['s1']) 1163 1164 obj1 = MyObj(context=self.context, foo=123, bar='abc') 1165 obj1.mutable_default = None 1166 obj1.mutable_default.append('s2') 1167 self.assertEqual(obj1.mutable_default, ['s2']) 1168 1169 def test_obj_mutable_default_set_default(self): 1170 obj1 = MyObj(context=self.context, foo=123, bar='abc') 1171 obj1.obj_set_defaults('mutable_default') 1172 self.assertEqual(obj1.mutable_default, []) 1173 obj1.mutable_default.append('s1') 1174 self.assertEqual(obj1.mutable_default, ['s1']) 1175 1176 obj2 = MyObj(context=self.context, foo=123, bar='abc') 1177 obj2.obj_set_defaults('mutable_default') 1178 self.assertEqual(obj2.mutable_default, []) 1179 obj2.mutable_default.append('s2') 1180 self.assertEqual(obj2.mutable_default, ['s2']) 1181 1182 def test_obj_repr(self): 1183 obj = MyObj(foo=123) 1184 self.assertEqual('MyObj(bar=<?>,foo=123,missing=<?>,' 1185 'mutable_default=<?>,readonly=<?>,' 1186 'rel_object=<?>,rel_objects=<?>,timestamp=<?>)', 1187 repr(obj)) 1188 1189 def test_obj_repr_sensitive(self): 1190 obj = MySensitiveObj(data="""{'admin_password':'mypassword'}""") 1191 self.assertEqual( 1192 'MySensitiveObj(data=\'{\'admin_password\':\'***\'}\')', repr(obj)) 1193 1194 obj2 = MySensitiveObj() 1195 self.assertEqual('MySensitiveObj(data=<?>)', repr(obj2)) 1196 1197 def test_obj_repr_unicode(self): 1198 obj = MyObj(bar=u'\u0191\u01A1\u01A1') 1199 # verify the unicode string has been encoded as ASCII if on python 2 1200 self.assertEqual("MyObj(bar='\u0191\u01A1\u01A1',foo=<?>," 1201 "missing=<?>,mutable_default=<?>,readonly=<?>," 1202 "rel_object=<?>,rel_objects=<?>,timestamp=<?>)", 1203 repr(obj)) 1204 1205 def test_obj_make_obj_compatible_with_relationships(self): 1206 subobj = MyOwnedObject(baz=1) 1207 obj = MyObj(rel_object=subobj) 1208 obj.obj_relationships = { 1209 'rel_object': [('1.5', '1.1'), ('1.7', '1.2')], 1210 } 1211 primitive = obj.obj_to_primitive()['versioned_object.data'] 1212 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1213 obj._obj_make_obj_compatible(copy.copy(primitive), '1.8', 1214 'rel_object') 1215 self.assertFalse(mock_compat.called) 1216 1217 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1218 obj._obj_make_obj_compatible(copy.copy(primitive), 1219 '1.7', 'rel_object') 1220 mock_compat.assert_called_once_with( 1221 primitive['rel_object']['versioned_object.data'], '1.2') 1222 self.assertEqual( 1223 '1.2', primitive['rel_object']['versioned_object.version']) 1224 1225 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1226 obj._obj_make_obj_compatible(copy.copy(primitive), 1227 '1.6', 'rel_object') 1228 mock_compat.assert_called_once_with( 1229 primitive['rel_object']['versioned_object.data'], '1.1') 1230 self.assertEqual( 1231 '1.1', primitive['rel_object']['versioned_object.version']) 1232 1233 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1234 obj._obj_make_obj_compatible(copy.copy(primitive), '1.5', 1235 'rel_object') 1236 mock_compat.assert_called_once_with( 1237 primitive['rel_object']['versioned_object.data'], '1.1') 1238 self.assertEqual( 1239 '1.1', primitive['rel_object']['versioned_object.version']) 1240 1241 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1242 _prim = copy.copy(primitive) 1243 obj._obj_make_obj_compatible(_prim, '1.4', 'rel_object') 1244 self.assertFalse(mock_compat.called) 1245 self.assertNotIn('rel_object', _prim) 1246 1247 def test_obj_make_compatible_hits_sub_objects_with_rels(self): 1248 subobj = MyOwnedObject(baz=1) 1249 obj = MyObj(foo=123, rel_object=subobj) 1250 obj.obj_relationships = {'rel_object': [('1.0', '1.0')]} 1251 with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat: 1252 obj.obj_make_compatible({'rel_object': 'foo'}, '1.10') 1253 mock_compat.assert_called_once_with({'rel_object': 'foo'}, '1.10', 1254 'rel_object') 1255 1256 def test_obj_make_compatible_skips_unset_sub_objects_with_rels(self): 1257 obj = MyObj(foo=123) 1258 obj.obj_relationships = {'rel_object': [('1.0', '1.0')]} 1259 with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat: 1260 obj.obj_make_compatible({'rel_object': 'foo'}, '1.10') 1261 self.assertFalse(mock_compat.called) 1262 1263 def test_obj_make_compatible_complains_about_missing_rel_rules(self): 1264 subobj = MyOwnedObject(baz=1) 1265 obj = MyObj(foo=123, rel_object=subobj) 1266 obj.obj_relationships = {} 1267 self.assertRaises(exception.ObjectActionError, 1268 obj.obj_make_compatible, {}, '1.0') 1269 1270 def test_obj_make_compatible_handles_list_of_objects_with_rels(self): 1271 subobj = MyOwnedObject(baz=1) 1272 obj = MyObj(rel_objects=[subobj]) 1273 obj.obj_relationships = {'rel_objects': [('1.0', '1.123')]} 1274 1275 def fake_make_compat(primitive, version, **k): 1276 self.assertEqual('1.123', version) 1277 self.assertIn('baz', primitive) 1278 1279 with mock.patch.object(subobj, 'obj_make_compatible') as mock_mc: 1280 mock_mc.side_effect = fake_make_compat 1281 obj.obj_to_primitive('1.0') 1282 self.assertTrue(mock_mc.called) 1283 1284 def test_obj_make_compatible_with_manifest(self): 1285 subobj = MyOwnedObject(baz=1) 1286 obj = MyObj(rel_object=subobj) 1287 obj.obj_relationships = {} 1288 orig_primitive = obj.obj_to_primitive()['versioned_object.data'] 1289 1290 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1291 manifest = {'MyOwnedObject': '1.2'} 1292 primitive = copy.deepcopy(orig_primitive) 1293 obj.obj_make_compatible_from_manifest(primitive, '1.5', manifest) 1294 mock_compat.assert_called_once_with( 1295 primitive['rel_object']['versioned_object.data'], '1.2') 1296 self.assertEqual( 1297 '1.2', 1298 primitive['rel_object']['versioned_object.version']) 1299 1300 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1301 manifest = {'MyOwnedObject': '1.0'} 1302 primitive = copy.deepcopy(orig_primitive) 1303 obj.obj_make_compatible_from_manifest(primitive, '1.5', manifest) 1304 mock_compat.assert_called_once_with( 1305 primitive['rel_object']['versioned_object.data'], '1.0') 1306 self.assertEqual( 1307 '1.0', 1308 primitive['rel_object']['versioned_object.version']) 1309 1310 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1311 manifest = {} 1312 primitive = copy.deepcopy(orig_primitive) 1313 obj.obj_make_compatible_from_manifest(primitive, '1.5', manifest) 1314 self.assertFalse(mock_compat.called) 1315 self.assertEqual( 1316 '1.0', 1317 primitive['rel_object']['versioned_object.version']) 1318 1319 def test_obj_make_compatible_with_manifest_subobj(self): 1320 # Make sure that we call the subobject's "from_manifest" method 1321 # as well 1322 subobj = MyOwnedObject(baz=1) 1323 obj = MyObj(rel_object=subobj) 1324 obj.obj_relationships = {} 1325 manifest = {'MyOwnedObject': '1.2'} 1326 primitive = obj.obj_to_primitive()['versioned_object.data'] 1327 method = 'obj_make_compatible_from_manifest' 1328 with mock.patch.object(subobj, method) as mock_compat: 1329 obj.obj_make_compatible_from_manifest(primitive, '1.5', manifest) 1330 mock_compat.assert_called_once_with( 1331 primitive['rel_object']['versioned_object.data'], 1332 '1.2', version_manifest=manifest) 1333 1334 def test_obj_make_compatible_with_manifest_subobj_list(self): 1335 # Make sure that we call the subobject's "from_manifest" method 1336 # as well 1337 subobj = MyOwnedObject(baz=1) 1338 obj = MyObj(rel_objects=[subobj]) 1339 obj.obj_relationships = {} 1340 manifest = {'MyOwnedObject': '1.2'} 1341 primitive = obj.obj_to_primitive()['versioned_object.data'] 1342 method = 'obj_make_compatible_from_manifest' 1343 with mock.patch.object(subobj, method) as mock_compat: 1344 obj.obj_make_compatible_from_manifest(primitive, '1.5', manifest) 1345 mock_compat.assert_called_once_with( 1346 primitive['rel_objects'][0]['versioned_object.data'], 1347 '1.2', version_manifest=manifest) 1348 1349 def test_obj_make_compatible_removes_field_cleans_changes(self): 1350 @base.VersionedObjectRegistry.register_if(False) 1351 class TestObject(base.VersionedObject): 1352 VERSION = '1.1' 1353 fields = {'foo': fields.StringField(), 1354 'bar': fields.StringField()} 1355 1356 def obj_make_compatible(self, primitive, target_version): 1357 del primitive['bar'] 1358 1359 obj = TestObject(foo='test1', bar='test2') 1360 prim = obj.obj_to_primitive('1.0') 1361 self.assertEqual(['foo'], prim['versioned_object.changes']) 1362 1363 def test_delattr(self): 1364 obj = MyObj(bar='foo') 1365 del obj.bar 1366 1367 # Should appear unset now 1368 self.assertFalse(obj.obj_attr_is_set('bar')) 1369 1370 # Make sure post-delete, references trigger lazy loads 1371 self.assertEqual('loaded!', getattr(obj, 'bar')) 1372 1373 def test_delattr_unset(self): 1374 obj = MyObj() 1375 self.assertRaises(AttributeError, delattr, obj, 'bar') 1376 1377 def test_obj_make_compatible_on_list_base(self): 1378 @base.VersionedObjectRegistry.register_if(False) 1379 class MyList(base.ObjectListBase, base.VersionedObject): 1380 VERSION = '1.1' 1381 fields = {'objects': fields.ListOfObjectsField('MyObj')} 1382 1383 childobj = MyObj(foo=1) 1384 listobj = MyList(objects=[childobj]) 1385 compat_func = 'obj_make_compatible_from_manifest' 1386 with mock.patch.object(childobj, compat_func) as mock_compat: 1387 listobj.obj_to_primitive(target_version='1.0') 1388 mock_compat.assert_called_once_with({'foo': 1}, '1.0', 1389 version_manifest=None) 1390 1391 def test_comparable_objects(self): 1392 class NonVersionedObject(object): 1393 pass 1394 1395 obj1 = MyComparableObj(foo=1) 1396 obj2 = MyComparableObj(foo=1) 1397 obj3 = MyComparableObj(foo=2) 1398 obj4 = NonVersionedObject() 1399 self.assertTrue(obj1 == obj2) 1400 self.assertFalse(obj1 == obj3) 1401 self.assertFalse(obj1 == obj4) 1402 self.assertNotEqual(obj1, None) 1403 1404 def test_compound_clone(self): 1405 obj = MyCompoundObject() 1406 obj.foo = [1, 2, 3] 1407 obj.bar = {"a": 1, "b": 2, "c": 3} 1408 obj.baz = set([1, 2, 3]) 1409 copy = obj.obj_clone() 1410 self.assertEqual(obj.foo, copy.foo) 1411 self.assertEqual(obj.bar, copy.bar) 1412 self.assertEqual(obj.baz, copy.baz) 1413 # ensure that the cloned object still coerces values in its compounds 1414 copy.foo.append("4") 1415 copy.bar.update(d="4") 1416 copy.baz.add("4") 1417 self.assertEqual([1, 2, 3, 4], copy.foo) 1418 self.assertEqual({"a": 1, "b": 2, "c": 3, "d": 4}, copy.bar) 1419 self.assertEqual(set([1, 2, 3, 4]), copy.baz) 1420 1421 def test_obj_list_fields_modifications(self): 1422 @base.VersionedObjectRegistry.register 1423 class ObjWithList(base.VersionedObject): 1424 fields = { 1425 'list_field': fields.Field(fields.List(fields.Integer())), 1426 } 1427 obj = ObjWithList() 1428 1429 def set_by_index(val): 1430 obj.list_field[0] = val 1431 1432 def append(val): 1433 obj.list_field.append(val) 1434 1435 def extend(val): 1436 obj.list_field.extend([val]) 1437 1438 def add(val): 1439 obj.list_field = obj.list_field + [val] 1440 1441 def iadd(val): 1442 """Test += corner case 1443 1444 a=a+b and a+=b use different magic methods under the hood: 1445 first one calls __add__ which clones initial value before the 1446 assignment, second one call __iadd__ which modifies the initial 1447 list. 1448 Assignment should cause coercing in both cases, but __iadd__ may 1449 corrupt the initial value even if the assignment fails. 1450 So it should be overridden as well, and this test is needed to 1451 verify it 1452 """ 1453 obj.list_field += [val] 1454 1455 def insert(val): 1456 obj.list_field.insert(0, val) 1457 1458 def simple_slice(val): 1459 obj.list_field[:] = [val] 1460 1461 def extended_slice(val): 1462 """Extended slice case 1463 1464 Extended slice (and regular slices in py3) are handled differently 1465 thus needing a separate test 1466 """ 1467 obj.list_field[::2] = [val] 1468 1469 # positive tests to ensure that coercing works properly 1470 obj.list_field = ["42"] 1471 set_by_index("1") 1472 append("2") 1473 extend("3") 1474 add("4") 1475 iadd("5") 1476 insert("0") 1477 self.assertEqual([0, 1, 2, 3, 4, 5], obj.list_field) 1478 simple_slice("10") 1479 self.assertEqual([10], obj.list_field) 1480 extended_slice("42") 1481 self.assertEqual([42], obj.list_field) 1482 obj.obj_reset_changes() 1483 # negative tests with non-coerceable values 1484 self.assertRaises(ValueError, set_by_index, "abc") 1485 self.assertRaises(ValueError, append, "abc") 1486 self.assertRaises(ValueError, extend, "abc") 1487 self.assertRaises(ValueError, add, "abc") 1488 self.assertRaises(ValueError, iadd, "abc") 1489 self.assertRaises(ValueError, insert, "abc") 1490 self.assertRaises(ValueError, simple_slice, "abc") 1491 self.assertRaises(ValueError, extended_slice, "abc") 1492 # ensure that nothing has been changed 1493 self.assertEqual([42], obj.list_field) 1494 self.assertEqual({}, obj.obj_get_changes()) 1495 1496 def test_obj_dict_field_modifications(self): 1497 @base.VersionedObjectRegistry.register 1498 class ObjWithDict(base.VersionedObject): 1499 fields = { 1500 'dict_field': fields.Field(fields.Dict(fields.Integer())), 1501 } 1502 obj = ObjWithDict() 1503 obj.dict_field = {"1": 1, "3": 3, "4": 4} 1504 1505 def set_by_key(key, value): 1506 obj.dict_field[key] = value 1507 1508 def add_by_key(key, value): 1509 obj.dict_field[key] = value 1510 1511 def update_w_dict(key, value): 1512 obj.dict_field.update({key: value}) 1513 1514 def update_w_kwargs(key, value): 1515 obj.dict_field.update(**{key: value}) 1516 1517 def setdefault(key, value): 1518 obj.dict_field.setdefault(key, value) 1519 1520 # positive tests to ensure that coercing works properly 1521 set_by_key("1", "10") 1522 add_by_key("2", "20") 1523 update_w_dict("3", "30") 1524 update_w_kwargs("4", "40") 1525 setdefault("5", "50") 1526 self.assertEqual({"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, 1527 obj.dict_field) 1528 obj.obj_reset_changes() 1529 # negative tests with non-coerceable values 1530 self.assertRaises(ValueError, set_by_key, "key", "abc") 1531 self.assertRaises(ValueError, add_by_key, "other", "abc") 1532 self.assertRaises(ValueError, update_w_dict, "key", "abc") 1533 self.assertRaises(ValueError, update_w_kwargs, "key", "abc") 1534 self.assertRaises(ValueError, setdefault, "other", "abc") 1535 # ensure that nothing has been changed 1536 self.assertEqual({"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, 1537 obj.dict_field) 1538 self.assertEqual({}, obj.obj_get_changes()) 1539 1540 def test_obj_set_field_modifications(self): 1541 @base.VersionedObjectRegistry.register 1542 class ObjWithSet(base.VersionedObject): 1543 fields = { 1544 'set_field': fields.Field(fields.Set(fields.Integer())) 1545 } 1546 obj = ObjWithSet() 1547 obj.set_field = set([42]) 1548 1549 def add(value): 1550 obj.set_field.add(value) 1551 1552 def update_w_set(value): 1553 obj.set_field.update(set([value])) 1554 1555 def update_w_list(value): 1556 obj.set_field.update([value, value, value]) 1557 1558 def sym_diff_upd(value): 1559 obj.set_field.symmetric_difference_update(set([value])) 1560 1561 def union(value): 1562 obj.set_field = obj.set_field | set([value]) 1563 1564 def iunion(value): 1565 obj.set_field |= set([value]) 1566 1567 def xor(value): 1568 obj.set_field = obj.set_field ^ set([value]) 1569 1570 def ixor(value): 1571 obj.set_field ^= set([value]) 1572 # positive tests to ensure that coercing works properly 1573 sym_diff_upd("42") 1574 add("1") 1575 update_w_list("2") 1576 update_w_set("3") 1577 union("4") 1578 iunion("5") 1579 xor("6") 1580 ixor("7") 1581 self.assertEqual(set([1, 2, 3, 4, 5, 6, 7]), obj.set_field) 1582 obj.set_field = set([42]) 1583 obj.obj_reset_changes() 1584 # negative tests with non-coerceable values 1585 self.assertRaises(ValueError, add, "abc") 1586 self.assertRaises(ValueError, update_w_list, "abc") 1587 self.assertRaises(ValueError, update_w_set, "abc") 1588 self.assertRaises(ValueError, sym_diff_upd, "abc") 1589 self.assertRaises(ValueError, union, "abc") 1590 self.assertRaises(ValueError, iunion, "abc") 1591 self.assertRaises(ValueError, xor, "abc") 1592 self.assertRaises(ValueError, ixor, "abc") 1593 # ensure that nothing has been changed 1594 self.assertEqual(set([42]), obj.set_field) 1595 self.assertEqual({}, obj.obj_get_changes()) 1596 1597 1598class TestObject(_LocalTest, _TestObject): 1599 def test_set_defaults(self): 1600 obj = MyObj() 1601 obj.obj_set_defaults('foo') 1602 self.assertTrue(obj.obj_attr_is_set('foo')) 1603 self.assertEqual(1, obj.foo) 1604 1605 def test_set_defaults_no_default(self): 1606 obj = MyObj() 1607 self.assertRaises(exception.ObjectActionError, 1608 obj.obj_set_defaults, 'bar') 1609 1610 def test_set_all_defaults(self): 1611 obj = MyObj() 1612 obj.obj_set_defaults() 1613 self.assertEqual(set(['mutable_default', 'foo']), 1614 obj.obj_what_changed()) 1615 self.assertEqual(1, obj.foo) 1616 1617 def test_set_defaults_not_overwrite(self): 1618 # NOTE(danms): deleted defaults to False, so verify that it does 1619 # not get reset by obj_set_defaults() 1620 obj = MyObj(deleted=True) 1621 obj.obj_set_defaults() 1622 self.assertEqual(1, obj.foo) 1623 self.assertTrue(obj.deleted) 1624 1625 1626class TestRemoteObject(_RemoteTest, _TestObject): 1627 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 1628 def test_major_version_mismatch(self, mock_otgv): 1629 mock_otgv.return_value = {'MyObj': '2.0'} 1630 self.assertRaises(exception.IncompatibleObjectVersion, 1631 MyObj2.query, self.context) 1632 1633 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 1634 def test_minor_version_greater(self, mock_otgv): 1635 mock_otgv.return_value = {'MyObj': '1.7'} 1636 self.assertRaises(exception.IncompatibleObjectVersion, 1637 MyObj2.query, self.context) 1638 1639 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 1640 def test_minor_version_less(self, mock_otgv): 1641 mock_otgv.return_value = {'MyObj': '1.2'} 1642 obj = MyObj2.query(self.context) 1643 self.assertEqual(obj.bar, 'bar') 1644 1645 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 1646 def test_compat(self, mock_otgv): 1647 mock_otgv.return_value = {'MyObj': '1.1'} 1648 obj = MyObj2.query(self.context) 1649 self.assertEqual('oldbar', obj.bar) 1650 1651 @mock.patch('oslo_versionedobjects.base.obj_tree_get_versions') 1652 def test_revision_ignored(self, mock_otgv): 1653 mock_otgv.return_value = {'MyObj': '1.1.456'} 1654 obj = MyObj2.query(self.context) 1655 self.assertEqual('bar', obj.bar) 1656 1657 def test_class_action_falls_back_compat(self): 1658 with mock.patch.object(base.VersionedObject, 'indirection_api') as ma: 1659 ma.object_class_action_versions.side_effect = NotImplementedError 1660 MyObj.query(self.context) 1661 ma.object_class_action.assert_called_once_with( 1662 self.context, 'MyObj', 'query', MyObj.VERSION, (), {}) 1663 1664 1665class TestObjectListBase(test.TestCase): 1666 def test_list_like_operations(self): 1667 @base.VersionedObjectRegistry.register 1668 class MyElement(base.VersionedObject): 1669 fields = {'foo': fields.IntegerField()} 1670 1671 def __init__(self, foo): 1672 super(MyElement, self).__init__() 1673 self.foo = foo 1674 1675 class Foo(base.ObjectListBase, base.VersionedObject): 1676 fields = {'objects': fields.ListOfObjectsField('MyElement')} 1677 1678 objlist = Foo(context='foo', 1679 objects=[MyElement(1), MyElement(2), MyElement(3)]) 1680 self.assertEqual(list(objlist), objlist.objects) 1681 self.assertEqual(len(objlist), 3) 1682 self.assertIn(objlist.objects[0], objlist) 1683 self.assertEqual(list(objlist[:1]), [objlist.objects[0]]) 1684 self.assertEqual(objlist[:1]._context, 'foo') 1685 self.assertEqual(objlist[2], objlist.objects[2]) 1686 self.assertEqual(objlist.count(objlist.objects[0]), 1) 1687 self.assertEqual(objlist.index(objlist.objects[1]), 1) 1688 objlist.sort(key=lambda x: x.foo, reverse=True) 1689 self.assertEqual([3, 2, 1], 1690 [x.foo for x in objlist]) 1691 1692 def test_serialization(self): 1693 @base.VersionedObjectRegistry.register 1694 class Foo(base.ObjectListBase, base.VersionedObject): 1695 fields = {'objects': fields.ListOfObjectsField('Bar')} 1696 1697 @base.VersionedObjectRegistry.register 1698 class Bar(base.VersionedObject): 1699 fields = {'foo': fields.Field(fields.String())} 1700 1701 obj = Foo(objects=[]) 1702 for i in 'abc': 1703 bar = Bar(foo=i) 1704 obj.objects.append(bar) 1705 1706 obj2 = base.VersionedObject.obj_from_primitive(obj.obj_to_primitive()) 1707 self.assertFalse(obj is obj2) 1708 self.assertEqual([x.foo for x in obj], 1709 [y.foo for y in obj2]) 1710 1711 def _test_object_list_version_mappings(self, list_obj_class): 1712 # Figure out what sort of object this list is for 1713 list_field = list_obj_class.fields['objects'] 1714 item_obj_field = list_field._type._element_type 1715 item_obj_name = item_obj_field._type._obj_name 1716 1717 # Look through all object classes of this type and make sure that 1718 # the versions we find are covered by the parent list class 1719 obj_classes = base.VersionedObjectRegistry.obj_classes()[item_obj_name] 1720 for item_class in obj_classes: 1721 if is_test_object(item_class): 1722 continue 1723 self.assertIn( 1724 item_class.VERSION, 1725 list_obj_class.child_versions.values(), 1726 'Version mapping is incomplete for %s' % ( 1727 list_obj_class.__name__)) 1728 1729 def test_object_version_mappings(self): 1730 self.skipTest('this needs to be generalized') 1731 # Find all object list classes and make sure that they at least handle 1732 # all the current object versions 1733 for obj_classes in base.VersionedObjectRegistry.obj_classes().values(): 1734 for obj_class in obj_classes: 1735 if issubclass(obj_class, base.ObjectListBase): 1736 self._test_object_list_version_mappings(obj_class) 1737 1738 def test_obj_make_compatible_child_versions(self): 1739 @base.VersionedObjectRegistry.register 1740 class MyElement(base.VersionedObject): 1741 fields = {'foo': fields.IntegerField()} 1742 1743 @base.VersionedObjectRegistry.register 1744 class Foo(base.ObjectListBase, base.VersionedObject): 1745 VERSION = '1.1' 1746 fields = {'objects': fields.ListOfObjectsField('MyElement')} 1747 child_versions = {'1.0': '1.0', '1.1': '1.0'} 1748 1749 subobj = MyElement(foo=1) 1750 obj = Foo(objects=[subobj]) 1751 primitive = obj.obj_to_primitive()['versioned_object.data'] 1752 1753 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1754 obj.obj_make_compatible(copy.copy(primitive), '1.1') 1755 self.assertTrue(mock_compat.called) 1756 1757 def test_obj_make_compatible_obj_relationships(self): 1758 @base.VersionedObjectRegistry.register 1759 class MyElement(base.VersionedObject): 1760 fields = {'foo': fields.IntegerField()} 1761 1762 @base.VersionedObjectRegistry.register 1763 class Bar(base.ObjectListBase, base.VersionedObject): 1764 VERSION = '1.1' 1765 fields = {'objects': fields.ListOfObjectsField('MyElement')} 1766 obj_relationships = { 1767 'objects': [('1.0', '1.0'), ('1.1', '1.0')] 1768 } 1769 1770 subobj = MyElement(foo=1) 1771 obj = Bar(objects=[subobj]) 1772 primitive = obj.obj_to_primitive()['versioned_object.data'] 1773 1774 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1775 obj.obj_make_compatible(copy.copy(primitive), '1.1') 1776 self.assertTrue(mock_compat.called) 1777 1778 def test_obj_make_compatible_no_relationships(self): 1779 @base.VersionedObjectRegistry.register 1780 class MyElement(base.VersionedObject): 1781 fields = {'foo': fields.IntegerField()} 1782 1783 @base.VersionedObjectRegistry.register 1784 class Baz(base.ObjectListBase, base.VersionedObject): 1785 VERSION = '1.1' 1786 fields = {'objects': fields.ListOfObjectsField('MyElement')} 1787 1788 subobj = MyElement(foo=1) 1789 obj = Baz(objects=[subobj]) 1790 primitive = obj.obj_to_primitive()['versioned_object.data'] 1791 1792 with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: 1793 obj.obj_make_compatible(copy.copy(primitive), '1.1') 1794 self.assertTrue(mock_compat.called) 1795 1796 def test_list_changes(self): 1797 @base.VersionedObjectRegistry.register 1798 class Foo(base.ObjectListBase, base.VersionedObject): 1799 fields = {'objects': fields.ListOfObjectsField('Bar')} 1800 1801 @base.VersionedObjectRegistry.register 1802 class Bar(base.VersionedObject): 1803 fields = {'foo': fields.StringField()} 1804 1805 obj = Foo(objects=[]) 1806 self.assertEqual(set(['objects']), obj.obj_what_changed()) 1807 obj.objects.append(Bar(foo='test')) 1808 self.assertEqual(set(['objects']), obj.obj_what_changed()) 1809 obj.obj_reset_changes() 1810 # This should still look dirty because the child is dirty 1811 self.assertEqual(set(['objects']), obj.obj_what_changed()) 1812 obj.objects[0].obj_reset_changes() 1813 # This should now look clean because the child is clean 1814 self.assertEqual(set(), obj.obj_what_changed()) 1815 1816 def test_initialize_objects(self): 1817 class Foo(base.ObjectListBase, base.VersionedObject): 1818 fields = {'objects': fields.ListOfObjectsField('Bar')} 1819 1820 class Bar(base.VersionedObject): 1821 fields = {'foo': fields.StringField()} 1822 1823 obj = Foo() 1824 self.assertEqual([], obj.objects) 1825 self.assertEqual(set(), obj.obj_what_changed()) 1826 1827 def test_obj_repr(self): 1828 @base.VersionedObjectRegistry.register 1829 class Foo(base.ObjectListBase, base.VersionedObject): 1830 fields = {'objects': fields.ListOfObjectsField('Bar')} 1831 1832 @base.VersionedObjectRegistry.register 1833 class Bar(base.VersionedObject): 1834 fields = {'uuid': fields.StringField()} 1835 1836 obj = Foo(objects=[Bar(uuid='fake-uuid')]) 1837 self.assertEqual('Foo(objects=[Bar(fake-uuid)])', repr(obj)) 1838 1839 1840class TestObjectSerializer(_BaseTestCase): 1841 def test_serialize_entity_primitive(self): 1842 ser = base.VersionedObjectSerializer() 1843 for thing in (1, 'foo', [1, 2], {'foo': 'bar'}): 1844 self.assertEqual(thing, ser.serialize_entity(None, thing)) 1845 1846 def test_deserialize_entity_primitive(self): 1847 ser = base.VersionedObjectSerializer() 1848 for thing in (1, 'foo', [1, 2], {'foo': 'bar'}): 1849 self.assertEqual(thing, ser.deserialize_entity(None, thing)) 1850 1851 def test_serialize_set_to_list(self): 1852 ser = base.VersionedObjectSerializer() 1853 self.assertEqual([1, 2], ser.serialize_entity(None, set([1, 2]))) 1854 1855 @mock.patch('oslo_versionedobjects.base.VersionedObject.indirection_api') 1856 def _test_deserialize_entity_newer(self, obj_version, backported_to, 1857 mock_iapi, 1858 my_version='1.6'): 1859 ser = base.VersionedObjectSerializer() 1860 mock_iapi.object_backport_versions.return_value = 'backported' 1861 1862 @base.VersionedObjectRegistry.register 1863 class MyTestObj(MyObj): 1864 VERSION = my_version 1865 1866 obj = MyTestObj() 1867 obj.VERSION = obj_version 1868 primitive = obj.obj_to_primitive() 1869 result = ser.deserialize_entity(self.context, primitive) 1870 if backported_to is None: 1871 self.assertFalse(mock_iapi.object_backport_versions.called) 1872 else: 1873 self.assertEqual('backported', result) 1874 mock_iapi.object_backport_versions.assert_called_with( 1875 self.context, primitive, {'MyTestObj': my_version, 1876 'MyOwnedObject': '1.0'}) 1877 1878 def test_deserialize_entity_newer_version_backports(self): 1879 self._test_deserialize_entity_newer('1.25', '1.6') 1880 1881 def test_deserialize_entity_newer_revision_does_not_backport_zero(self): 1882 self._test_deserialize_entity_newer('1.6.0', None) 1883 1884 def test_deserialize_entity_newer_revision_does_not_backport(self): 1885 self._test_deserialize_entity_newer('1.6.1', None) 1886 1887 def test_deserialize_entity_newer_version_passes_revision(self): 1888 self._test_deserialize_entity_newer('1.7', '1.6.1', my_version='1.6.1') 1889 1890 def test_deserialize_dot_z_with_extra_stuff(self): 1891 primitive = {'versioned_object.name': 'MyObj', 1892 'versioned_object.namespace': 'versionedobjects', 1893 'versioned_object.version': '1.6.1', 1894 'versioned_object.data': { 1895 'foo': 1, 1896 'unexpected_thing': 'foobar'}} 1897 ser = base.VersionedObjectSerializer() 1898 obj = ser.deserialize_entity(self.context, primitive) 1899 self.assertEqual(1, obj.foo) 1900 self.assertFalse(hasattr(obj, 'unexpected_thing')) 1901 # NOTE(danms): The serializer is where the logic lives that 1902 # avoids backports for cases where only a .z difference in 1903 # the received object version is detected. As a result, we 1904 # end up with a version of what we expected, effectively the 1905 # .0 of the object. 1906 self.assertEqual('1.6', obj.VERSION) 1907 1908 def test_deserialize_entity_newer_version_no_indirection(self): 1909 ser = base.VersionedObjectSerializer() 1910 obj = MyObj() 1911 obj.VERSION = '1.25' 1912 primitive = obj.obj_to_primitive() 1913 self.assertRaises(exception.IncompatibleObjectVersion, 1914 ser.deserialize_entity, self.context, primitive) 1915 1916 def _test_nested_backport(self, old): 1917 @base.VersionedObjectRegistry.register 1918 class Parent(base.VersionedObject): 1919 VERSION = '1.0' 1920 1921 fields = { 1922 'child': fields.ObjectField('MyObj'), 1923 } 1924 1925 @base.VersionedObjectRegistry.register # noqa 1926 class Parent(base.VersionedObject): # noqa 1927 VERSION = '1.1' 1928 1929 fields = { 1930 'child': fields.ObjectField('MyObj'), 1931 } 1932 1933 child = MyObj(foo=1) 1934 parent = Parent(child=child) 1935 prim = parent.obj_to_primitive() 1936 child_prim = prim['versioned_object.data']['child'] 1937 child_prim['versioned_object.version'] = '1.10' 1938 ser = base.VersionedObjectSerializer() 1939 with mock.patch.object(base.VersionedObject, 'indirection_api') as a: 1940 if old: 1941 a.object_backport_versions.side_effect = NotImplementedError 1942 ser.deserialize_entity(self.context, prim) 1943 a.object_backport_versions.assert_called_once_with( 1944 self.context, prim, {'Parent': '1.1', 1945 'MyObj': '1.6', 1946 'MyOwnedObject': '1.0'}) 1947 if old: 1948 # NOTE(danms): This should be the version of the parent object, 1949 # not the child. If wrong, this will be '1.6', which is the max 1950 # child version in our registry. 1951 a.object_backport.assert_called_once_with( 1952 self.context, prim, '1.1') 1953 1954 def test_nested_backport_new_method(self): 1955 self._test_nested_backport(old=False) 1956 1957 def test_nested_backport_old_method(self): 1958 self._test_nested_backport(old=True) 1959 1960 def test_object_serialization(self): 1961 ser = base.VersionedObjectSerializer() 1962 obj = MyObj() 1963 primitive = ser.serialize_entity(self.context, obj) 1964 self.assertIn('versioned_object.name', primitive) 1965 obj2 = ser.deserialize_entity(self.context, primitive) 1966 self.assertIsInstance(obj2, MyObj) 1967 self.assertEqual(self.context, obj2._context) 1968 1969 def test_object_serialization_iterables(self): 1970 ser = base.VersionedObjectSerializer() 1971 obj = MyObj() 1972 for iterable in (list, tuple, set): 1973 thing = iterable([obj]) 1974 primitive = ser.serialize_entity(self.context, thing) 1975 self.assertEqual(1, len(primitive)) 1976 for item in primitive: 1977 self.assertNotIsInstance(item, base.VersionedObject) 1978 thing2 = ser.deserialize_entity(self.context, primitive) 1979 self.assertEqual(1, len(thing2)) 1980 for item in thing2: 1981 self.assertIsInstance(item, MyObj) 1982 # dict case 1983 thing = {'key': obj} 1984 primitive = ser.serialize_entity(self.context, thing) 1985 self.assertEqual(1, len(primitive)) 1986 for item in primitive.values(): 1987 self.assertNotIsInstance(item, base.VersionedObject) 1988 thing2 = ser.deserialize_entity(self.context, primitive) 1989 self.assertEqual(1, len(thing2)) 1990 for item in thing2.values(): 1991 self.assertIsInstance(item, MyObj) 1992 1993 # object-action updates dict case 1994 thing = {'foo': obj.obj_to_primitive()} 1995 primitive = ser.serialize_entity(self.context, thing) 1996 self.assertEqual(thing, primitive) 1997 thing2 = ser.deserialize_entity(self.context, thing) 1998 self.assertIsInstance(thing2['foo'], base.VersionedObject) 1999 2000 def test_serializer_subclass_namespace(self): 2001 @base.VersionedObjectRegistry.register 2002 class MyNSObj(base.VersionedObject): 2003 OBJ_SERIAL_NAMESPACE = 'foo' 2004 fields = {'foo': fields.IntegerField()} 2005 2006 class MySerializer(base.VersionedObjectSerializer): 2007 OBJ_BASE_CLASS = MyNSObj 2008 2009 ser = MySerializer() 2010 obj = MyNSObj(foo=123) 2011 obj2 = ser.deserialize_entity(None, ser.serialize_entity(None, obj)) 2012 self.assertIsInstance(obj2, MyNSObj) 2013 self.assertEqual(obj.foo, obj2.foo) 2014 2015 def test_serializer_subclass_namespace_mismatch(self): 2016 @base.VersionedObjectRegistry.register 2017 class MyNSObj(base.VersionedObject): 2018 OBJ_SERIAL_NAMESPACE = 'foo' 2019 fields = {'foo': fields.IntegerField()} 2020 2021 class MySerializer(base.VersionedObjectSerializer): 2022 OBJ_BASE_CLASS = MyNSObj 2023 2024 myser = MySerializer() 2025 voser = base.VersionedObjectSerializer() 2026 obj = MyObj(foo=123) 2027 obj2 = myser.deserialize_entity(None, 2028 voser.serialize_entity(None, obj)) 2029 2030 # NOTE(danms): The new serializer should have ignored the objects 2031 # serialized by the base serializer, so obj2 here should be a dict 2032 # primitive and not a hydrated object 2033 self.assertNotIsInstance(obj2, MyNSObj) 2034 self.assertIn('versioned_object.name', obj2) 2035 2036 def test_serializer_subclass_base_object_indirection(self): 2037 @base.VersionedObjectRegistry.register 2038 class MyNSObj(base.VersionedObject): 2039 OBJ_SERIAL_NAMESPACE = 'foo' 2040 fields = {'foo': fields.IntegerField()} 2041 indirection_api = mock.MagicMock() 2042 2043 class MySerializer(base.VersionedObjectSerializer): 2044 OBJ_BASE_CLASS = MyNSObj 2045 2046 ser = MySerializer() 2047 prim = MyNSObj(foo=1).obj_to_primitive() 2048 prim['foo.version'] = '2.0' 2049 ser.deserialize_entity(mock.sentinel.context, prim) 2050 indirection_api = MyNSObj.indirection_api 2051 indirection_api.object_backport_versions.assert_called_once_with( 2052 mock.sentinel.context, prim, {'MyNSObj': '1.0'}) 2053 2054 @mock.patch('oslo_versionedobjects.base.VersionedObject.indirection_api') 2055 def test_serializer_calls_old_backport_interface(self, indirection_api): 2056 @base.VersionedObjectRegistry.register 2057 class MyOldObj(base.VersionedObject): 2058 pass 2059 2060 ser = base.VersionedObjectSerializer() 2061 prim = MyOldObj(foo=1).obj_to_primitive() 2062 prim['versioned_object.version'] = '2.0' 2063 indirection_api.object_backport_versions.side_effect = ( 2064 NotImplementedError('Old')) 2065 ser.deserialize_entity(mock.sentinel.context, prim) 2066 indirection_api.object_backport.assert_called_once_with( 2067 mock.sentinel.context, prim, '1.0') 2068 2069 2070class TestSchemaGeneration(test.TestCase): 2071 @base.VersionedObjectRegistry.register 2072 class FakeObject(base.VersionedObject): 2073 fields = { 2074 'a_boolean': fields.BooleanField(nullable=True), 2075 } 2076 2077 @base.VersionedObjectRegistry.register 2078 class FakeComplexObject(base.VersionedObject): 2079 fields = { 2080 'a_dict': fields.DictOfListOfStringsField(), 2081 'an_obj': fields.ObjectField('FakeObject', nullable=True), 2082 'list_of_objs': fields.ListOfObjectsField('FakeObject'), 2083 } 2084 2085 def test_to_json_schema(self): 2086 schema = self.FakeObject.to_json_schema() 2087 self.assertEqual({ 2088 '$schema': 'http://json-schema.org/draft-04/schema#', 2089 'title': 'FakeObject', 2090 'type': ['object'], 2091 'properties': { 2092 'versioned_object.namespace': { 2093 'type': 'string' 2094 }, 2095 'versioned_object.name': { 2096 'type': 'string' 2097 }, 2098 'versioned_object.version': { 2099 'type': 'string' 2100 }, 2101 'versioned_object.changes': { 2102 'type': 'array', 2103 'items': { 2104 'type': 'string' 2105 } 2106 }, 2107 'versioned_object.data': { 2108 'type': 'object', 2109 'description': 'fields of FakeObject', 2110 'properties': { 2111 'a_boolean': { 2112 'readonly': False, 2113 'type': ['boolean', 'null']}, 2114 }, 2115 }, 2116 }, 2117 'required': ['versioned_object.namespace', 'versioned_object.name', 2118 'versioned_object.version', 'versioned_object.data'] 2119 }, schema) 2120 2121 jsonschema.validate(self.FakeObject(a_boolean=True).obj_to_primitive(), 2122 self.FakeObject.to_json_schema()) 2123 2124 def test_to_json_schema_complex_object(self): 2125 schema = self.FakeComplexObject.to_json_schema() 2126 expected_schema = { 2127 '$schema': 'http://json-schema.org/draft-04/schema#', 2128 'properties': { 2129 'versioned_object.changes': 2130 {'items': {'type': 'string'}, 'type': 'array'}, 2131 'versioned_object.data': { 2132 'description': 'fields of FakeComplexObject', 2133 'properties': { 2134 'a_dict': { 2135 'readonly': False, 2136 'type': ['object'], 2137 'additionalProperties': { 2138 'type': ['array'], 2139 'readonly': False, 2140 'items': { 2141 'type': ['string'], 2142 'readonly': False}}}, 2143 'an_obj': { 2144 'properties': { 2145 'versioned_object.changes': 2146 {'items': {'type': 'string'}, 2147 'type': 'array'}, 2148 'versioned_object.data': { 2149 'description': 'fields of FakeObject', 2150 'properties': 2151 {'a_boolean': {'readonly': False, 2152 'type': ['boolean', 'null']}}, 2153 'type': 'object'}, 2154 'versioned_object.name': {'type': 'string'}, 2155 'versioned_object.namespace': 2156 {'type': 'string'}, 2157 'versioned_object.version': 2158 {'type': 'string'}}, 2159 'readonly': False, 2160 'required': ['versioned_object.namespace', 2161 'versioned_object.name', 2162 'versioned_object.version', 2163 'versioned_object.data'], 2164 'type': ['object', 'null']}, 2165 'list_of_objs': { 2166 'items': { 2167 'properties': { 2168 'versioned_object.changes': 2169 {'items': {'type': 'string'}, 2170 'type': 'array'}, 2171 'versioned_object.data': { 2172 'description': 'fields of FakeObject', 2173 'properties': { 2174 'a_boolean': { 2175 'readonly': False, 2176 'type': ['boolean', 'null']}}, 2177 'type': 'object'}, 2178 'versioned_object.name': 2179 {'type': 'string'}, 2180 'versioned_object.namespace': 2181 {'type': 'string'}, 2182 'versioned_object.version': 2183 {'type': 'string'}}, 2184 'readonly': False, 2185 'required': ['versioned_object.namespace', 2186 'versioned_object.name', 2187 'versioned_object.version', 2188 'versioned_object.data'], 2189 'type': ['object']}, 2190 'readonly': False, 2191 'type': ['array']}}, 2192 'required': ['a_dict', 'list_of_objs'], 2193 'type': 'object'}, 2194 'versioned_object.name': {'type': 'string'}, 2195 'versioned_object.namespace': {'type': 'string'}, 2196 'versioned_object.version': {'type': 'string'}}, 2197 'required': ['versioned_object.namespace', 2198 'versioned_object.name', 2199 'versioned_object.version', 2200 'versioned_object.data'], 2201 'title': 'FakeComplexObject', 2202 'type': ['object']} 2203 self.assertEqual(expected_schema, schema) 2204 2205 fake_obj = self.FakeComplexObject( 2206 a_dict={'key1': ['foo', 'bar'], 2207 'key2': ['bar', 'baz']}, 2208 an_obj=self.FakeObject(a_boolean=True), 2209 list_of_objs=[self.FakeObject(a_boolean=False), 2210 self.FakeObject(a_boolean=True), 2211 self.FakeObject(a_boolean=False)]) 2212 2213 primitives = fake_obj.obj_to_primitive() 2214 jsonschema.validate(primitives, schema) 2215 2216 2217class TestNamespaceCompatibility(test.TestCase): 2218 def setUp(self): 2219 super(TestNamespaceCompatibility, self).setUp() 2220 2221 @base.VersionedObjectRegistry.register_if(False) 2222 class TestObject(base.VersionedObject): 2223 OBJ_SERIAL_NAMESPACE = 'foo' 2224 OBJ_PROJECT_NAMESPACE = 'tests' 2225 2226 self.test_class = TestObject 2227 2228 def test_obj_primitive_key(self): 2229 self.assertEqual('foo.data', 2230 self.test_class._obj_primitive_key('data')) 2231 2232 def test_obj_primitive_field(self): 2233 primitive = { 2234 'foo.data': mock.sentinel.data, 2235 } 2236 self.assertEqual(mock.sentinel.data, 2237 self.test_class._obj_primitive_field(primitive, 2238 'data')) 2239 2240 def test_obj_primitive_field_namespace(self): 2241 primitive = { 2242 'foo.name': 'TestObject', 2243 'foo.namespace': 'tests', 2244 'foo.version': '1.0', 2245 'foo.data': {}, 2246 } 2247 with mock.patch.object(self.test_class, 'obj_class_from_name'): 2248 self.test_class.obj_from_primitive(primitive) 2249 2250 def test_obj_primitive_field_namespace_wrong(self): 2251 primitive = { 2252 'foo.name': 'TestObject', 2253 'foo.namespace': 'wrong', 2254 'foo.version': '1.0', 2255 'foo.data': {}, 2256 } 2257 self.assertRaises(exception.UnsupportedObjectError, 2258 self.test_class.obj_from_primitive, primitive) 2259 2260 2261class TestUtilityMethods(test.TestCase): 2262 def test_flat(self): 2263 @base.VersionedObjectRegistry.register 2264 class TestObject(base.VersionedObject): 2265 VERSION = '1.23' 2266 fields = {} 2267 2268 tree = base.obj_tree_get_versions('TestObject') 2269 self.assertEqual({'TestObject': '1.23'}, tree) 2270 2271 def test_parent_child(self): 2272 @base.VersionedObjectRegistry.register 2273 class TestChild(base.VersionedObject): 2274 VERSION = '2.34' 2275 2276 @base.VersionedObjectRegistry.register 2277 class TestObject(base.VersionedObject): 2278 VERSION = '1.23' 2279 fields = { 2280 'child': fields.ObjectField('TestChild'), 2281 } 2282 2283 tree = base.obj_tree_get_versions('TestObject') 2284 self.assertEqual({'TestObject': '1.23', 2285 'TestChild': '2.34'}, 2286 tree) 2287 2288 def test_complex(self): 2289 @base.VersionedObjectRegistry.register 2290 class TestChild(base.VersionedObject): 2291 VERSION = '2.34' 2292 2293 @base.VersionedObjectRegistry.register 2294 class TestChildTwo(base.VersionedObject): 2295 VERSION = '4.56' 2296 fields = { 2297 'sibling': fields.ObjectField('TestChild'), 2298 } 2299 2300 @base.VersionedObjectRegistry.register 2301 class TestObject(base.VersionedObject): 2302 VERSION = '1.23' 2303 fields = { 2304 'child': fields.ObjectField('TestChild'), 2305 'childtwo': fields.ListOfObjectsField('TestChildTwo'), 2306 } 2307 2308 tree = base.obj_tree_get_versions('TestObject') 2309 self.assertEqual({'TestObject': '1.23', 2310 'TestChild': '2.34', 2311 'TestChildTwo': '4.56'}, 2312 tree) 2313 2314 def test_complex_loopy(self): 2315 @base.VersionedObjectRegistry.register 2316 class TestChild(base.VersionedObject): 2317 VERSION = '2.34' 2318 fields = { 2319 'sibling': fields.ObjectField('TestChildTwo'), 2320 } 2321 2322 @base.VersionedObjectRegistry.register 2323 class TestChildTwo(base.VersionedObject): 2324 VERSION = '4.56' 2325 fields = { 2326 'sibling': fields.ObjectField('TestChild'), 2327 'parents': fields.ListOfObjectsField('TestObject'), 2328 } 2329 2330 @base.VersionedObjectRegistry.register 2331 class TestObject(base.VersionedObject): 2332 VERSION = '1.23' 2333 fields = { 2334 'child': fields.ObjectField('TestChild'), 2335 'childtwo': fields.ListOfObjectsField('TestChildTwo'), 2336 } 2337 2338 tree = base.obj_tree_get_versions('TestObject') 2339 self.assertEqual({'TestObject': '1.23', 2340 'TestChild': '2.34', 2341 'TestChildTwo': '4.56'}, 2342 tree) 2343 2344 def test_missing_referenced(self): 2345 """Ensure a missing child object is highlighted.""" 2346 @base.VersionedObjectRegistry.register 2347 class TestObjectFoo(base.VersionedObject): 2348 VERSION = '1.23' 2349 fields = { 2350 # note that this object does not exist 2351 'child': fields.ObjectField('TestChildBar'), 2352 } 2353 2354 exc = self.assertRaises(exception.UnregisteredSubobject, 2355 base.obj_tree_get_versions, 2356 'TestObjectFoo') 2357 self.assertIn('TestChildBar is referenced by TestObjectFoo', 2358 exc.format_message()) 2359 2360 2361class TestListObjectConcat(test.TestCase): 2362 def test_list_object_concat(self): 2363 @base.VersionedObjectRegistry.register_if(False) 2364 class MyList(base.ObjectListBase, base.VersionedObject): 2365 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject')} 2366 2367 values = [1, 2, 42] 2368 2369 list1 = MyList(objects=[MyOwnedObject(baz=values[0]), 2370 MyOwnedObject(baz=values[1])]) 2371 list2 = MyList(objects=[MyOwnedObject(baz=values[2])]) 2372 2373 concat_list = list1 + list2 2374 for idx, obj in enumerate(concat_list): 2375 self.assertEqual(values[idx], obj.baz) 2376 2377 # Assert that the original lists are unmodified 2378 self.assertEqual(2, len(list1.objects)) 2379 self.assertEqual(1, list1.objects[0].baz) 2380 self.assertEqual(2, list1.objects[1].baz) 2381 self.assertEqual(1, len(list2.objects)) 2382 self.assertEqual(42, list2.objects[0].baz) 2383 2384 def test_list_object_concat_fails_different_objects(self): 2385 @base.VersionedObjectRegistry.register_if(False) 2386 class MyList(base.ObjectListBase, base.VersionedObject): 2387 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject')} 2388 2389 @base.VersionedObjectRegistry.register_if(False) 2390 class MyList2(base.ObjectListBase, base.VersionedObject): 2391 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject')} 2392 2393 list1 = MyList(objects=[MyOwnedObject(baz=1)]) 2394 list2 = MyList2(objects=[MyOwnedObject(baz=2)]) 2395 2396 def add(x, y): 2397 return x + y 2398 2399 self.assertRaises(TypeError, add, list1, list2) 2400 # Assert that the original lists are unmodified 2401 self.assertEqual(1, len(list1.objects)) 2402 self.assertEqual(1, len(list2.objects)) 2403 self.assertEqual(1, list1.objects[0].baz) 2404 self.assertEqual(2, list2.objects[0].baz) 2405 2406 def test_list_object_concat_fails_extra_fields(self): 2407 @base.VersionedObjectRegistry.register_if(False) 2408 class MyList(base.ObjectListBase, base.VersionedObject): 2409 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject'), 2410 'foo': fields.IntegerField(nullable=True)} 2411 2412 list1 = MyList(objects=[MyOwnedObject(baz=1)]) 2413 list2 = MyList(objects=[MyOwnedObject(baz=2)]) 2414 2415 def add(x, y): 2416 return x + y 2417 2418 self.assertRaises(TypeError, add, list1, list2) 2419 # Assert that the original lists are unmodified 2420 self.assertEqual(1, len(list1.objects)) 2421 self.assertEqual(1, len(list2.objects)) 2422 self.assertEqual(1, list1.objects[0].baz) 2423 self.assertEqual(2, list2.objects[0].baz) 2424 2425 def test_builtin_list_add_fails(self): 2426 @base.VersionedObjectRegistry.register_if(False) 2427 class MyList(base.ObjectListBase, base.VersionedObject): 2428 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject')} 2429 2430 list1 = MyList(objects=[MyOwnedObject(baz=1)]) 2431 2432 def add(obj): 2433 return obj + [] 2434 2435 self.assertRaises(TypeError, add, list1) 2436 2437 def test_builtin_list_radd_fails(self): 2438 @base.VersionedObjectRegistry.register_if(False) 2439 class MyList(base.ObjectListBase, base.VersionedObject): 2440 fields = {'objects': fields.ListOfObjectsField('MyOwnedObject')} 2441 2442 list1 = MyList(objects=[MyOwnedObject(baz=1)]) 2443 2444 def add(obj): 2445 return [] + obj 2446 2447 self.assertRaises(TypeError, add, list1) 2448 2449 2450class TestTimestampedObject(test.TestCase): 2451 """Test TimestampedObject mixin. 2452 2453 Do this by creating an object that uses the mixin and confirm that the 2454 added fields are there and in fact behaves as the DateTimeFields we desire. 2455 """ 2456 2457 def setUp(self): 2458 super(TestTimestampedObject, self).setUp() 2459 2460 @base.VersionedObjectRegistry.register_if(False) 2461 class MyTimestampedObject(base.VersionedObject, 2462 base.TimestampedObject): 2463 fields = { 2464 'field1': fields.Field(fields.String()), 2465 } 2466 2467 self.myclass = MyTimestampedObject 2468 self.my_object = self.myclass(field1='field1') 2469 2470 def test_timestamped_has_fields(self): 2471 self.assertEqual('field1', self.my_object.field1) 2472 self.assertIn('updated_at', self.my_object.fields) 2473 self.assertIn('created_at', self.my_object.fields) 2474 2475 def test_timestamped_holds_timestamps(self): 2476 now = timeutils.utcnow(with_timezone=True) 2477 self.my_object.updated_at = now 2478 self.my_object.created_at = now 2479 self.assertEqual(now, self.my_object.updated_at) 2480 self.assertEqual(now, self.my_object.created_at) 2481 2482 def test_timestamped_rejects_not_timestamps(self): 2483 with testtools.ExpectedException(ValueError, '.*parse date.*'): 2484 self.my_object.updated_at = 'a string' 2485 with testtools.ExpectedException(ValueError, '.*parse date.*'): 2486 self.my_object.created_at = 'a string' 2487