Versioned objects for cloudpulse

Implements: blueprint versioned-objects

Change-Id: I3747f4ab99985c1310aa5a301a04382408d48d58
This commit is contained in:
vinod pandarinathan 2015-06-06 18:29:46 -07:00
parent 1d615e954e
commit 9ad6b58e37
7 changed files with 508 additions and 0 deletions

View 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()

View 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
View 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

View 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]

View 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
View 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

View File

@ -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.versionedobjects>=0.1.1,<0.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
SQLAlchemy>=0.9.7,<=0.9.99
taskflow>=0.7.1,<0.8.0
WSME>=0.6