Add Nimble base object

Change-Id: I9e9f11c04ce7833adde5bf26203662c2bc45bfdf
This commit is contained in:
Zhenguo Niu 2016-08-17 13:27:24 +08:00
parent 4deba007f1
commit d004abb331
5 changed files with 242 additions and 0 deletions

View File

@ -118,3 +118,7 @@ class NotAcceptable(NimbleException):
class ConfigInvalid(NimbleException):
_msg_fmt = _("Invalid configuration file. %(error_msg)s")
class InvalidMAC(Invalid):
_msg_fmt = _("Expected a MAC address but received %(mac)s.")

View File

@ -15,9 +15,12 @@
"""Utilities and helper functions."""
import re
from oslo_log import log as logging
import six
from nimble.common import exception
from nimble.common.i18n import _LW
LOG = logging.getLogger(__name__)
@ -38,3 +41,34 @@ def safe_rstrip(value, chars=None):
return value
return value.rstrip(chars) or value
def is_valid_mac(address):
"""Verify the format of a MAC address.
Check if a MAC address is valid and contains six octets. Accepts
colon-separated format only.
:param address: MAC address to be validated.
:returns: True if valid. False if not.
"""
m = "[0-9a-f]{2}(:[0-9a-f]{2}){5}$"
return (isinstance(address, six.string_types) and
re.match(m, address.lower()))
def validate_and_normalize_mac(address):
"""Validate a MAC address and return normalized form.
Checks whether the supplied MAC address is formally correct and
normalize it to all lower case.
:param address: MAC address to be validated and normalized.
:returns: Normalized and validated MAC address.
:raises: InvalidMAC If the MAC address is not valid.
"""
if not is_valid_mac(address):
raise exception.InvalidMAC(mac=address)
return address.lower()

View File

96
nimble/objects/base.py Normal file
View File

@ -0,0 +1,96 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# 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.
"""Nimble common internal object model"""
from oslo_utils import versionutils
from oslo_versionedobjects import base as object_base
from nimble import objects
from nimble.objects import fields as object_fields
class NimbleObjectRegistry(object_base.VersionedObjectRegistry):
def registration_hook(self, cls, index):
# NOTE(jroll): blatantly stolen from nova
# NOTE(danms): This is called when an object is registered,
# and is responsible for maintaining nimble.objects.$OBJECT
# as the highest-versioned implementation of a given object.
version = versionutils.convert_version_to_tuple(cls.VERSION)
if not hasattr(objects, cls.obj_name()):
setattr(objects, cls.obj_name(), cls)
else:
cur_version = versionutils.convert_version_to_tuple(
getattr(objects, cls.obj_name()).VERSION)
if version >= cur_version:
setattr(objects, cls.obj_name(), cls)
class NimbleObject(object_base.VersionedObject):
"""Base class and object factory.
This forms the base of all objects that can be remoted or instantiated
via RPC. Simply defining a class that inherits from this base class
will make it remotely instantiatable. Objects should implement the
necessary "get" classmethod routines as well as "save" object methods
as appropriate.
"""
OBJ_SERIAL_NAMESPACE = 'nimble_object'
OBJ_PROJECT_NAMESPACE = 'nimble'
# TODO(lintan) Refactor these fields and create PersistentObject and
# TimeStampObject like Nova when it is necessary.
fields = {
'created_at': object_fields.DateTimeField(nullable=True),
'updated_at': object_fields.DateTimeField(nullable=True),
}
def as_dict(self):
return dict((k, getattr(self, k))
for k in self.fields
if hasattr(self, k))
def obj_refresh(self, loaded_object):
"""Applies updates for objects that inherit from base.NimbleObject.
Checks for updated attributes in an object. Updates are applied from
the loaded object column by column in comparison with the current
object.
"""
for field in self.fields:
if (self.obj_attr_is_set(field) and
self[field] != loaded_object[field]):
self[field] = loaded_object[field]
@staticmethod
def _from_db_object(obj, db_object):
"""Converts a database entity to a formal object.
:param obj: An object of the class.
:param db_object: A DB model of the object
:return: The object of the class with the database entity added
"""
for field in obj.fields:
obj[field] = db_object[field]
obj.obj_reset_changes()
return obj
class NimbleObjectSerializer(object_base.VersionedObjectSerializer):
# Base class to use for object hydration
OBJ_BASE_CLASS = NimbleObject

108
nimble/objects/fields.py Normal file
View File

@ -0,0 +1,108 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# 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.
import ast
import hashlib
import inspect
import six
from oslo_versionedobjects import fields as object_fields
from nimble.common import utils
class IntegerField(object_fields.IntegerField):
pass
class UUIDField(object_fields.UUIDField):
pass
class StringField(object_fields.StringField):
pass
class StringAcceptsCallable(object_fields.String):
@staticmethod
def coerce(obj, attr, value):
if callable(value):
value = value()
return super(StringAcceptsCallable, StringAcceptsCallable).coerce(
obj, attr, value)
class StringFieldThatAcceptsCallable(object_fields.StringField):
"""Custom StringField object that allows for functions as default
In some cases we need to allow for dynamic defaults based on configuration
options, this StringField object allows for a function to be passed as a
default, and will only process it at the point the field is coerced
"""
AUTO_TYPE = StringAcceptsCallable()
def __repr__(self):
default = self._default
if (self._default != object_fields.UnspecifiedDefault and
callable(self._default)):
default = "%s-%s" % (
self._default.__name__,
hashlib.md5(inspect.getsource(
self._default).encode()).hexdigest())
return '%s(default=%s,nullable=%s)' % (self._type.__class__.__name__,
default, self._nullable)
class DateTimeField(object_fields.DateTimeField):
pass
class BooleanField(object_fields.BooleanField):
pass
class ListOfStringsField(object_fields.ListOfStringsField):
pass
class FlexibleDict(object_fields.FieldType):
@staticmethod
def coerce(obj, attr, value):
if isinstance(value, six.string_types):
value = ast.literal_eval(value)
return dict(value)
class FlexibleDictField(object_fields.AutoTypedField):
AUTO_TYPE = FlexibleDict()
# TODO(lucasagomes): In our code we've always translated None to {},
# this method makes this field to work like this. But probably won't
# be accepted as-is in the oslo_versionedobjects library
def _null(self, obj, attr):
if self.nullable:
return {}
super(FlexibleDictField, self)._null(obj, attr)
class MACAddress(object_fields.FieldType):
@staticmethod
def coerce(obj, attr, value):
return utils.validate_and_normalize_mac(value)
class MACAddressField(object_fields.AutoTypedField):
AUTO_TYPE = MACAddress()