Merge "ovo: Introduce standard attributes to objects"
This commit is contained in:
commit
4cdd1b1fc6
@ -23,7 +23,9 @@ import six
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db import model_base
|
||||
from neutron.objects.db import api as obj_db_api
|
||||
from neutron.objects.extensions import standardattributes
|
||||
|
||||
|
||||
class NeutronObjectUpdateForbidden(exceptions.NeutronException):
|
||||
@ -146,6 +148,9 @@ class DeclarativeObject(abc.ABCMeta):
|
||||
cls.fields_no_update += base.primary_keys
|
||||
# avoid duplicate entries
|
||||
cls.fields_no_update = list(set(cls.fields_no_update))
|
||||
if (hasattr(cls, 'has_standard_attributes') and
|
||||
cls.has_standard_attributes()):
|
||||
standardattributes.add_standard_attributes(cls)
|
||||
|
||||
|
||||
@six.add_metaclass(DeclarativeObject)
|
||||
@ -179,6 +184,11 @@ class NeutronDbObject(NeutronObject):
|
||||
self.load_synthetic_db_fields()
|
||||
self.obj_reset_changes()
|
||||
|
||||
@classmethod
|
||||
def has_standard_attributes(cls):
|
||||
return bool(cls.db_model and
|
||||
issubclass(cls.db_model, model_base.HasStandardAttributes))
|
||||
|
||||
@classmethod
|
||||
def modify_fields_to_db(cls, fields):
|
||||
"""
|
||||
@ -201,20 +211,22 @@ class NeutronDbObject(NeutronObject):
|
||||
|
||||
@classmethod
|
||||
def modify_fields_from_db(cls, db_obj):
|
||||
"""
|
||||
This method enables to modify the fields and its
|
||||
content after data was fetched from DB.
|
||||
"""Modify the fields after data were fetched from DB.
|
||||
|
||||
It uses the fields_need_translation dict with structure:
|
||||
{
|
||||
'field_name_in_object': 'field_name_in_db'
|
||||
}
|
||||
|
||||
:param db_obj: dict of object fetched from database
|
||||
:param db_obj: model fetched from database
|
||||
:return: modified dict of DB values
|
||||
"""
|
||||
result = {field: value for field, value in dict(db_obj).items()
|
||||
if value is not None}
|
||||
# db models can have declarative proxies that are not exposed into
|
||||
# db.keys() so we must fetch data based on object fields definition
|
||||
potential_fields = (list(cls.fields.keys()) +
|
||||
list(cls.fields_need_translation.values()))
|
||||
result = {field: db_obj[field] for field in potential_fields
|
||||
if db_obj.get(field) is not None}
|
||||
for field, field_db in cls.fields_need_translation.items():
|
||||
if field_db in result:
|
||||
result[field] = result.pop(field_db)
|
||||
|
0
neutron/objects/extensions/__init__.py
Normal file
0
neutron/objects/extensions/__init__.py
Normal file
26
neutron/objects/extensions/standardattributes.py
Normal file
26
neutron/objects/extensions/standardattributes.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
|
||||
STANDARD_ATTRIBUTES = {
|
||||
'description': obj_fields.StringField(),
|
||||
'created_at': obj_fields.DateTimeField(nullable=True, tzinfo_aware=False),
|
||||
'updated_at': obj_fields.DateTimeField(nullable=True, tzinfo_aware=False),
|
||||
}
|
||||
|
||||
|
||||
def add_standard_attributes(cls):
|
||||
# Don't use parent's fields in case child class doesn't create
|
||||
# its own instance of list
|
||||
cls.fields = cls.fields.copy()
|
||||
cls.fields.update(STANDARD_ATTRIBUTES)
|
0
neutron/tests/unit/objects/extensions/__init__.py
Normal file
0
neutron/tests/unit/objects/extensions/__init__.py
Normal file
@ -0,0 +1,47 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_versionedobjects import base as obj_base
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import model_base
|
||||
from neutron.objects import base as objects_base
|
||||
from neutron.tests.unit.objects import test_base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class FakeDbModelWithStandardAttributes(
|
||||
model_base.HasStandardAttributes, model_base.BASEV2):
|
||||
id = sa.Column(sa.String(36), primary_key=True, nullable=False)
|
||||
item = sa.Column(sa.String(64))
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register_if(False)
|
||||
class FakeObjectWithStandardAttributes(objects_base.NeutronDbObject):
|
||||
VERSION = '1.0'
|
||||
db_model = FakeDbModelWithStandardAttributes
|
||||
fields = {
|
||||
'id': obj_fields.UUIDField(),
|
||||
'item': obj_fields.StringField(),
|
||||
}
|
||||
|
||||
|
||||
class HasStandardAttributesDbTestCase(test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
_test_class = FakeObjectWithStandardAttributes
|
||||
|
||||
|
||||
class HasStandardAttributesTestCase(test_base.BaseObjectIfaceTestCase):
|
||||
_test_class = FakeObjectWithStandardAttributes
|
@ -16,6 +16,7 @@ import random
|
||||
|
||||
import mock
|
||||
from oslo_db import exception as obj_exc
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
from oslo_versionedobjects import base as obj_base
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
@ -36,6 +37,7 @@ from neutron.tests import tools
|
||||
SQLALCHEMY_COMMIT = 'sqlalchemy.engine.Connection._commit_impl'
|
||||
OBJECTS_BASE_OBJ_FROM_PRIMITIVE = ('oslo_versionedobjects.base.'
|
||||
'VersionedObject.obj_from_primitive')
|
||||
TIMESTAMP_FIELDS = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
class FakeModel(object):
|
||||
@ -237,6 +239,7 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = {
|
||||
common_types.IPNetworkPrefixLenField: tools.get_random_prefixlen,
|
||||
common_types.ListOfIPNetworksField: get_list_of_random_networks,
|
||||
common_types.IPVersionEnumField: tools.get_random_ip_version,
|
||||
obj_fields.DateTimeField: timeutils.utcnow,
|
||||
}
|
||||
|
||||
|
||||
@ -245,6 +248,11 @@ def get_obj_db_fields(obj):
|
||||
if field not in obj.synthetic_fields}
|
||||
|
||||
|
||||
def remove_timestamps_from_fields(obj_fields):
|
||||
return {field: value for field, value in obj_fields.items()
|
||||
if field not in TIMESTAMP_FIELDS}
|
||||
|
||||
|
||||
class _BaseObjectTestCase(object):
|
||||
|
||||
_test_class = FakeNeutronObject
|
||||
@ -474,6 +482,10 @@ class BaseObjectIfaceTestCase(_BaseObjectTestCase, test_base.BaseTestCase):
|
||||
with mock.patch.object(obj_db_api, 'get_objects',
|
||||
side_effect=self.fake_get_objects):
|
||||
obj = self._test_class(self.context, **self.db_obj)
|
||||
# get new values and fix keys
|
||||
update_mock.return_value = self.db_objs[1].copy()
|
||||
for key, value in obj._get_composite_keys().items():
|
||||
update_mock.return_value[key] = value
|
||||
obj.update()
|
||||
update_mock.assert_called_once_with(
|
||||
self.context, self._test_class.db_model,
|
||||
@ -631,8 +643,14 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
'device_id': 'fake_device',
|
||||
'device_owner': 'fake_owner'})
|
||||
|
||||
def _make_object(self, fields):
|
||||
return self._test_class(
|
||||
self.context, **remove_timestamps_from_fields(fields))
|
||||
|
||||
def test_get_object_create_update_delete(self):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
# Timestamps can't be initialized and multiple objects may use standard
|
||||
# attributes so we need to remove timestamps when creating objects
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.create()
|
||||
|
||||
new = self._test_class.get_object(self.context,
|
||||
@ -657,7 +675,7 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
self.assertIsNone(new)
|
||||
|
||||
def test_update_non_existent_object_raises_not_found(self):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.obj_reset_changes()
|
||||
|
||||
fields_to_update = self.get_updatable_fields(self.obj_fields[0])
|
||||
@ -670,17 +688,17 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
self.assertRaises(n_exc.ObjectNotFound, obj.update)
|
||||
|
||||
def test_delete_non_existent_object_raises_not_found(self):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
self.assertRaises(n_exc.ObjectNotFound, obj.delete)
|
||||
|
||||
@mock.patch(SQLALCHEMY_COMMIT)
|
||||
def test_create_single_transaction(self, mock_commit):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.create()
|
||||
self.assertEqual(1, mock_commit.call_count)
|
||||
|
||||
def test_update_single_transaction(self):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.create()
|
||||
|
||||
fields_to_update = self.get_updatable_fields(self.obj_fields[1])
|
||||
@ -695,7 +713,7 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
self.assertEqual(1, mock_commit.call_count)
|
||||
|
||||
def test_delete_single_transaction(self):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.create()
|
||||
|
||||
with mock.patch(SQLALCHEMY_COMMIT) as mock_commit:
|
||||
@ -709,7 +727,7 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
|
||||
@mock.patch(SQLALCHEMY_COMMIT)
|
||||
def test_get_object_single_transaction(self, mock_commit):
|
||||
obj = self._test_class(self.context, **self.obj_fields[0])
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj.create()
|
||||
|
||||
obj = self._test_class.get_object(self.context,
|
||||
|
@ -32,7 +32,7 @@ object_data = {
|
||||
'QosDscpMarkingRule': '1.1-0313c6554b34fd10c753cb63d638256c',
|
||||
'QosRuleType': '1.1-8a53fef4c6a43839d477a85b787d22ce',
|
||||
'QosPolicy': '1.1-721fa60ea8f0e8f15d456d6e917dfe59',
|
||||
'SubnetPool': '1.0-6e03cee0148ced4a60dd8342fed3d0be',
|
||||
'SubnetPool': '1.0-320598830183ee739cbc9f32ebc26bba',
|
||||
'SubnetPoolPrefix': '1.0-13c15144135eb869faa4a76dc3ee3b6c',
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,11 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||
mock.patch('neutron.objects.db.api.get_object').start()
|
||||
mock.patch(
|
||||
'neutron.objects.qos.policy.QosPolicy.obj_load_attr').start()
|
||||
# We don't use real models as per mocks above. We also need to mock-out
|
||||
# methods that work with real data types
|
||||
mock.patch(
|
||||
'neutron.objects.base.NeutronDbObject.modify_fields_from_db'
|
||||
).start()
|
||||
|
||||
cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS)
|
||||
cfg.CONF.set_override("service_plugins", ["qos"])
|
||||
|
Loading…
x
Reference in New Issue
Block a user