summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIhar Hrachyshka <ihar.hrachyshka@gmail.com>2018-05-14 21:57:52 +0000
committerIhar Hrachyshka <ihar.hrachyshka@gmail.com>2018-05-15 19:51:49 +0000
commit1a8a15f6309a56087702a0974041fa9850de4f62 (patch)
tree0b0861d4940b3c59c01d2640660519cf483b7062
parent6674ac5a2a0b819c9c590c4aac29b1f6c3b0036b (diff)
objects: avoid deepcopying models in test_db_obj
SQLAlchemy may asynchronously push models out of session cache in which case we may receive DetachedInstanceError. In the test case, instead of deepcopying models to compare, compare each modified attribute independently. This change also includes conversion from InstrumentedLists to regular lists when converting model attributes to object fields. The fact that we were returning InstrumentedLists was always an oversight but it revealed itself after the modification of the test case that is the core of this patch. When converting object fields to db, convert Port's distributed_binding None value to a empty list to reflect that the relationship of the Port database model is a list. It was not an issue before the patch because we were not comparing model attribute for equality but for in-equality before, and so None was always != []. Finally, this patch moves a bunch of TODOs to better reflect where they belong to. Closes-Bug: #1770452 Change-Id: I42cdf540129bd4470ec1a59345db9845a6198328
Notes
Notes (review): Verified+1: Arista CI <arista-openstack-test@aristanetworks.com> Code-Review+2: Slawek Kaplonski <skaplons@redhat.com> Code-Review+2: Miguel Lavalle <miguel.lavalle@huawei.com> Workflow+1: Miguel Lavalle <miguel.lavalle@huawei.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Wed, 16 May 2018 18:54:49 +0000 Reviewed-on: https://review.openstack.org/568390 Project: openstack/neutron Branch: refs/heads/master
-rw-r--r--neutron/objects/base.py5
-rw-r--r--neutron/objects/ports.py17
-rw-r--r--neutron/tests/unit/objects/test_base.py8
3 files changed, 24 insertions, 6 deletions
diff --git a/neutron/objects/base.py b/neutron/objects/base.py
index 4a42392..e835a68 100644
--- a/neutron/objects/base.py
+++ b/neutron/objects/base.py
@@ -28,6 +28,7 @@ from oslo_versionedobjects import base as obj_base
28from oslo_versionedobjects import exception as obj_exception 28from oslo_versionedobjects import exception as obj_exception
29from oslo_versionedobjects import fields as obj_fields 29from oslo_versionedobjects import fields as obj_fields
30import six 30import six
31from sqlalchemy import orm
31 32
32from neutron._i18n import _ 33from neutron._i18n import _
33from neutron.db import api as db_api 34from neutron.db import api as db_api
@@ -484,6 +485,10 @@ class NeutronDbObject(NeutronObject):
484 for field, field_db in cls.fields_need_translation.items(): 485 for field, field_db in cls.fields_need_translation.items():
485 if field_db in result: 486 if field_db in result:
486 result[field] = result.pop(field_db) 487 result[field] = result.pop(field_db)
488 for k, v in result.items():
489 # don't allow sqlalchemy lists to propagate outside
490 if isinstance(v, orm.collections.InstrumentedList):
491 result[k] = list(v)
487 return result 492 return result
488 493
489 @classmethod 494 @classmethod
diff --git a/neutron/objects/ports.py b/neutron/objects/ports.py
index 05a85a3..c03cc27 100644
--- a/neutron/objects/ports.py
+++ b/neutron/objects/ports.py
@@ -395,22 +395,31 @@ class Port(base.NeutronDbObject):
395 return super(Port, cls).get_objects(context, _pager, validate_filters, 395 return super(Port, cls).get_objects(context, _pager, validate_filters,
396 **kwargs) 396 **kwargs)
397 397
398 # TODO(rossella_s): get rid of it once we switch the db model to using
399 # custom types.
400 @classmethod 398 @classmethod
401 def modify_fields_to_db(cls, fields): 399 def modify_fields_to_db(cls, fields):
402 result = super(Port, cls).modify_fields_to_db(fields) 400 result = super(Port, cls).modify_fields_to_db(fields)
401
402 # TODO(rossella_s): get rid of it once we switch the db model to using
403 # custom types.
403 if 'mac_address' in result: 404 if 'mac_address' in result:
404 result['mac_address'] = cls.filter_to_str(result['mac_address']) 405 result['mac_address'] = cls.filter_to_str(result['mac_address'])
406
407 # convert None to []
408 if 'distributed_port_binding' in result:
409 result['distributed_port_binding'] = (
410 result['distributed_port_binding'] or []
411 )
405 return result 412 return result
406 413
407 # TODO(rossella_s): get rid of it once we switch the db model to using
408 # custom types.
409 @classmethod 414 @classmethod
410 def modify_fields_from_db(cls, db_obj): 415 def modify_fields_from_db(cls, db_obj):
411 fields = super(Port, cls).modify_fields_from_db(db_obj) 416 fields = super(Port, cls).modify_fields_from_db(db_obj)
417
418 # TODO(rossella_s): get rid of it once we switch the db model to using
419 # custom types.
412 if 'mac_address' in fields: 420 if 'mac_address' in fields:
413 fields['mac_address'] = utils.AuthenticEUI(fields['mac_address']) 421 fields['mac_address'] = utils.AuthenticEUI(fields['mac_address'])
422
414 distributed_port_binding = fields.get('distributed_binding') 423 distributed_port_binding = fields.get('distributed_binding')
415 if distributed_port_binding: 424 if distributed_port_binding:
416 fields['distributed_binding'] = fields['distributed_binding'][0] 425 fields['distributed_binding'] = fields['distributed_binding'][0]
diff --git a/neutron/tests/unit/objects/test_base.py b/neutron/tests/unit/objects/test_base.py
index 2f55a64..7c5769d 100644
--- a/neutron/tests/unit/objects/test_base.py
+++ b/neutron/tests/unit/objects/test_base.py
@@ -2014,12 +2014,16 @@ class BaseDbObjectTestCase(_BaseObjectTestCase,
2014 2014
2015 fields_to_update = self.get_updatable_fields(self.obj_fields[1]) 2015 fields_to_update = self.get_updatable_fields(self.obj_fields[1])
2016 if fields_to_update: 2016 if fields_to_update:
2017 old_model = copy.deepcopy(obj.db_obj) 2017 old_fields = {}
2018 for key, val in fields_to_update.items(): 2018 for key, val in fields_to_update.items():
2019 db_model_attr = (
2020 obj.fields_need_translation.get(key, key))
2021 old_fields[db_model_attr] = obj.db_obj[db_model_attr]
2019 setattr(obj, key, val) 2022 setattr(obj, key, val)
2020 obj.update() 2023 obj.update()
2021 self.assertIsNotNone(obj.db_obj) 2024 self.assertIsNotNone(obj.db_obj)
2022 self.assertNotEqual(old_model, obj.db_obj) 2025 for k, v in obj.modify_fields_to_db(fields_to_update).items():
2026 self.assertEqual(v, obj.db_obj[k], '%s attribute differs' % k)
2023 2027
2024 obj.delete() 2028 obj.delete()
2025 self.assertIsNone(obj.db_obj) 2029 self.assertIsNone(obj.db_obj)