Improvement to oslo.versionedobject support

This patch contains several improvements to oslo.versionedobject
implementation:

- Added VersionedObjectRegistry
- Fixed usage of DictOfStringsField which is only limited to string
  values by introducing JsonField
- Added 'isotime' function to be compatible with oslo.versionedobject
- Fixed nullability of some fields
- Fixed test cases where non-nullable fields are not assigned
- Fixed backref problems when using cluster_policy object
- Fixed datetime data type problems

Change-Id: I306657b3c3e69c63e0a534baa22ce8a4db16858f
This commit is contained in:
tengqm 2016-05-30 11:10:22 -04:00
parent 2a064c72af
commit 1def001fce
55 changed files with 522 additions and 145 deletions

View File

@ -51,9 +51,8 @@ class ServiceManageCommand(object):
return
status = 'down'
seconds_since_update = (timeutils.utcnow() -
service.updated_at).total_seconds()
if seconds_since_update <= 2 * CONF.periodic_interval:
max_interval = 2 * CONF.periodic_interval
if timeutils.is_older_than(service.updated_at, max_interval):
status = 'up'
result = {

View File

@ -32,6 +32,7 @@ from senlin.common.i18n import _LI
cfg.CONF.import_opt('max_response_size', 'senlin.common.config')
LOG = logging.getLogger(__name__)
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
class URLFetchError(exception.Error, IOError):
@ -173,3 +174,17 @@ def format_time(value):
value = value.replace(microsecond=0)
value = value.isoformat()
return value
def isotime(at):
"""Stringify time in ISO 8601 format.
oslo.versionedobject is using this function for datetime formatting.
"""
if at is None:
return None
st = at.strftime(_ISO8601_TIME_FORMAT)
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
st += ('Z' if tz == 'UTC' else tz)
return st

View File

@ -1157,7 +1157,7 @@ def receiver_delete(context, receiver_id):
def service_create(context, service_id, host=None, binary=None,
topic=None):
with session_for_write() as session:
time_now = timeutils.utcnow()
time_now = timeutils.utcnow(True)
svc = models.Service(id=service_id, host=host, binary=binary,
topic=topic, created_at=time_now,
updated_at=time_now)
@ -1174,7 +1174,7 @@ def service_update(context, service_id, values=None):
if values is None:
values = {}
values.update({'updated_at': timeutils.utcnow()})
values.update({'updated_at': timeutils.utcnow(True)})
service.update(values)
service.save(session)
return service

View File

@ -16,7 +16,7 @@ SQLAlchemy models for Senlin data.
from oslo_db.sqlalchemy import models
from oslo_utils import uuidutils
from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Integer
from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer
from sqlalchemy import String, Text
from sqlalchemy.ext import declarative
from sqlalchemy.orm import backref
@ -29,8 +29,8 @@ UUID4 = uuidutils.generate_uuid
class TimestampMixin(object):
created_at = Column(DateTime)
updated_at = Column(DateTime)
created_at = Column(types.TZAwareDateTime)
updated_at = Column(types.TZAwareDateTime)
class Profile(BASE, TimestampMixin, models.ModelBase):
@ -80,7 +80,7 @@ class Cluster(BASE, TimestampMixin, models.ModelBase):
domain = Column(String(32))
parent = Column(String(36))
init_at = Column(DateTime)
init_at = Column(types.TZAwareDateTime)
min_size = Column(Integer)
max_size = Column(Integer)
@ -111,7 +111,7 @@ class Node(BASE, TimestampMixin, models.ModelBase):
index = Column(Integer)
role = Column(String(64))
init_at = Column(DateTime)
init_at = Column(types.TZAwareDateTime)
status = Column(String(255))
status_reason = Column(Text)
@ -151,7 +151,7 @@ class ClusterPolicies(BASE, models.ModelBase):
enabled = Column(Boolean)
priority = Column(Integer)
data = Column(types.Dict)
last_op = Column(DateTime)
last_op = Column(types.TZAwareDateTime)
class HealthRegistry(BASE, models.ModelBase):
@ -244,7 +244,7 @@ class Event(BASE, models.ModelBase):
__tablename__ = 'event'
id = Column('id', String(36), primary_key=True, default=lambda: UUID4())
timestamp = Column(DateTime)
timestamp = Column(types.TZAwareDateTime)
oid = Column(String(36))
oname = Column(String(255))
otype = Column(String(36))

View File

@ -11,6 +11,7 @@
# under the License.
from oslo_serialization import jsonutils
import pytz
from sqlalchemy.dialects import mysql
from sqlalchemy.ext import mutable
@ -108,5 +109,15 @@ class List(types.TypeDecorator):
return jsonutils.loads(value)
class TZAwareDateTime(types.TypeDecorator):
"""A DB type that is time zone aware."""
impl = types.DateTime
def process_result_value(self, value, dialect):
if value is None:
return None
return value.replace(tzinfo=pytz.utc)
mutable.MutableDict.associate_with(Dict)
MutableList.associate_with(List)

View File

@ -21,6 +21,7 @@ from senlin.common import context as req_context
from senlin.common import exception
from senlin.common.i18n import _
from senlin.common.i18n import _LE
from senlin.common import utils
from senlin.engine import cluster_policy as cp_mod
from senlin.engine import event as EVENT
from senlin.objects import action as ao
@ -151,7 +152,7 @@ class Action(object):
:return: The ID of the stored object.
"""
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
values = {
'name': self.name,
@ -492,8 +493,8 @@ class Action(object):
'outputs': self.outputs,
'depends_on': dep_on,
'depended_by': dep_by,
'created_at': self.created_at,
'updated_at': self.updated_at,
'created_at': utils.isotime(self.created_at),
'updated_at': utils.isotime(self.updated_at),
'data': self.data,
}
return action_dict

View File

@ -120,7 +120,7 @@ class ClusterAction(base.Action):
nodes = []
child = []
for m in range(count):
index = co.Cluster.next_index(self.context, self.cluster.id)
index = co.Cluster.get_next_index(self.context, self.cluster.id)
kwargs = {
'index': index,
'metadata': {},

View File

@ -135,7 +135,7 @@ class Cluster(object):
'data': self.data,
}
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
if self.id:
values['updated_at'] = timestamp
co.Cluster.update(context, self.id, values)
@ -241,7 +241,7 @@ class Cluster(object):
"""
values = {}
now = timeutils.utcnow()
now = timeutils.utcnow(True)
if status == self.ACTIVE and self.status == self.CREATING:
self.created_at = now
values['created_at'] = now

View File

@ -108,7 +108,7 @@ class ClusterPolicy(object):
return False
def record_last_op(self, context):
self.last_op = timeutils.utcnow()
self.last_op = timeutils.utcnow(True)
self.store(context)
def to_dict(self):

View File

@ -82,6 +82,11 @@ class Event(object):
self.cluster_id = entity.node.cluster_id
self.oname = entity.node.name
self.otype = 'NODE'
else:
self.oid = entity.target
self.cluster_id = ''
self.oname = ''
self.otype = ''
def store(self, context):
'''Store the event into database and return its ID.'''
@ -108,7 +113,7 @@ class Event(object):
def critical(context, entity, action, status=None, status_reason=None,
timestamp=None):
timestamp = timestamp or timeutils.utcnow()
timestamp = timestamp or timeutils.utcnow(True)
event = Event(timestamp, logging.CRITICAL, entity,
action=action, status=status, status_reason=status_reason,
user=context.user, project=context.project)
@ -122,7 +127,7 @@ def critical(context, entity, action, status=None, status_reason=None,
def error(context, entity, action, status=None, status_reason=None,
timestamp=None):
timestamp = timestamp or timeutils.utcnow()
timestamp = timestamp or timeutils.utcnow(True)
event = Event(timestamp, logging.ERROR, entity,
action=action, status=status, status_reason=status_reason,
user=context.user, project=context.project)
@ -137,7 +142,7 @@ def error(context, entity, action, status=None, status_reason=None,
def warning(context, entity, action, status=None, status_reason=None,
timestamp=None):
timestamp = timestamp or timeutils.utcnow()
timestamp = timestamp or timeutils.utcnow(True)
event = Event(timestamp, logging.WARNING, entity,
action=action, status=status, status_reason=status_reason,
user=context.user, project=context.project)
@ -152,7 +157,7 @@ def warning(context, entity, action, status=None, status_reason=None,
def info(context, entity, action, status=None, status_reason=None,
timestamp=None):
timestamp = timestamp or timeutils.utcnow()
timestamp = timestamp or timeutils.utcnow(True)
event = Event(timestamp, logging.INFO, entity,
action=action, status=status, status_reason=status_reason,
user=context.user, project=context.project)
@ -167,7 +172,7 @@ def info(context, entity, action, status=None, status_reason=None,
def debug(context, entity, action, status=None, status_reason=None,
timestamp=None):
timestamp = timestamp or timeutils.utcnow()
timestamp = timestamp or timeutils.utcnow(True)
event = Event(timestamp, logging.DEBUG, entity,
action=action, status=status, status_reason=status_reason,
user=context.user, project=context.project)

View File

@ -124,7 +124,7 @@ class Node(object):
if self.id:
no.Node.update(context, self.id, values)
else:
init_at = timeutils.utcnow()
init_at = timeutils.utcnow(True)
self.init_at = init_at
values['init_at'] = init_at
node = no.Node.create(context, values)
@ -212,7 +212,7 @@ class Node(object):
'''Set status of the node.'''
values = {}
now = timeutils.utcnow()
now = timeutils.utcnow(True)
if status == self.ACTIVE and self.status == self.CREATING:
self.created_at = values['created_at'] = now
elif status == self.ACTIVE and self.status == self.UPDATING:
@ -329,7 +329,7 @@ class Node(object):
return True
res = profile_base.Profile.join_cluster(context, self, cluster_id)
if res:
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
db_node = no.Node.migrate(context, self.id, cluster_id, timestamp)
self.cluster_id = cluster_id
self.updated_at = timestamp
@ -344,7 +344,7 @@ class Node(object):
res = profile_base.Profile.leave_cluster(context, self)
if res:
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
no.Node.migrate(context, self.id, None, timestamp)
self.cluster_id = ''
self.updated_at = timestamp

View File

@ -66,7 +66,7 @@ class Receiver(object):
:param context: Context for DB operations.
"""
self.created_at = timeutils.utcnow()
self.created_at = timeutils.utcnow(True)
values = {
'id': self.id,
'name': self.name,

View File

@ -46,7 +46,7 @@ def is_engine_dead(ctx, engine_id, period_time=None):
eng = service_obj.Service.get(ctx, engine_id)
if not eng:
return True
if (timeutils.utcnow() - eng.updated_at).total_seconds() > period_time:
if timeutils.is_older_than(eng.updated_at, period_time):
return True
return False

View File

@ -11,7 +11,6 @@
# under the License.
import copy
import datetime
import functools
import uuid
@ -173,14 +172,13 @@ class EngineService(service.Service):
def service_manage_cleanup(self):
ctx = senlin_context.get_admin_context()
last_updated_window = (2 * cfg.CONF.periodic_interval)
time_line = timeutils.utcnow() - datetime.timedelta(
seconds=last_updated_window)
time_window = (2 * cfg.CONF.periodic_interval)
svcs = service_obj.Service.get_all(ctx)
for svc in svcs:
if svc['id'] == self.engine_id:
continue
if svc['updated_at'] < time_line:
if timeutils.is_older_than(svc['updated_at'], time_window):
# < time_line:
# hasn't been updated, assuming it's died.
LOG.info(_LI('Service %s was aborted'), svc['id'])
service_obj.Service.delete(ctx, svc['id'])
@ -1427,7 +1425,7 @@ class EngineService(service.Service):
'operation aborted.')
LOG.error(msg)
raise exception.ProfileTypeNotMatch(message=msg)
index = cluster_obj.Cluster.next_index(context, cluster_id)
index = cluster_obj.Cluster.get_next_index(context, cluster_id)
# Create a node instance
kwargs = {

View File

@ -0,0 +1,33 @@
# 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.
# When objects are registered, an attribute is set on this module
# automatically, pointing to the latest version of the object.
def register_all():
# Objects should be imported here in order to be registered by services
# that may need to receive it via RPC.
__import__('senlin.objects.action')
__import__('senlin.objects.cluster')
__import__('senlin.objects.cluster_lock')
__import__('senlin.objects.cluster_policy')
__import__('senlin.objects.credential')
__import__('senlin.objects.dependency')
__import__('senlin.objects.event')
__import__('senlin.objects.health_registry')
__import__('senlin.objects.node')
__import__('senlin.objects.node_lock')
__import__('senlin.objects.policy')
__import__('senlin.objects.profile')
__import__('senlin.objects.receiver')
__import__('senlin.objects.service')

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Action(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin action object."""
@ -27,7 +29,7 @@ class Action(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'created_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(nullable=True),
'name': fields.StringField(),
'context': fields.DictOfStringsField(),
'context': senlin_fields.JsonField(),
'target': fields.UUIDField(),
'action': fields.StringField(),
'cause': fields.StringField(),
@ -37,14 +39,14 @@ class Action(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'end_time': fields.FloatField(nullable=True),
'timeout': fields.IntegerField(nullable=True),
'status': fields.StringField(),
'status_reason': fields.StringField(),
'status_reason': fields.StringField(nullable=True),
'control': fields.StringField(nullable=True),
'inputs': fields.DictOfStringsField(nullable=True),
'outputs': fields.DictOfStringsField(nullable=True),
'data': fields.DictOfStringsField(nullable=True),
'inputs': senlin_fields.JsonField(nullable=True),
'outputs': senlin_fields.JsonField(nullable=True),
'data': senlin_fields.JsonField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(),
'domain': fields.StringField(nullable=True),
}
@staticmethod

View File

@ -12,8 +12,11 @@
"""Senlin common internal object model"""
from oslo_utils import versionutils
from oslo_versionedobjects import base
from senlin import objects
class SenlinObject(base.VersionedObject):
"""Base class for senlin objects.
@ -26,3 +29,22 @@ class SenlinObject(base.VersionedObject):
OBJ_PROJECT_NAMESPACE = 'senlin'
VERSION = '1.0'
class SenlinObjectRegistry(base.VersionedObjectRegistry):
def registration_hook(self, cls, index):
"""Callback for object registration.
When an object is registered, this function will be called for
maintaining senlin.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:
curr_version = versionutils.convert_version_to_tuple(
getattr(objects, cls.obj_name()).VERSION)
if version >= curr_version:
setattr(objects, cls.obj_name(), cls)

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Cluster(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin cluster object."""
@ -27,18 +29,18 @@ class Cluster(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'name': fields.StringField(),
'profile_id': fields.UUIDField(),
'parent': fields.UUIDField(nullable=True),
'init_at': fields.DateTimeField(nullable=True),
'init_at': fields.DateTimeField(),
'created_at': fields.DateTimeField(nullable=True),
'updated_at': fields.DateTimeField(nullable=True),
'min_size': fields.IntegerField(nullable=True),
'max_size': fields.IntegerField(nullable=True),
'desired_capacity': fields.IntegerField(nullable=True),
'next_index': fields.IntegerField(),
'timeout': fields.IntegerField(),
'next_index': fields.IntegerField(nullable=True),
'timeout': fields.IntegerField(nullable=True),
'status': fields.StringField(),
'status_reason': fields.StringField(),
'metadata': fields.DictOfStringsField(),
'data': fields.DictOfStringsField(),
'status_reason': fields.StringField(nullable=True),
'metadata': senlin_fields.JsonField(nullable=True),
'data': senlin_fields.JsonField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(nullable=True),
@ -86,7 +88,7 @@ class Cluster(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
return [cls._from_db_object(context, cls(), obj) for obj in objs]
@classmethod
def next_index(cls, context, cluster_id):
def get_next_index(cls, context, cluster_id):
return db_api.cluster_next_index(context, cluster_id)
@classmethod

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class ClusterLock(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin cluster lock object."""

View File

@ -17,8 +17,12 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import cluster as cluster_obj
from senlin.objects import fields as senlin_fields
from senlin.objects import policy as policy_obj
@senlin_base.SenlinObjectRegistry.register
class ClusterPolicy(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin cluster-policy binding object."""
@ -26,12 +30,12 @@ class ClusterPolicy(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'id': fields.UUIDField(),
'cluster_id': fields.UUIDField(),
'policy_id': fields.UUIDField(),
'cluster': fields.ObjectField('Cluster'),
'policy': fields.ObjectField('Policy'),
'cluster': fields.ObjectField('Cluster', nullable=True),
'policy': fields.ObjectField('Policy', nullable=True),
'enabled': fields.BooleanField(),
'priority': fields.IntegerField(),
'data': fields.DictOfStringsField(),
'last_op': fields.DateTimeField(),
'data': senlin_fields.JsonField(nullable=True),
'last_op': fields.DateTimeField(nullable=True),
}
@staticmethod
@ -39,7 +43,14 @@ class ClusterPolicy(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
if db_obj is None:
return None
for field in binding.fields:
binding[field] = db_obj[field]
if field == 'cluster':
c = cluster_obj.Cluster.get(context, db_obj['cluster_id'])
binding['cluster'] = c
elif field == 'policy':
p = policy_obj.Policy.get(context, db_obj['policy_id'])
binding['policy'] = p
else:
binding[field] = db_obj[field]
binding._context = context
binding.obj_reset_changes()

View File

@ -17,16 +17,18 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Credential(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin credential object."""
fields = {
'user': fields.StringField(),
'project': fields.StringField(),
'cred': fields.DictOfStringsField(),
'data': fields.DictOfStringsField(),
'cred': senlin_fields.JsonField(),
'data': senlin_fields.JsonField(nullable=True),
}
@staticmethod

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class Dependency(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin action dependency object."""

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class Event(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin event object."""

88
senlin/objects/fields.py Normal file
View File

@ -0,0 +1,88 @@
# 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_serialization import jsonutils
from oslo_versionedobjects import fields
import six
class Json(fields.FieldType):
def coerce(self, obj, attr, value):
if isinstance(value, six.string_types):
loaded = jsonutils.loads(value)
return loaded
return value
def from_primitive(self, obj, attr, value):
return self.coerce(obj, attr, value)
def to_primitive(self, obj, attr, value):
return jsonutils.dumps(value)
class JsonField(fields.AutoTypedField):
AUTO_TYPE = Json()
class ListField(fields.AutoTypedField):
AUTO_TYPE = fields.List(fields.FieldType())
class NotificationPriority(fields.Enum):
ALL = (
AUDIT, CRITICAL, DEBUG, INFO, ERROR, SAMPLE, WARN,
) = (
'audit', 'critical', 'debug', 'info', 'error', 'sample', 'warn',
)
def __init__(self):
super(NotificationPriority, self).__init__(
valid_values=NotificationPriority.ALL)
class NotificationPhase(fields.Enum):
ALL = (
START, END, ERROR,
) = (
'start', 'end', 'error',
)
def __init__(self):
super(NotificationPhase, self).__init__(
valid_values=NotificationPhase.ALL)
class NotificationAction(fields.Enum):
ALL = (
UPDATE,
) = (
'update',
)
def __init__(self):
super(NotificationAction, self).__init__(
valid_values=NotificationAction.ALL)
class NotificationPriorityField(fields.BaseEnumField):
AUTO_TYPE = NotificationPriority()
class NotificationPhaseField(fields.BaseEnumField):
AUTO_TYPE = NotificationPhase()
class NotificationActionField(fields.BaseEnumField):
AUTO_TYPE = NotificationAction()

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class HealthRegistry(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin health registry object."""

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Node(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin node object."""
@ -26,17 +28,18 @@ class Node(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'id': fields.UUIDField(),
'name': fields.StringField(),
'profile_id': fields.UUIDField(),
'cluster_id': fields.UUIDField(),
'physical_id': fields.UUIDField(),
# This field is treated as string because we may store '' into it
'cluster_id': fields.StringField(),
'physical_id': fields.UUIDField(nullable=True),
'index': fields.IntegerField(),
'role': fields.StringField(nullable=True),
'init_at': fields.DateTimeField(nullable=True),
'init_at': fields.DateTimeField(),
'created_at': fields.DateTimeField(nullable=True),
'updated_at': fields.DateTimeField(nullable=True),
'status': fields.StringField(),
'status_reason': fields.StringField(),
'metadata': fields.DictOfStringsField(),
'data': fields.DictOfStringsField(),
'status_reason': fields.StringField(nullable=True),
'metadata': senlin_fields.JsonField(nullable=True),
'data': senlin_fields.JsonField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(nullable=True),

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class NodeLock(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin node lock object."""

View File

@ -0,0 +1,137 @@
# 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 senlin.objects import base
from senlin.objects import fields
from senlin import rpc
@base.SenlinObjectRegistry.register
class EventType(base.SenlinObject):
VERSION = '1.0'
fields = {
'object': fields.StringField(nullable=False),
'action': fields.NotificationActionField(nullable=False),
'phase': fields.NotificationPhaseField(nullable=True),
}
def to_notification_field(self):
"""Serialize the object to the wire format."""
s = '%s.%s' % (self.object, self.action)
if self.obj_attr_is_set('phase'):
s += '.%s' % self.phase
return s
class NotificationPayloadBase(base.NovaObject):
"""Base class for the payload of versioned notifications."""
# schema is a dict that defines how to populate the payload fields, where
# each key-value pair has the following format:
#
# <payload_field>: (<data_source>, <data_source_field>)
#
# The <payload_field> is the name where the data will be stored in the
# payload object, this field has to be defined as a field of the payload.
# The <data_source> field shall refer to name of the parameter passed as
# kwarg to the payload's populate_schema() call and this object will be
# used as the source of the data.
# The SCHEMA needs to be applied with the populate_schema() call before the
# notification can be emitted.
# The value of the payload.<payload_field> field will be set by the
# <data_source>.<data_source_field> field. The <data_source> will not be
# part of the payload object internal or external representation.
# Payload fields that are not set by the SCHEMA can be filled in the same
# way as in any versioned object.
schema = {}
VERSION = '1.0'
def __init__(self, *args, **kwargs):
super(NotificationPayloadBase, self).__init__(*args, **kwargs)
self.populated = not self.SCHEMA
def populate_schema(self, **kwargs):
"""Populate the object based on the SCHEMA and the source objects
:param kwargs: A dict contains the source object at the key defined in
the SCHEMA
"""
for key, (obj, field) in self.SCHEMA.items():
source = kwargs[obj]
if source.obj_attr_is_set(field):
setattr(self, key, getattr(source, field))
self.populated = True
@base.SenlinObjectRegistry.register
class NotificationPublisher(base.SenlinObject):
VERSION = '1.0'
fields = {
'host': fields.StringField(nullable=False),
'binary': fields.StringField(nullable=False),
}
@classmethod
def from_service_obj(cls, service):
return cls(host=service.host, binary=service.binary)
class NotificationBase(base.SenlinObject):
"""Base class for versioned notifications.
Every subclass shall define a 'payload' field.
"""
VERSION = '1.0'
fields = {
'priority': fields.NotificationPriorityField(),
'event_type': fields.ObjectField('EventType'),
'publisher': fields.ObjectField('NotificationPublisher'),
}
def _emit(self, context, event_type, publisher_id, payload):
notifier = rpc.get_versioned_notifier(publisher_id)
notify = getattr(notifier, self.priority)
notify(context, event_type=event_type, payload=payload)
def emit(self, context):
"""Send the notification."""
assert self.payload.populated
# Note(gibi): notification payload will be a newly populated object
# therefore every field of it will look changed so this does not carry
# any extra information so we drop this from the payload.
self.payload.obj_reset_changes(recursive=False)
self._emit(context,
event_type=self.event_type.to_notification_field(),
publisher_id='%s:%s' %
(self.publisher.binary,
self.publisher.host),
payload=self.payload.obj_to_primitive())
def notification_sample(sample):
"""Class decorator for documentation generation.
This decorator is used to attach the notification sample information
to the notification object for documentation generation purposes.
:param sample: the path of the sample json file relative to the
doc/notification_samples/ directory in the nova repository
root.
"""
def wrap(cls):
cls.sample = sample
return cls
return wrap

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Policy(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin policy object."""
@ -26,12 +28,12 @@ class Policy(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'id': fields.UUIDField(),
'name': fields.StringField(),
'type': fields.StringField(),
'spec': fields.DictOfStringsField(),
'spec': senlin_fields.JsonField(),
'cooldown': fields.IntegerField(nullable=True),
'level': fields.IntegerField(nullable=True),
'data': fields.DictOfStringsField(),
'data': fields.DictOfStringsField(nullable=True),
'created_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(nullable=True),

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Profile(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin profile object."""
@ -26,15 +28,15 @@ class Profile(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'id': fields.UUIDField(),
'name': fields.StringField(),
'type': fields.StringField(),
'context': fields.DictOfStringsField(),
'spec': fields.DictOfStringsField(),
'context': senlin_fields.JsonField(),
'spec': senlin_fields.JsonField(),
'created_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(nullable=True),
'permission': fields.StringField(nullable=True),
'metadata': fields.DictOfStringsField(),
'metadata': fields.DictOfStringsField(nullable=True),
}
@staticmethod

View File

@ -17,8 +17,10 @@ from oslo_versionedobjects import fields
from senlin.db import api as db_api
from senlin.objects import base as senlin_base
from senlin.objects import fields as senlin_fields
@senlin_base.SenlinObjectRegistry.register
class Receiver(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin receiver object."""
@ -27,15 +29,15 @@ class Receiver(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'name': fields.StringField(),
'type': fields.StringField(),
'cluster_id': fields.UUIDField(),
'actor': fields.DictOfStringsField(nullable=True),
'actor': senlin_fields.JsonField(nullable=True),
'action': fields.StringField(),
'params': fields.DictOfStringsField(nullable=True),
'channel': fields.DictOfStringsField(nullable=True),
'params': senlin_fields.JsonField(nullable=True),
'channel': senlin_fields.JsonField(nullable=True),
'created_at': fields.DateTimeField(nullable=True),
'updated_at': fields.DateTimeField(nullable=True),
'user': fields.StringField(),
'project': fields.StringField(),
'domain': fields.StringField(),
'domain': fields.StringField(nullable=True),
}
@staticmethod

View File

@ -19,6 +19,7 @@ from senlin.db import api as db_api
from senlin.objects import base as senlin_base
@senlin_base.SenlinObjectRegistry.register
class Service(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
"""Senlin service object."""
@ -28,7 +29,7 @@ class Service(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
'binary': fields.StringField(),
'topic': fields.StringField(),
'disabled': fields.BooleanField(),
'disabled_reason': fields.StringField(),
'disabled_reason': fields.StringField(nullable=True),
'created_at': fields.DateTimeField(),
'updated_at': fields.DateTimeField(),
}

View File

@ -167,7 +167,7 @@ class Policy(object):
def store(self, context):
'''Store the policy object into database table.'''
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
values = {
'name': self.name,
@ -270,8 +270,8 @@ class Policy(object):
'project': self.project,
'domain': self.domain,
'spec': self.spec,
'created_at': utils.format_time(self.created_at),
'updated_at': utils.format_time(self.updated_at),
'created_at': utils.isotime(self.created_at),
'updated_at': utils.isotime(self.updated_at),
'data': self.data,
}
return pb_dict

View File

@ -160,7 +160,7 @@ class Profile(object):
def store(self, ctx):
'''Store the profile into database and return its ID.'''
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
values = {
'name': self.name,

View File

@ -25,7 +25,7 @@ class NovaClient(base.DriverBase):
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": True,
"id": "1",
"id": "66a81d68-bf48-4af5-897b-a3bfef7279a8",
"links": [],
"name": "m1.tiny",
"ram": 512,

View File

@ -71,7 +71,7 @@ def create_cluster(ctx, profile, **kwargs):
'next_index': 1,
'timeout': 60,
'desired_capacity': 0,
'init_at': tu.utcnow(),
'init_at': tu.utcnow(True),
'status': 'INIT',
'status_reason': 'Just Initialized',
'meta_data': {},
@ -168,7 +168,7 @@ def create_policy(ctx, **kwargs):
def create_event(ctx, **kwargs):
values = {
'timestamp': tu.utcnow(),
'timestamp': tu.utcnow(True),
'obj_id': 'FAKE_ID',
'obj_name': 'FAKE_NAME',
'obj_type': 'CLUSTER',

View File

@ -249,7 +249,7 @@ class DBAPIClusterTest(base.SenlinTestCase):
def test_cluster_get_all_default_sort_dir(self):
clusters = [shared.create_cluster(self.ctx, self.profile,
init_at=tu.utcnow())
init_at=tu.utcnow(True))
for x in range(3)]
st_db = db_api.cluster_get_all(self.ctx)
@ -260,7 +260,7 @@ class DBAPIClusterTest(base.SenlinTestCase):
def test_cluster_get_all_str_sort_keys(self):
clusters = [shared.create_cluster(self.ctx, self.profile,
created_at=tu.utcnow())
created_at=tu.utcnow(True))
for x in range(3)]
st_db = db_api.cluster_get_all(self.ctx, sort='created_at')
@ -282,7 +282,7 @@ class DBAPIClusterTest(base.SenlinTestCase):
def test_cluster_get_all_marker(self):
clusters = [shared.create_cluster(self.ctx, self.profile,
created_at=tu.utcnow())
created_at=tu.utcnow(True))
for x in range(3)]
cl_db = db_api.cluster_get_all(self.ctx, marker=clusters[1].id)
self.assertEqual(1, len(cl_db))

View File

@ -132,7 +132,7 @@ class DBAPIClusterPolicyTest(base.SenlinTestCase):
self.assertEqual(1, len(bindings))
self.assertIsNone(bindings[0].last_op)
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
fields = {'last_op': timestamp}
db_api.cluster_policy_update(self.ctx, self.cluster.id, policy.id,
fields)

View File

@ -74,7 +74,9 @@ class DBAPIEventTest(base.SenlinTestCase):
self.assertIsNotNone(ret_event)
tst_timestamp = tu.parse_strtime('2014-12-19 11:51:54.670244',
'%Y-%m-%d %H:%M:%S.%f')
self.assertEqual(tst_timestamp, ret_event.timestamp)
self.assertEqual(tu.isotime(tst_timestamp),
tu.isotime(ret_event.timestamp))
self.assertEqual('20', ret_event.level)
self.assertEqual('', ret_event.oid)
self.assertEqual('', ret_event.otype)
@ -193,13 +195,13 @@ class DBAPIEventTest(base.SenlinTestCase):
cluster1 = shared.create_cluster(self.ctx, self.profile)
event1 = self.create_event(self.ctx, entity=cluster1,
timestamp=tu.utcnow(),
timestamp=tu.utcnow(True),
action='action2')
event2 = self.create_event(self.ctx, entity=cluster1,
timestamp=tu.utcnow(),
timestamp=tu.utcnow(True),
action='action3')
event3 = self.create_event(self.ctx, entity=cluster1,
timestamp=tu.utcnow(),
timestamp=tu.utcnow(True),
action='action1')
events = db_api.event_get_all(self.ctx, sort='timestamp')

View File

@ -178,7 +178,7 @@ class DBAPINodeTest(base.SenlinTestCase):
node_ids = ['node1', 'node2', 'node3']
for v in node_ids:
shared.create_node(self.ctx, self.cluster, self.profile,
id=v, init_at=tu.utcnow())
id=v, init_at=tu.utcnow(True))
nodes = db_api.node_get_all(self.ctx, limit=1)
self.assertEqual(1, len(nodes))
@ -247,7 +247,7 @@ class DBAPINodeTest(base.SenlinTestCase):
def test_node_get_all_default_sorting(self):
nodes = [shared.create_node(self.ctx, None, self.profile,
init_at=tu.utcnow())
init_at=tu.utcnow(True))
for x in range(3)]
results = db_api.node_get_all(self.ctx)
@ -442,7 +442,7 @@ class DBAPINodeTest(base.SenlinTestCase):
def test_node_migrate_from_none(self):
node_orphan = shared.create_node(self.ctx, None, self.profile)
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
node = db_api.node_migrate(self.ctx, node_orphan.id, self.cluster.id,
timestamp, 'NEW-ROLE')
@ -456,7 +456,7 @@ class DBAPINodeTest(base.SenlinTestCase):
def test_node_migrate_to_none(self):
node = shared.create_node(self.ctx, self.cluster, self.profile)
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
node_new = db_api.node_migrate(self.ctx, node.id, None, timestamp)
self.assertEqual(timestamp, node_new.updated_at)
@ -479,7 +479,7 @@ class DBAPINodeTest(base.SenlinTestCase):
self.assertEqual(2, cluster1.next_index)
self.assertEqual(1, cluster2.next_index)
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
node_new = db_api.node_migrate(self.ctx, node.id, cluster2.id,
timestamp)
@ -496,7 +496,7 @@ class DBAPINodeTest(base.SenlinTestCase):
self.assertEqual(2, cluster2.next_index)
# Migrate it back!
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
node_new = db_api.node_migrate(self.ctx, node.id, cluster1.id,
timestamp, 'FAKE-ROLE')

View File

@ -232,7 +232,7 @@ class DBAPIPolicyTest(base.SenlinTestCase):
def test_policy_get_all_with_limit_marker(self):
ids = ['policy1', 'policy2', 'policy3']
for pid in ids:
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
data = self.new_policy_data(id=pid, created_at=timestamp)
db_api.policy_create(self.ctx, data)
@ -282,7 +282,7 @@ class DBAPIPolicyTest(base.SenlinTestCase):
{'id': '003', 'name': 'policy2'}]
for v in values:
v['created_at'] = tu.utcnow()
v['created_at'] = tu.utcnow(True)
data = self.new_policy_data(**v)
db_api.policy_create(self.ctx, data)
@ -310,7 +310,7 @@ class DBAPIPolicyTest(base.SenlinTestCase):
def test_policy_get_all_default_sorting(self):
policies = []
for x in range(3):
data = self.new_policy_data(created_at=tu.utcnow())
data = self.new_policy_data(created_at=tu.utcnow(True))
policies.append(db_api.policy_create(self.ctx, data))
results = db_api.policy_get_all(self.ctx)

View File

@ -184,7 +184,7 @@ class DBAPIProfileTest(base.SenlinTestCase):
def test_profile_get_all_with_limit_marker(self):
ids = ['profile1', 'profile2', 'profile3']
for pid in ids:
timestamp = tu.utcnow()
timestamp = tu.utcnow(True)
shared.create_profile(self.ctx, id=pid, created_at=timestamp)
# different limit settings
@ -256,7 +256,8 @@ class DBAPIProfileTest(base.SenlinTestCase):
def test_profile_get_all_default_sorting(self):
profiles = []
for x in range(3):
profile = shared.create_profile(self.ctx, created_at=tu.utcnow())
profile = shared.create_profile(self.ctx,
created_at=tu.utcnow(True))
profiles.append(profile)
results = db_api.profile_get_all(self.ctx)

View File

@ -156,7 +156,8 @@ class DBAPIReceiverTest(base.SenlinTestCase):
def test_receiver_get_all_with_limit_marker(self):
receiver_ids = ['receiver1', 'receiver2', 'receiver3']
for v in receiver_ids:
self._create_receiver(self.ctx, id=v, created_at=tu.utcnow())
self._create_receiver(self.ctx, id=v,
created_at=tu.utcnow(True))
receivers = db_api.receiver_get_all(self.ctx, limit=1)
self.assertEqual(1, len(receivers))

View File

@ -14,9 +14,11 @@ import copy
import mock
from oslo_config import cfg
from oslo_utils import timeutils
import six
from senlin.common import exception
from senlin.common import utils as common_utils
from senlin.engine.actions import base as ab
from senlin.engine import cluster as cluster_mod
from senlin.engine import cluster_policy as cp_mod
@ -55,7 +57,7 @@ class ActionBaseTest(base.SenlinTestCase):
'status_reason': 'FAKE_STATUS_REASON',
'inputs': {'param': 'value'},
'outputs': {'key': 'output_value'},
'created_at': None,
'created_at': timeutils.utcnow(True),
'updated_at': None,
'data': {'data_key': 'data_value'},
}
@ -120,7 +122,8 @@ class ActionBaseTest(base.SenlinTestCase):
obj = ab.Action('OBJID', 'OBJECT_ACTION', self.ctx,
**values)
self.assertIsNone(obj.created_at)
self.assertEqual(common_utils.isotime(values['created_at']),
common_utils.isotime(obj.created_at))
self.assertIsNone(obj.updated_at)
# store for creation
@ -172,7 +175,8 @@ class ActionBaseTest(base.SenlinTestCase):
self.assertEqual(obj.status_reason, action_obj.status_reason)
self.assertEqual(obj.inputs, action_obj.inputs)
self.assertEqual(obj.outputs, action_obj.outputs)
self.assertEqual(obj.created_at, action_obj.created_at)
self.assertEqual(common_utils.isotime(obj.created_at),
common_utils.isotime(action_obj.created_at))
self.assertEqual(obj.updated_at, action_obj.updated_at)
self.assertEqual(obj.data, action_obj.data)
self.assertEqual(obj.user, action_obj.user)
@ -552,6 +556,7 @@ class ActionBaseTest(base.SenlinTestCase):
action = ab.Action('OBJID', 'OBJECT_ACTION', self.ctx,
**self.action_values)
action.id = 'FAKE_ID'
ts = common_utils.isotime(self.action_values['created_at'])
expected = {
'id': 'FAKE_ID',
'name': 'FAKE_NAME',
@ -569,7 +574,7 @@ class ActionBaseTest(base.SenlinTestCase):
'outputs': {'key': 'output_value'},
'depends_on': ['ACTION_1'],
'depended_by': ['ACTION_2'],
'created_at': None,
'created_at': ts,
'updated_at': None,
'data': {'data_key': 'data_value'},
}

View File

@ -112,7 +112,7 @@ class ClusterActionTest(base.SenlinTestCase):
@mock.patch.object(ao.Action, 'update')
@mock.patch.object(ab.Action, 'create')
@mock.patch.object(co.Cluster, 'next_index')
@mock.patch.object(co.Cluster, 'get_next_index')
@mock.patch.object(nm, 'Node')
@mock.patch.object(dobj.Dependency, 'create')
@mock.patch.object(dispatcher, 'start_action')
@ -185,7 +185,7 @@ class ClusterActionTest(base.SenlinTestCase):
@mock.patch.object(ao.Action, 'update')
@mock.patch.object(ab.Action, 'create')
@mock.patch.object(co.Cluster, 'next_index')
@mock.patch.object(co.Cluster, 'get_next_index')
@mock.patch.object(nm, 'Node')
@mock.patch.object(dobj.Dependency, 'create')
@mock.patch.object(dispatcher, 'start_action')

View File

@ -19,11 +19,11 @@ import six
from senlin.common import consts
from senlin.common import exception as exc
from senlin.common.i18n import _
from senlin.db.sqlalchemy import api as db_api
from senlin.engine.actions import base as action_mod
from senlin.engine import dispatcher
from senlin.engine import node as node_mod
from senlin.engine import service
from senlin.objects import cluster as co
from senlin.objects import node as no
from senlin.tests.unit.common import base
from senlin.tests.unit.common import utils
@ -273,7 +273,7 @@ class NodeTest(base.SenlinTestCase):
@mock.patch.object(action_mod.Action, 'create')
@mock.patch('senlin.engine.node.Node')
@mock.patch.object(db_api, 'cluster_next_index')
@mock.patch.object(co.Cluster, 'get_next_index')
@mock.patch.object(service.EngineService, 'cluster_find')
@mock.patch.object(service.EngineService, 'profile_find')
@mock.patch.object(dispatcher, 'start_action')
@ -312,7 +312,7 @@ class NodeTest(base.SenlinTestCase):
@mock.patch.object(action_mod.Action, 'create')
@mock.patch('senlin.engine.node.Node')
@mock.patch.object(db_api, 'cluster_next_index')
@mock.patch.object(co.Cluster, 'get_next_index')
@mock.patch.object(service.EngineService, 'cluster_find')
@mock.patch.object(service.EngineService, 'profile_find')
@mock.patch.object(dispatcher, 'start_action')

View File

@ -12,6 +12,7 @@
import mock
from oslo_config import cfg
from oslo_utils import timeutils
import six
from senlin.common import exception
@ -255,6 +256,7 @@ class TestCluster(base.SenlinTestCase):
'name': 'test-cluster',
'desired_capacity': 1,
'status': 'INIT',
'init_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
}

View File

@ -15,6 +15,7 @@ from oslo_utils import timeutils
import six
from senlin.common import exception
from senlin.common import utils as common_utils
from senlin.engine import cluster_policy as cpm
from senlin.objects import cluster as co
from senlin.objects import cluster_policy as cpo
@ -72,7 +73,7 @@ class TestClusterPolicy(base.SenlinTestCase):
cp.enabled = False
cp.priority = 60
cp.data = {'foo': 'bar'}
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
cp.last_op = timestamp
new_id = cp.store(self.context)
@ -85,26 +86,31 @@ class TestClusterPolicy(base.SenlinTestCase):
self.assertFalse(result.enabled)
self.assertEqual(60, result.priority)
self.assertEqual({'foo': 'bar'}, result.data)
self.assertEqual(timestamp, result.last_op)
self.assertEqual(common_utils.isotime(timestamp),
common_utils.isotime(result.last_op))
def _create_cluster(self, cluster_id):
values = {
'id': cluster_id,
'profile_id': 'some-profile',
'name': 'test_cluster',
'user': 'user',
'project': 'project'
'status': 'ACTIVE',
'init_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
}
return co.Cluster.create(self.context, values)
def _create_policy(self, policy_id):
values = {
'id': policy_id,
'name': 'test_policy',
'type': 'policy-type',
'spec': {'prop': 'value'},
'created_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
'domain': self.context.domain,
'name': 'test_policy',
}
return po.Policy.create(self.context, values)
@ -148,9 +154,11 @@ class TestClusterPolicy(base.SenlinTestCase):
policy1 = self._create_policy('P1')
policy2 = self._create_policy('P2')
b1 = cpm.ClusterPolicy(cluster.id, policy1.id, enabled=True)
b1 = cpm.ClusterPolicy(cluster.id, policy1.id, enabled=True,
priority=10)
b1.store(self.context)
b2 = cpm.ClusterPolicy(cluster.id, policy2.id, enabled=False)
b2 = cpm.ClusterPolicy(cluster.id, policy2.id, enabled=False,
priority=20)
b2.store(self.context)
# NOTE: we don't test all other parameters because the db api tests
@ -182,7 +190,7 @@ class TestClusterPolicy(base.SenlinTestCase):
def test_cooldown_inprogress(self):
values = {
'enabled': True,
'last_op': timeutils.utcnow(),
'last_op': timeutils.utcnow(True),
}
cp = cpm.ClusterPolicy('fake-cluster', 'fake-policy', **values)
self.assertTrue(cp.cooldown_inprogress(60))

View File

@ -146,9 +146,8 @@ class EngineStatusTest(base.SenlinTestCase):
@mock.patch.object(service_obj.Service, 'get_all')
@mock.patch.object(service_obj.Service, 'delete')
def test_service_manage_report_cleanup(self, mock_delete, mock_get_all):
ages_a_go = timeutils.utcnow() - datetime.timedelta(
seconds=2 * cfg.CONF.periodic_interval)
mock_get_all.return_value = [{'id': 'foo',
'updated_at': ages_a_go}]
delta = datetime.timedelta(seconds=2 * cfg.CONF.periodic_interval)
ages_a_go = timeutils.utcnow(True) - delta
mock_get_all.return_value = [{'id': 'foo', 'updated_at': ages_a_go}]
self.eng.service_manage_cleanup()
mock_delete.assert_called_once_with(mock.ANY, 'foo')

View File

@ -29,7 +29,7 @@ class TestEvent(base.SenlinTestCase):
self.context = utils.dummy_context()
def test_event_init(self):
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
kwargs = {
'id': 'FAKE-ID',
'user': 'test-user',
@ -63,7 +63,7 @@ class TestEvent(base.SenlinTestCase):
self.assertEqual({'foo': 'bar'}, event.metadata)
def test_event_init_with_entity(self):
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
x_cluster = cluster_mod.Cluster('fake-cluster', 0, 'fake-profile',
id='FAKE_CLUSTER')
@ -169,7 +169,7 @@ class TestEvent(base.SenlinTestCase):
mock_get.assert_called_once_with(entity, fully_qualified=False)
def test_event_store(self):
timestamp = timeutils.utcnow()
timestamp = timeutils.utcnow(True)
kwargs = {
'user': self.context.user,
'project': self.context.project,

View File

@ -38,12 +38,14 @@ class TestNode(base.SenlinTestCase):
def _create_profile(self, profile_id):
values = {
'id': profile_id,
'context': self.context.to_dict(),
'type': 'os.nova.server-1.0',
'name': 'test-profile',
'spec': {
'type': 'os.nova.server',
'version': '1.0',
},
'created_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project
}
@ -54,6 +56,8 @@ class TestNode(base.SenlinTestCase):
'id': cluster_id,
'profile_id': self.profile.id,
'name': 'test-cluster',
'status': 'ACTIVE',
'init_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
'next_index': 1,
@ -64,11 +68,15 @@ class TestNode(base.SenlinTestCase):
def _create_node(self, node_id):
values = {
'id': node_id,
'name': 'node1',
'profile_id': self.profile.id,
'cluster_id': self.cluster.id,
'index': 2,
'init_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
'name': 'node1',
'role': 'test_node',
'status': 'ACTIVE',
}
return node_obj.Node.create(self.context, values)
@ -510,10 +518,9 @@ class TestNode(base.SenlinTestCase):
self.assertIsNone(node.updated_at)
self.assertFalse(mock_migrate.called)
@mock.patch.object(timeutils, 'utcnow')
@mock.patch.object(profiles_base.Profile, 'join_cluster')
@mock.patch.object(node_obj.Node, 'migrate')
def test_node_join(self, mock_migrate, mock_join_cluster, mock_time):
def test_node_join(self, mock_migrate, mock_join_cluster):
node = nodem.Node('node1', self.profile.id, self.cluster.id,
self.context)
mock_join_cluster.return_value = True
@ -547,10 +554,9 @@ class TestNode(base.SenlinTestCase):
self.assertEqual('', node.cluster_id)
self.assertIsNone(node.updated_at)
@mock.patch.object(timeutils, 'utcnow')
@mock.patch.object(profiles_base.Profile, 'leave_cluster')
@mock.patch.object(node_obj.Node, 'migrate')
def test_node_leave(self, mock_migrate, mock_leave_cluster, mock_time):
def test_node_leave(self, mock_migrate, mock_leave_cluster):
node = nodem.Node('node1', self.profile.id, self.cluster.id,
self.context)
mock_leave_cluster.return_value = True

View File

@ -48,7 +48,7 @@ class TestReceiver(base.SenlinTestCase):
'user': self.context.user,
'project': self.context.project,
'domain': self.context.domain,
'created_at': timeutils.utcnow(),
'created_at': timeutils.utcnow(True),
'updated_at': None,
'actor': self.actor,
'params': self.params,
@ -64,7 +64,7 @@ class TestReceiver(base.SenlinTestCase):
'user': 'test-user',
'project': 'test-project',
'domain': 'test-domain',
'created_at': timeutils.utcnow(),
'created_at': timeutils.utcnow(True),
'updated_at': None,
'actor': self.actor,
'params': self.params,
@ -111,6 +111,7 @@ class TestReceiver(base.SenlinTestCase):
def test_receiver_store(self):
receiver = rb.Receiver('webhook', 'FAKE_CLUSTER', 'test-action',
name='test_receiver_123456',
project=self.context.project)
self.assertIsNone(receiver.id)
@ -128,7 +129,8 @@ class TestReceiver(base.SenlinTestCase):
self.assertEqual(receiver.user, result.user)
self.assertEqual(receiver.project, result.project)
self.assertEqual(receiver.domain, result.domain)
self.assertEqual(receiver.created_at, result.created_at)
self.assertEqual(common_utils.isotime(receiver.created_at),
common_utils.isotime(result.created_at)),
self.assertEqual(receiver.updated_at, result.updated_at)
self.assertEqual(receiver.action, result.action)
self.assertEqual(receiver.actor, result.actor)
@ -139,7 +141,8 @@ class TestReceiver(base.SenlinTestCase):
cluster = mock.Mock()
cluster.id = 'FAKE_CLUSTER'
receiver = rb.Receiver.create(self.context, 'webhook', cluster,
'FAKE_ACTION')
'FAKE_ACTION',
name='test_receiver_2234')
self.assertEqual(self.context.user, receiver.user)
self.assertEqual(self.context.project, receiver.project)

View File

@ -13,6 +13,7 @@
import datetime
import mock
from oslo_config import cfg
from oslo_utils import timeutils
from senlin.engine import scheduler
from senlin.engine import senlin_lock as lockm
@ -285,15 +286,14 @@ class SenlinLockEnginCheckTest(base.SenlinTestCase):
@mock.patch.object(service_obj.Service, 'get')
def test_engine_is_dead(self, mock_service):
update_time = (datetime.datetime.utcnow() - datetime.timedelta(
seconds=3 * cfg.CONF.periodic_interval))
delta = datetime.timedelta(seconds=3 * cfg.CONF.periodic_interval)
update_time = timeutils.utcnow(True) - delta
mock_service.return_value = mock.Mock(updated_at=update_time)
self.assertTrue(lockm.is_engine_dead(self.ctx, 'fake_engine_id'))
mock_service.assert_called_once_with(self.ctx, 'fake_engine_id')
@mock.patch.object(service_obj.Service, 'get')
def test_engine_is_alive(self, mock_service):
mock_service.return_value = mock.Mock(
updated_at=datetime.datetime.utcnow())
def test_engine_is_alive(self, mock_svc):
mock_svc.return_value = mock.Mock(updated_at=timeutils.utcnow(True))
self.assertFalse(lockm.is_engine_dead(self.ctx, 'fake_engine_id'))
mock_service.assert_called_once_with(self.ctx, 'fake_engine_id')
mock_svc.assert_called_once_with(self.ctx, 'fake_engine_id')

View File

@ -12,6 +12,7 @@
import mock
from oslo_context import context as oslo_ctx
from oslo_utils import timeutils
import six
from senlin.common import consts
@ -79,10 +80,10 @@ class TestPolicyBase(base.SenlinTestCase):
'name': 'test-policy',
'type': 'senlin.policy.dummy-1.0',
'spec': self.spec,
'created_at': timeutils.utcnow(True),
'user': self.ctx.user,
'project': self.ctx.project,
'domain': self.ctx.domain,
'data': {}
}
values.update(kwargs)
@ -275,7 +276,7 @@ class TestPolicyBase(base.SenlinTestCase):
'domain': policy.domain,
'spec': policy.spec,
'data': policy.data,
'created_at': common_utils.format_time(policy.created_at),
'created_at': common_utils.isotime(policy.created_at),
'updated_at': None,
}

View File

@ -51,10 +51,14 @@ class TestScalingPolicy(base.SenlinTestCase):
def _create_profile(self, profile_id):
values = {
'context': self.context.to_dict(),
'id': profile_id,
'type': 'os.heat.stack',
'name': 'test-profile',
'created_at': timeutils.utcnow(),
'spec': {
'template': 'fake_stack.yml',
},
'created_at': timeutils.utcnow(True),
'user': self.context.user,
'project': self.context.project,
}
@ -71,6 +75,8 @@ class TestScalingPolicy(base.SenlinTestCase):
'min_size': 1,
'max_size': 5,
'desired_capacity': 3,
'status': 'ACTIVE',
'init_at': timeutils.utcnow(True),
}
return co.Cluster.create(self.context, values)
@ -84,15 +90,17 @@ class TestScalingPolicy(base.SenlinTestCase):
'physical_id': 'FAKE_PHY_ID_%s' % (i + 1),
'cluster_id': cluster_id,
'profile_id': profile_id,
'project': self.context.project,
'index': i + 1,
'role': None,
'created_at': timeutils.utcnow(),
'init_at': timeutils.utcnow(True),
'created_at': timeutils.utcnow(True),
'updated_at': None,
'status': 'ACTIVE',
'status_reason': 'create complete',
'metadata': {'foo': '123'},
'data': {'key1': 'value1'},
'user': self.context.user,
'project': self.context.project,
}
db_node = no.Node.create(self.context, values)
nodes.append(six.text_type(db_node.id))