Versioned objects for cloudpulse
Implements: blueprint versioned-objects Change-Id: I3747f4ab99985c1310aa5a301a04382408d48d58
This commit is contained in:
19
cloudpulse/common/__init__.py
Normal file
19
cloudpulse/common/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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 pbr.version
|
||||||
|
import threading
|
||||||
|
|
||||||
|
__version__ = pbr.version.VersionInfo('cloudpulse').version_string()
|
||||||
|
|
||||||
|
# Make a project global TLS trace storage repository
|
||||||
|
TLS = threading.local()
|
19
cloudpulse/objects/__init__.py
Normal file
19
cloudpulse/objects/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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 cloudpulse.objects import cpulse
|
||||||
|
|
||||||
|
Cpulse = cpulse.Cpulse
|
||||||
|
|
||||||
|
__all__ = (Cpulse)
|
108
cloudpulse/objects/base.py
Normal file
108
cloudpulse/objects/base.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Cloudpulse common internal object model"""
|
||||||
|
|
||||||
|
from oslo_versionedobjects import base as ovoo_base
|
||||||
|
from oslo_versionedobjects import fields as ovoo_fields
|
||||||
|
|
||||||
|
from cloudpulse.openstack.common import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger('object')
|
||||||
|
remotable_classmethod = ovoo_base.remotable_classmethod
|
||||||
|
remotable = ovoo_base.remotable
|
||||||
|
|
||||||
|
|
||||||
|
class CloudpulseObjectRegistry(ovoo_base.VersionedObjectRegistry):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CloudpulseObject(ovoo_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 = 'cloudpulse_object'
|
||||||
|
OBJ_PROJECT_NAMESPACE = 'cloudpulse'
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return dict((k, getattr(self, k))
|
||||||
|
for k in self.fields
|
||||||
|
if hasattr(self, k))
|
||||||
|
|
||||||
|
|
||||||
|
class CloudpulseObjectDictCompat(ovoo_base.VersionedObjectDictCompat):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CloudpulsePersistentObject(object):
|
||||||
|
"""Mixin class for Persistent objects.
|
||||||
|
|
||||||
|
This adds the fields that we use in common for all persistent objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
fields = {
|
||||||
|
'created_at': ovoo_fields.DateTimeField(nullable=True),
|
||||||
|
'updated_at': ovoo_fields.DateTimeField(nullable=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectListBase(ovoo_base.ObjectListBase):
|
||||||
|
# TODO(xek): These are for transition to using the oslo base object
|
||||||
|
# and can be removed when we move to it.
|
||||||
|
fields = {
|
||||||
|
'objects': list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _attr_objects_to_primitive(self):
|
||||||
|
"""Serialization of object list."""
|
||||||
|
return [x.obj_to_primitive() for x in self.objects]
|
||||||
|
|
||||||
|
def _attr_objects_from_primitive(self, value):
|
||||||
|
"""Deserialization of object list."""
|
||||||
|
objects = []
|
||||||
|
for entity in value:
|
||||||
|
ctx = self._context
|
||||||
|
obj = CloudpulseObject.obj_from_primitive(entity,
|
||||||
|
context=ctx)
|
||||||
|
objects.append(obj)
|
||||||
|
return objects
|
||||||
|
|
||||||
|
|
||||||
|
class CloudpulseObjectSerializer(ovoo_base.VersionedObjectSerializer):
|
||||||
|
# Base class to use for object hydration
|
||||||
|
OBJ_BASE_CLASS = CloudpulseObject
|
||||||
|
|
||||||
|
|
||||||
|
def obj_to_primitive(obj):
|
||||||
|
"""Recursively turn an object into a python primitive.
|
||||||
|
|
||||||
|
An CloudpulseObject becomes a dict, and anything
|
||||||
|
that implements ObjectListBase becomes a list.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, ObjectListBase):
|
||||||
|
return [obj_to_primitive(x) for x in obj]
|
||||||
|
elif isinstance(obj, CloudpulseObject):
|
||||||
|
result = {}
|
||||||
|
for key in obj.obj_fields:
|
||||||
|
if obj.obj_attr_is_set(key) or key in obj.obj_extra_fields:
|
||||||
|
result[key] = obj_to_primitive(getattr(obj, key))
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return obj
|
205
cloudpulse/objects/cpulse.py
Normal file
205
cloudpulse/objects/cpulse.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
from cloudpulse.common import exception
|
||||||
|
from cloudpulse.common import utils
|
||||||
|
from cloudpulse.db import api as dbapi
|
||||||
|
from cloudpulse.objects import base
|
||||||
|
from cloudpulse.openstack.common._i18n import _LI
|
||||||
|
from cloudpulse.openstack.common import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Status(object):
|
||||||
|
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
|
||||||
|
CREATE_FAILED = 'CREATE_FAILED'
|
||||||
|
CREATED = 'CREATED'
|
||||||
|
UPDATE_IN_PROGRESS = 'UPDATE_IN_PROGRESS'
|
||||||
|
UPDATE_FAILED = 'UPDATE_FAILED'
|
||||||
|
UPDATED = 'UPDATED'
|
||||||
|
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
|
||||||
|
DELETE_FAILED = 'DELETE_FAILED'
|
||||||
|
DELETED = 'DELETED'
|
||||||
|
|
||||||
|
|
||||||
|
@base.CloudpulseObjectRegistry.register
|
||||||
|
class Cpulse(base.CloudpulsePersistentObject, base.CloudpulseObject,
|
||||||
|
base.CloudpulseObjectDictCompat):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
dbapi = dbapi.get_instance()
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'uuid': fields.UUIDField(nullable=True),
|
||||||
|
'name': fields.StringField(nullable=True),
|
||||||
|
'state': fields.StringField(nullable=True),
|
||||||
|
'result': fields.StringField(nullable=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object(test, db):
|
||||||
|
"""Converts a database entity to a formal object."""
|
||||||
|
for field in test.fields:
|
||||||
|
test[field] = db[field]
|
||||||
|
|
||||||
|
test.obj_reset_changes()
|
||||||
|
return test
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object_list(db_objects, cls, ctx):
|
||||||
|
"""Converts a list of db entities to a list of formal objects."""
|
||||||
|
return [Cpulse._from_db_object(cls(ctx), obj) for obj in db_objects]
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get(cls, context, test_id):
|
||||||
|
"""Find a test based on its id or uuid and return a Cpulse object.
|
||||||
|
|
||||||
|
:param test_id: the id *or* uuid of a test.
|
||||||
|
:returns: a :class:`Cpulse` object.
|
||||||
|
"""
|
||||||
|
if utils.is_int_like(test_id):
|
||||||
|
return cls.get_by_id(context, test_id)
|
||||||
|
elif utils.is_uuid_like(test_id):
|
||||||
|
return cls.get_by_uuid(context, test_id)
|
||||||
|
else:
|
||||||
|
raise exception.InvalidIdentity(identity=test_id)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_id(cls, context, test_id):
|
||||||
|
"""Find a test based on its integer id and return a Cpulse object.
|
||||||
|
|
||||||
|
:param test_id: the id of a test.
|
||||||
|
:returns: a :class:`Cpulse` object.
|
||||||
|
"""
|
||||||
|
db = cls.dbapi.get_test_by_id(context, test_id)
|
||||||
|
test = Cpulse._from_db_object(cls(context), db)
|
||||||
|
return test
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_uuid(cls, context, uuid):
|
||||||
|
"""Find a test based on uuid and return a :class:`Cpulse` object.
|
||||||
|
|
||||||
|
:param uuid: the uuid of a test.
|
||||||
|
:param context: Security context
|
||||||
|
:returns: a :class:`Cpulse` object.
|
||||||
|
"""
|
||||||
|
db = cls.dbapi.get_test_by_uuid(context, uuid)
|
||||||
|
test = Cpulse._from_db_object(cls(context), db)
|
||||||
|
return test
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_name(cls, context, name):
|
||||||
|
"""Find a test based on name and return a Cpulse object.
|
||||||
|
|
||||||
|
:param name: the logical name of a test.
|
||||||
|
:param context: Security context
|
||||||
|
:returns: a :class:`Cpulse` object.
|
||||||
|
"""
|
||||||
|
db = cls.dbapi.get_test_by_name(context, name)
|
||||||
|
test = Cpulse._from_db_object(cls(context), db)
|
||||||
|
return test
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def list(cls, context, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None):
|
||||||
|
"""Return a list of Cpulse objects.
|
||||||
|
|
||||||
|
:param context: Security context.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param sort_key: column to sort results by.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc".
|
||||||
|
:returns: a list of :class:`Cpulse` object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
db = cls.dbapi.get_test_list(context, limit=limit,
|
||||||
|
marker=marker,
|
||||||
|
sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
return Cpulse._from_db_object_list(db, cls, context)
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def create(self, context=None):
|
||||||
|
"""Create a Cpulse record in the DB.
|
||||||
|
|
||||||
|
:param context: Security context. NOTE: This should only
|
||||||
|
be used internally by the indirection_api.
|
||||||
|
Unfortunately, RPC requires context as the first
|
||||||
|
argument, even though we don't use it.
|
||||||
|
A context should be set when instantiating the
|
||||||
|
object, e.g.: Cpulse(context)
|
||||||
|
|
||||||
|
"""
|
||||||
|
values = self.obj_get_changes()
|
||||||
|
LOG.info(_LI('Dumping CREATE test datastructure %s') % str(values))
|
||||||
|
db = self.dbapi.create_test(values)
|
||||||
|
self._from_db_object(self, db)
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def destroy(self, context=None):
|
||||||
|
"""Delete the Cpulse from the DB.
|
||||||
|
|
||||||
|
:param context: Security context. NOTE: This should only
|
||||||
|
be used internally by the indirection_api.
|
||||||
|
Unfortunately, RPC requires context as the first
|
||||||
|
argument, even though we don't use it.
|
||||||
|
A context should be set when instantiating the
|
||||||
|
object, e.g.: Cpulse(context)
|
||||||
|
"""
|
||||||
|
self.dbapi.destroy_test(self.uuid)
|
||||||
|
self.obj_reset_changes()
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def save(self, context=None):
|
||||||
|
"""Save updates to this Cpulse.
|
||||||
|
|
||||||
|
Updates will be made column by column based on the result
|
||||||
|
of self.what_changed().
|
||||||
|
|
||||||
|
:param context: Security context. NOTE: This should only
|
||||||
|
be used internally by the indirection_api.
|
||||||
|
Unfortunately, RPC requires context as the first
|
||||||
|
argument, even though we don't use it.
|
||||||
|
A context should be set when instantiating the
|
||||||
|
object, e.g.: Cpulse(context)
|
||||||
|
"""
|
||||||
|
updates = self.obj_get_changes()
|
||||||
|
self.dbapi.update_test(self.uuid, updates)
|
||||||
|
|
||||||
|
self.obj_reset_changes()
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def refresh(self, context=None):
|
||||||
|
"""Loads updates for this Cpulse.
|
||||||
|
|
||||||
|
Loads a test with the same uuid from the database and
|
||||||
|
checks for updated attributes. Updates are applied from
|
||||||
|
the loaded test column by column, if there are any updates.
|
||||||
|
|
||||||
|
:param context: Security context. NOTE: This should only
|
||||||
|
be used internally by the indirection_api.
|
||||||
|
Unfortunately, RPC requires context as the first
|
||||||
|
argument, even though we don't use it.
|
||||||
|
A context should be set when instantiating the
|
||||||
|
object, e.g.: Cpulse(context)
|
||||||
|
"""
|
||||||
|
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||||
|
for field in self.fields:
|
||||||
|
if self.obj_attr_is_set(field) and self[field] != current[field]:
|
||||||
|
self[field] = current[field]
|
19
cloudpulse/objects/fields.py
Normal file
19
cloudpulse/objects/fields.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2015 Intel Corp.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
class ListOfDictsField(fields.AutoTypedField):
|
||||||
|
AUTO_TYPE = fields.List(fields.Dict(fields.FieldType()))
|
134
cloudpulse/objects/utils.py
Normal file
134
cloudpulse/objects/utils.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Utility methods for objects"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import iso8601
|
||||||
|
import netaddr
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cloudpulse.openstack.common._i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_or_none(dt):
|
||||||
|
"""Validate a datetime or None value."""
|
||||||
|
if dt is None:
|
||||||
|
return None
|
||||||
|
elif isinstance(dt, datetime.datetime):
|
||||||
|
if dt.utcoffset() is None:
|
||||||
|
# NOTE(danms): Legacy objects from sqlalchemy are stored in UTC,
|
||||||
|
# but are returned without a timezone attached.
|
||||||
|
# As a transitional aid, assume a tz-naive object is in UTC.
|
||||||
|
return dt.replace(tzinfo=iso8601.iso8601.Utc())
|
||||||
|
else:
|
||||||
|
return dt
|
||||||
|
raise ValueError(_("A datetime.datetime is required here"))
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_or_str_or_none(val):
|
||||||
|
if isinstance(val, six.string_types):
|
||||||
|
return timeutils.parse_isotime(val)
|
||||||
|
return datetime_or_none(val)
|
||||||
|
|
||||||
|
|
||||||
|
def int_or_none(val):
|
||||||
|
"""Attempt to parse an integer value, or None."""
|
||||||
|
if val is None:
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
return int(val)
|
||||||
|
|
||||||
|
|
||||||
|
def str_or_none(val):
|
||||||
|
"""Attempt to stringify a value to unicode, or None."""
|
||||||
|
if val is None:
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
return six.text_type(val)
|
||||||
|
|
||||||
|
|
||||||
|
def dict_or_none(val):
|
||||||
|
"""Attempt to dictify a value, or None."""
|
||||||
|
if val is None:
|
||||||
|
return {}
|
||||||
|
elif isinstance(val, six.string_types):
|
||||||
|
return dict(ast.literal_eval(val))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return dict(val)
|
||||||
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def list_or_none(val):
|
||||||
|
"""Attempt to listify a value, or None."""
|
||||||
|
if val is None:
|
||||||
|
return []
|
||||||
|
elif isinstance(val, six.string_types):
|
||||||
|
return list(ast.literal_eval(val))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return list(val)
|
||||||
|
except ValueError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def ip_or_none(version):
|
||||||
|
"""Return a version-specific IP address validator."""
|
||||||
|
def validator(val, version=version):
|
||||||
|
if val is None:
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
return netaddr.IPAddress(val, version=version)
|
||||||
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
def nested_object_or_none(objclass):
|
||||||
|
def validator(val, objclass=objclass):
|
||||||
|
if val is None or isinstance(val, objclass):
|
||||||
|
return val
|
||||||
|
raise ValueError(_("An object of class %s is required here")
|
||||||
|
% objclass)
|
||||||
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
def dt_serializer(name):
|
||||||
|
"""Return a datetime serializer for a named attribute."""
|
||||||
|
def serializer(self, name=name):
|
||||||
|
if getattr(self, name) is not None:
|
||||||
|
return timeutils.isotime(getattr(self, name))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
return serializer
|
||||||
|
|
||||||
|
|
||||||
|
def dt_deserializer(instance, val):
|
||||||
|
"""A deserializer method for datetime attributes."""
|
||||||
|
if val is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return timeutils.parse_isotime(val)
|
||||||
|
|
||||||
|
|
||||||
|
def obj_serializer(name):
|
||||||
|
def serializer(self, name=name):
|
||||||
|
if getattr(self, name) is not None:
|
||||||
|
return getattr(self, name).obj_to_primitive()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
return serializer
|
@@ -15,7 +15,11 @@ oslo.serialization>=1.4.0,<1.5.0 # Apache-2.0
|
|||||||
oslo.utils>=1.4.0,<1.5.0 # Apache-2.0
|
oslo.utils>=1.4.0,<1.5.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=0.1.1,<0.2.0
|
oslo.versionedobjects>=0.1.1,<0.2.0
|
||||||
oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0
|
oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0
|
||||||
|
paramiko>=1.13.0
|
||||||
|
pecan>=0.8.0
|
||||||
|
python-keystoneclient>=1.1.0
|
||||||
six>=1.9.0
|
six>=1.9.0
|
||||||
SQLAlchemy>=0.9.7,<=0.9.99
|
SQLAlchemy>=0.9.7,<=0.9.99
|
||||||
taskflow>=0.7.1,<0.8.0
|
taskflow>=0.7.1,<0.8.0
|
||||||
|
WSME>=0.6
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user