Added action_plan.create|update|delete notifs

In this changeset, I added 3 notifications:

- action_plan.create
- action_plan.update
- action_plan.delete

Partially Implements: blueprint action-plan-versioned-notifications-api

Change-Id: I8821fc6f47e7486037839d81bed9e28020b02fdd
This commit is contained in:
Vincent Françoise 2017-01-24 11:08:08 +01:00
parent ea1fd5967a
commit e51e7e4317
33 changed files with 986 additions and 112 deletions

View File

@ -0,0 +1,54 @@
{
"publisher_id": "infra-optim:node0",
"payload": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"display_name": "test strategy",
"name": "TEST",
"updated_at": null,
"parameters_spec": {},
"created_at": "2016-10-18T09:52:05Z",
"deleted_at": null
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "StrategyPayload"
},
"created_at": null,
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"audit_type": "ONESHOT",
"scope": [],
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"parameters": {},
"interval": null,
"deleted_at": null,
"state": "PENDING",
"created_at": "2016-10-18T09:52:05Z",
"updated_at": null
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "TerseAuditPayload"
},
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {},
"deleted_at": null,
"state": "RECOMMENDED",
"updated_at": null
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "ActionPlanCreatePayload"
},
"priority": "INFO",
"message_id": "5148bff1-ea06-4ad6-8e4e-8c85ca5eb629",
"event_type": "action_plan.create",
"timestamp": "2016-10-18 09:52:05.219414"
}

View File

@ -0,0 +1,54 @@
{
"publisher_id": "infra-optim:node0",
"timestamp": "2016-10-18 09:52:05.219414",
"payload": {
"watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"created_at": "2016-10-18T09:52:05Z",
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.data": {
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"interval": null,
"audit_type": "ONESHOT",
"scope": [],
"updated_at": null,
"deleted_at": null,
"state": "PENDING",
"created_at": "2016-10-18T09:52:05Z",
"parameters": {}
},
"watcher_object.version": "1.0",
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher"
},
"global_efficacy": {},
"updated_at": null,
"deleted_at": null,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.data": {
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"created_at": "2016-10-18T09:52:05Z",
"name": "TEST",
"display_name": "test strategy",
"deleted_at": null,
"updated_at": null,
"parameters_spec": {}
},
"watcher_object.version": "1.0",
"watcher_object.name": "StrategyPayload",
"watcher_object.namespace": "watcher"
},
"state": "DELETED"
},
"watcher_object.version": "1.0",
"watcher_object.name": "ActionPlanDeletePayload",
"watcher_object.namespace": "watcher"
},
"event_type": "action_plan.delete",
"message_id": "3d137686-a1fd-4683-ab40-c4210aac2140",
"priority": "INFO"
}

View File

@ -0,0 +1,63 @@
{
"payload": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"audit_type": "ONESHOT",
"scope": [],
"created_at": "2016-10-18T09:52:05Z",
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"interval": null,
"updated_at": null,
"state": "PENDING",
"deleted_at": null,
"parameters": {}
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "TerseAuditPayload"
},
"created_at": "2016-10-18T09:52:05Z",
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"updated_at": null,
"state_update": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"old_state": "PENDING",
"state": "ONGOING"
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "ActionPlanStateUpdatePayload"
},
"state": "ONGOING",
"deleted_at": null,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.version": "1.0",
"watcher_object.data": {
"name": "TEST",
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"display_name": "test strategy",
"created_at": "2016-10-18T09:52:05Z",
"updated_at": null,
"deleted_at": null,
"parameters_spec": {}
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "StrategyPayload"
},
"global_efficacy": {}
},
"watcher_object.namespace": "watcher",
"watcher_object.name": "ActionPlanUpdatePayload"
},
"publisher_id": "infra-optim:node0",
"priority": "INFO",
"timestamp": "2016-10-18 09:52:05.219414",
"event_type": "action_plan.update",
"message_id": "0a8a7329-fd5a-4ec6-97d7-2b776ce51a4c"
}

View File

@ -10,6 +10,7 @@
"state": "PENDING",
"updated_at": null,
"deleted_at": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -26,6 +27,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -10,6 +10,7 @@
"state": "DELETED",
"updated_at": null,
"deleted_at": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -26,6 +27,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -11,6 +11,7 @@
"updated_at": null,
"deleted_at": null,
"fault": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -27,6 +28,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -21,6 +21,7 @@
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -37,6 +38,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -11,6 +11,7 @@
"updated_at": null,
"deleted_at": null,
"fault": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -27,6 +28,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -11,6 +11,7 @@
"updated_at": null,
"deleted_at": null,
"fault": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -27,6 +28,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -21,6 +21,7 @@
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -37,6 +38,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -11,6 +11,7 @@
"updated_at": null,
"deleted_at": null,
"fault": null,
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.data": {
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
@ -27,6 +28,7 @@
},
"interval": null,
"scope": [],
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.data": {
"parameters_spec": {

View File

@ -4,6 +4,7 @@
"payload": {
"watcher_object.name": "AuditUpdatePayload",
"watcher_object.data": {
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": {
"watcher_object.name": "StrategyPayload",
"watcher_object.data": {
@ -36,6 +37,7 @@
"scope": [],
"created_at": "2016-11-04T16:51:21Z",
"uuid": "f1e0d912-afd9-4bf2-91ef-c99cd08cc1ef",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"goal": {
"watcher_object.name": "GoalPayload",
"watcher_object.data": {

View File

@ -455,7 +455,8 @@ class ActionPlansController(rest.RestController):
:param action_plan_uuid: UUID of a action.
"""
context = pecan.request.context
action_plan = api_utils.get_resource('ActionPlan', action_plan_uuid)
action_plan = api_utils.get_resource(
'ActionPlan', action_plan_uuid, eager=True)
policy.enforce(context, 'action_plan:delete', action_plan,
action='action_plan:delete')
@ -474,8 +475,8 @@ class ActionPlansController(rest.RestController):
raise exception.OperationNotPermitted
context = pecan.request.context
action_plan_to_update = api_utils.get_resource('ActionPlan',
action_plan_uuid)
action_plan_to_update = api_utils.get_resource(
'ActionPlan', action_plan_uuid, eager=True)
policy.enforce(context, 'action_plan:update', action_plan_to_update,
action='action_plan:update')

View File

@ -33,7 +33,8 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
self.action_plan_uuid = action_plan_uuid
def update_action_plan(self, uuid, state):
action_plan = objects.ActionPlan.get_by_uuid(self.ctx, uuid)
action_plan = objects.ActionPlan.get_by_uuid(
self.ctx, uuid, eager=True)
action_plan.state = state
action_plan.save()

View File

@ -174,6 +174,14 @@ class EagerlyLoadedAuditRequired(InvalidAudit):
msg_fmt = _("Audit %(audit)s was not eagerly loaded")
class InvalidActionPlan(Invalid):
msg_fmt = _("Action plan %(action_plan)s is invalid")
class EagerlyLoadedActionPlanRequired(InvalidActionPlan):
msg_fmt = _("Action plan %(action_plan)s was not eagerly loaded")
class InvalidUUID(Invalid):
msg_fmt = _("Expected a uuid but received %(uuid)s")

View File

@ -350,7 +350,7 @@ class Syncer(object):
for strategy_id, synced_strategy in self.strategy_mapping.items():
filters = {"strategy_id": strategy_id}
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
self.ctx, filters=filters, eager=True)
# Update strategy IDs for all stale action plans (w/o saving)
for action_plan in stale_action_plans:
@ -369,7 +369,7 @@ class Syncer(object):
for audit_id, synced_audit in self.stale_audits_map.items():
filters = {"audit_id": audit_id}
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
self.ctx, filters=filters, eager=True)
# Update audit IDs for all stale action plans (w/o saving)
for action_plan in stale_action_plans:
@ -448,7 +448,7 @@ class Syncer(object):
audit.id].state = objects.audit.State.CANCELLED
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
self.ctx, filters=filters, eager=True)
for action_plan in stale_action_plans:
LOG.warning(
_LW("Action Plan '%(action_plan)s' references a "

View File

@ -20,6 +20,7 @@
# need to be changed after we moved these function inside the package
# Todo(gibi): remove these imports after legacy notifications using these are
# transformed to versioned notifications
from watcher.notifications import action_plan # noqa
from watcher.notifications import audit # noqa
from watcher.notifications import exception # noqa
from watcher.notifications import goal # noqa

View File

@ -0,0 +1,267 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 b<>com
#
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
#
# 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_config import cfg
from watcher.common import context as wcontext
from watcher.common import exception
from watcher.notifications import audit as audit_notifications
from watcher.notifications import base as notificationbase
from watcher.notifications import strategy as strategy_notifications
from watcher import objects
from watcher.objects import base
from watcher.objects import fields as wfields
CONF = cfg.CONF
@base.WatcherObjectRegistry.register_notification
class ActionPlanPayload(notificationbase.NotificationPayloadBase):
SCHEMA = {
'uuid': ('action_plan', 'uuid'),
'state': ('action_plan', 'state'),
'global_efficacy': ('action_plan', 'global_efficacy'),
'audit_uuid': ('audit', 'uuid'),
'strategy_uuid': ('strategy', 'uuid'),
'created_at': ('action_plan', 'created_at'),
'updated_at': ('action_plan', 'updated_at'),
'deleted_at': ('action_plan', 'deleted_at'),
}
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'uuid': wfields.UUIDField(),
'state': wfields.StringField(),
'global_efficacy': wfields.FlexibleDictField(nullable=True),
'audit_uuid': wfields.UUIDField(),
'strategy_uuid': wfields.UUIDField(),
'audit': wfields.ObjectField('TerseAuditPayload'),
'strategy': wfields.ObjectField('StrategyPayload'),
'created_at': wfields.DateTimeField(nullable=True),
'updated_at': wfields.DateTimeField(nullable=True),
'deleted_at': wfields.DateTimeField(nullable=True),
}
def __init__(self, action_plan, audit, strategy, **kwargs):
super(ActionPlanPayload, self).__init__(
audit=audit, strategy=strategy, **kwargs)
self.populate_schema(
action_plan=action_plan, audit=audit, strategy=strategy)
@base.WatcherObjectRegistry.register_notification
class ActionPlanStateUpdatePayload(notificationbase.NotificationPayloadBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'old_state': wfields.StringField(nullable=True),
'state': wfields.StringField(nullable=True),
}
@base.WatcherObjectRegistry.register_notification
class ActionPlanCreatePayload(ActionPlanPayload):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {}
def __init__(self, action_plan, audit, strategy):
super(ActionPlanCreatePayload, self).__init__(
action_plan=action_plan,
audit=audit,
strategy=strategy)
@base.WatcherObjectRegistry.register_notification
class ActionPlanUpdatePayload(ActionPlanPayload):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'state_update': wfields.ObjectField('ActionPlanStateUpdatePayload'),
}
def __init__(self, action_plan, state_update, audit, strategy):
super(ActionPlanUpdatePayload, self).__init__(
action_plan=action_plan,
state_update=state_update,
audit=audit,
strategy=strategy)
@base.WatcherObjectRegistry.register_notification
class ActionPlanActionPayload(ActionPlanPayload):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
}
def __init__(self, action_plan, audit, strategy, **kwargs):
super(ActionPlanActionPayload, self).__init__(
action_plan=action_plan,
audit=audit,
strategy=strategy,
**kwargs)
@base.WatcherObjectRegistry.register_notification
class ActionPlanDeletePayload(ActionPlanPayload):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {}
def __init__(self, action_plan, audit, strategy):
super(ActionPlanDeletePayload, self).__init__(
action_plan=action_plan,
audit=audit,
strategy=strategy)
@notificationbase.notification_sample('action_plan-create.json')
@base.WatcherObjectRegistry.register_notification
class ActionPlanCreateNotification(notificationbase.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': wfields.ObjectField('ActionPlanCreatePayload')
}
@notificationbase.notification_sample('action_plan-update.json')
@base.WatcherObjectRegistry.register_notification
class ActionPlanUpdateNotification(notificationbase.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': wfields.ObjectField('ActionPlanUpdatePayload')
}
@notificationbase.notification_sample('action_plan-delete.json')
@base.WatcherObjectRegistry.register_notification
class ActionPlanDeleteNotification(notificationbase.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': wfields.ObjectField('ActionPlanDeletePayload')
}
def _get_common_payload(action_plan):
audit = None
strategy = None
try:
audit = action_plan.audit
strategy = action_plan.strategy
except NotImplementedError:
raise exception.EagerlyLoadedActionPlanRequired(
action_plan=action_plan.uuid)
goal = objects.Goal.get(
wcontext.make_context(show_deleted=True), audit.goal_id)
audit_payload = audit_notifications.TerseAuditPayload(
audit=audit, goal_uuid=goal.uuid)
strategy_payload = strategy_notifications.StrategyPayload(
strategy=strategy)
return audit_payload, strategy_payload
def send_create(context, action_plan, service='infra-optim', host=None):
"""Emit an action_plan.create notification."""
audit_payload, strategy_payload = _get_common_payload(action_plan)
versioned_payload = ActionPlanCreatePayload(
action_plan=action_plan,
audit=audit_payload,
strategy=strategy_payload,
)
notification = ActionPlanCreateNotification(
priority=wfields.NotificationPriority.INFO,
event_type=notificationbase.EventType(
object='action_plan',
action=wfields.NotificationAction.CREATE),
publisher=notificationbase.NotificationPublisher(
host=host or CONF.host,
binary=service),
payload=versioned_payload)
notification.emit(context)
def send_update(context, action_plan, service='infra-optim',
host=None, old_state=None):
"""Emit an action_plan.update notification."""
audit_payload, strategy_payload = _get_common_payload(action_plan)
state_update = ActionPlanStateUpdatePayload(
old_state=old_state,
state=action_plan.state if old_state else None)
versioned_payload = ActionPlanUpdatePayload(
action_plan=action_plan,
state_update=state_update,
audit=audit_payload,
strategy=strategy_payload,
)
notification = ActionPlanUpdateNotification(
priority=wfields.NotificationPriority.INFO,
event_type=notificationbase.EventType(
object='action_plan',
action=wfields.NotificationAction.UPDATE),
publisher=notificationbase.NotificationPublisher(
host=host or CONF.host,
binary=service),
payload=versioned_payload)
notification.emit(context)
def send_delete(context, action_plan, service='infra-optim', host=None):
"""Emit an action_plan.delete notification."""
audit_payload, strategy_payload = _get_common_payload(action_plan)
versioned_payload = ActionPlanDeletePayload(
action_plan=action_plan,
audit=audit_payload,
strategy=strategy_payload,
)
notification = ActionPlanDeleteNotification(
priority=wfields.NotificationPriority.INFO,
event_type=notificationbase.EventType(
object='action_plan',
action=wfields.NotificationAction.DELETE),
publisher=notificationbase.NotificationPublisher(
host=host or CONF.host,
binary=service),
payload=versioned_payload)
notification.emit(context)

View File

@ -30,7 +30,7 @@ CONF = cfg.CONF
@base.WatcherObjectRegistry.register_notification
class AuditPayload(notificationbase.NotificationPayloadBase):
class TerseAuditPayload(notificationbase.NotificationPayloadBase):
SCHEMA = {
'uuid': ('audit', 'uuid'),
@ -57,19 +57,54 @@ class AuditPayload(notificationbase.NotificationPayloadBase):
'scope': wfields.FlexibleListOfDictField(nullable=True),
'goal_uuid': wfields.UUIDField(),
'strategy_uuid': wfields.UUIDField(nullable=True),
'goal': wfields.ObjectField('GoalPayload'),
'strategy': wfields.ObjectField('StrategyPayload', nullable=True),
'created_at': wfields.DateTimeField(nullable=True),
'updated_at': wfields.DateTimeField(nullable=True),
'deleted_at': wfields.DateTimeField(nullable=True),
}
def __init__(self, audit, **kwargs):
super(AuditPayload, self).__init__(**kwargs)
def __init__(self, audit, goal_uuid, strategy_uuid=None, **kwargs):
super(TerseAuditPayload, self).__init__(
goal_uuid=goal_uuid, strategy_uuid=strategy_uuid, **kwargs)
self.populate_schema(audit=audit)
@base.WatcherObjectRegistry.register_notification
class AuditPayload(TerseAuditPayload):
SCHEMA = {
'uuid': ('audit', 'uuid'),
'audit_type': ('audit', 'audit_type'),
'state': ('audit', 'state'),
'parameters': ('audit', 'parameters'),
'interval': ('audit', 'interval'),
'scope': ('audit', 'scope'),
'created_at': ('audit', 'created_at'),
'updated_at': ('audit', 'updated_at'),
'deleted_at': ('audit', 'deleted_at'),
}
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'goal': wfields.ObjectField('GoalPayload'),
'strategy': wfields.ObjectField('StrategyPayload', nullable=True),
}
def __init__(self, audit, goal, strategy=None, **kwargs):
if not kwargs.get('goal_uuid'):
kwargs['goal_uuid'] = goal.uuid
if strategy and not kwargs.get('strategy_uuid'):
kwargs['strategy_uuid'] = strategy.uuid
super(AuditPayload, self).__init__(
audit=audit, goal=goal,
strategy=strategy, **kwargs)
@base.WatcherObjectRegistry.register_notification
class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
# Version 1.0: Initial version
@ -91,6 +126,7 @@ class AuditCreatePayload(AuditPayload):
super(AuditCreatePayload, self).__init__(
audit=audit,
goal=goal,
goal_uuid=goal.uuid,
strategy=strategy)
@ -107,6 +143,7 @@ class AuditUpdatePayload(AuditPayload):
audit=audit,
state_update=state_update,
goal=goal,
goal_uuid=goal.uuid,
strategy=strategy)
@ -122,6 +159,7 @@ class AuditActionPayload(AuditPayload):
super(AuditActionPayload, self).__init__(
audit=audit,
goal=goal,
goal_uuid=goal.uuid,
strategy=strategy,
**kwargs)
@ -136,6 +174,7 @@ class AuditDeletePayload(AuditPayload):
super(AuditDeletePayload, self).__init__(
audit=audit,
goal=goal,
goal_uuid=goal.uuid,
strategy=strategy)

View File

@ -13,6 +13,7 @@
# under the License.
from oslo_config import cfg
from oslo_log import log
from watcher.common import exception
from watcher.common import rpc
@ -20,6 +21,7 @@ from watcher.objects import base
from watcher.objects import fields as wfields
CONF = cfg.CONF
LOG = log.getLogger(__name__)
# Definition of notification levels in increasing order of severity
NOTIFY_LEVELS = {
@ -59,7 +61,8 @@ class EventType(NotificationObject):
# Version 1.0: Initial version
# Version 1.1: Added STRATEGY action in NotificationAction enum
# Version 1.2: Added PLANNER action in NotificationAction enum
VERSION = '1.2'
# Version 1.3: Added EXECUTION action in NotificationAction enum
VERSION = '1.3'
fields = {
'object': wfields.StringField(),
@ -171,6 +174,7 @@ class NotificationBase(NotificationObject):
def _emit(self, context, event_type, publisher_id, payload):
notifier = rpc.get_notifier(publisher_id)
notify = getattr(notifier, self.priority)
LOG.debug("Emitting notification `%s`", event_type)
notify(context, event_type=event_type, payload=payload)
def emit(self, context):

View File

@ -72,6 +72,7 @@ state may be one of the following:
from watcher.common import exception
from watcher.common import utils
from watcher.db import api as db_api
from watcher import notifications
from watcher import objects
from watcher.objects import base
from watcher.objects import fields as wfields
@ -117,6 +118,39 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
'strategy': (objects.Strategy, 'strategy_id'),
}
# Proxified field so we can keep the previous value after an update
_state = None
_old_state = None
# NOTE(v-francoise): The way oslo.versionedobjects works is by using a
# __new__ that will automatically create the attributes referenced in
# fields. These attributes are properties that raise an exception if no
# value has been assigned, which means that they store the actual field
# value in an "_obj_%(field)s" attribute. So because we want to proxify a
# value that is already proxified, we have to do what you see below.
@property
def _obj_state(self):
return self._state
@property
def _obj_old_state(self):
return self._old_state
@property
def old_state(self):
return self._old_state
@_obj_old_state.setter
def _obj_old_state(self, value):
self._old_state = value
@_obj_state.setter
def _obj_state(self, value):
if self._old_state is None and self._state is None:
self._state = value
else:
self._old_state, self._state = self._state, value
@base.remotable_classmethod
def get(cls, context, action_plan_id, eager=False):
"""Find a action_plan based on its id or uuid and return a Action object.
@ -198,6 +232,11 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
# notifications containing information about the related relationships
self._from_db_object(self, db_action_plan, eager=True)
def _notify():
notifications.action_plan.send_create(self._context, self)
_notify()
@base.remotable
def destroy(self):
"""Delete the action plan from the DB"""
@ -221,8 +260,16 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
"""
updates = self.obj_get_changes()
db_obj = self.dbapi.update_action_plan(self.uuid, updates)
obj = self._from_db_object(self, db_obj, eager=False)
obj = self._from_db_object(
self.__class__(self._context), db_obj, eager=False)
self.obj_refresh(obj)
def _notify():
notifications.action_plan.send_update(
self._context, self, old_state=self.old_state)
_notify()
self.obj_reset_changes()
@base.remotable
@ -262,3 +309,8 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
obj = self._from_db_object(
self.__class__(self._context), db_obj, eager=False)
self.obj_refresh(obj)
def _notify():
notifications.action_plan.send_delete(self._context, self)
_notify()

View File

@ -128,8 +128,9 @@ class NotificationAction(BaseWatcherEnum):
STRATEGY = 'strategy'
PLANNER = 'planner'
EXECUTION = 'execution'
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER)
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER, EXECUTION)
class NotificationPriorityField(BaseEnumField):

View File

@ -89,7 +89,7 @@ def get_test_audit(**kwargs):
'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'),
'parameters': kwargs.get('parameters', {}),
'interval': kwargs.get('period', 3600),
'interval': kwargs.get('interval', 3600),
'goal_id': kwargs.get('goal_id', 1),
'strategy_id': kwargs.get('strategy_id', None),
'scope': kwargs.get('scope', []),

View File

@ -175,12 +175,19 @@ class TestAutoTriggerActionPlan(base.DbTestCase):
self.ongoing_action_plan = obj_utils.create_test_action_plan(
self.context,
uuid=uuidutils.generate_uuid(),
audit_id=self.audit.id)
audit_id=self.audit.id,
strategy_id=self.strategy.id,
audit=self.audit,
strategy=self.strategy,
)
self.recommended_action_plan = obj_utils.create_test_action_plan(
self.context,
uuid=uuidutils.generate_uuid(),
state=objects.action_plan.State.ONGOING,
audit_id=self.audit.id
audit_id=self.audit.id,
strategy_id=self.strategy.id,
audit=self.audit,
strategy=self.strategy,
)
@mock.patch.object(objects.action_plan.ActionPlan, 'list')

View File

@ -153,7 +153,6 @@ class FakeMonascaMetrics(object):
# measurements[uuid] = random.randint(1, 4)
measurements[uuid] = 8
# import ipdb; ipdb.set_trace()
return [{'columns': ['avg'],
'statistics': [[float(measurements[str(uuid)])]]}]
# return float(measurements[str(uuid)])

View File

@ -60,6 +60,7 @@ class TestActionScheduling(base.DbTestCase):
def setUp(self):
super(TestActionScheduling, self).setUp()
self.goal = db_utils.create_test_goal(name="dummy")
self.strategy = db_utils.create_test_strategy(name="dummy")
self.audit = db_utils.create_test_audit(
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)

View File

@ -61,6 +61,7 @@ class TestActionScheduling(base.DbTestCase):
def setUp(self):
super(TestActionScheduling, self).setUp()
self.goal = db_utils.create_test_goal(name="dummy")
self.strategy = db_utils.create_test_strategy(name="dummy")
self.audit = db_utils.create_test_audit(
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)

View File

@ -0,0 +1,261 @@
# 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 freezegun
import mock
import oslo_messaging as om
from watcher.common import exception
from watcher.common import rpc
from watcher import notifications
from watcher import objects
from watcher.tests.db import base
from watcher.tests.objects import utils
@freezegun.freeze_time('2016-10-18T09:52:05.219414')
class TestActionPlanNotification(base.DbTestCase):
def setUp(self):
super(TestActionPlanNotification, self).setUp()
p_get_notifier = mock.patch.object(rpc, 'get_notifier')
m_get_notifier = p_get_notifier.start()
self.addCleanup(p_get_notifier.stop)
self.m_notifier = mock.Mock(spec=om.Notifier)
def fake_get_notifier(publisher_id):
self.m_notifier.publisher_id = publisher_id
return self.m_notifier
m_get_notifier.side_effect = fake_get_notifier
self.goal = utils.create_test_goal(mock.Mock())
self.audit = utils.create_test_audit(mock.Mock(), interval=None)
self.strategy = utils.create_test_strategy(mock.Mock())
def test_send_invalid_action_plan(self):
action_plan = utils.get_test_action_plan(
mock.Mock(), state='DOESNOTMATTER', audit_id=1)
self.assertRaises(
exception.InvalidActionPlan,
notifications.action_plan.send_update,
mock.MagicMock(), action_plan, host='node0')
def test_send_action_plan_update(self):
action_plan = utils.create_test_action_plan(
mock.Mock(), state=objects.action_plan.State.ONGOING,
audit_id=self.audit.id, strategy_id=self.strategy.id,
audit=self.audit, strategy=self.strategy)
notifications.action_plan.send_update(
mock.MagicMock(), action_plan, host='node0',
old_state=objects.action_plan.State.PENDING)
# The 1st notification is because we created the object.
# The 2nd notification is because we created the action plan object.
self.assertEqual(3, self.m_notifier.info.call_count)
notification = self.m_notifier.info.call_args[1]
payload = notification['payload']
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"global_efficacy": {},
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"updated_at": None,
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"name": "TEST",
"parameters_spec": {},
"created_at": "2016-10-18T09:52:05Z",
"display_name": "test strategy",
"deleted_at": None
},
"watcher_object.name": "StrategyPayload"
},
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.data": {
"interval": None,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
"goal_uuid": (
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
"deleted_at": None,
"scope": [],
"state": "PENDING",
"updated_at": None,
"created_at": "2016-10-18T09:52:05Z",
"audit_type": "ONESHOT"
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"deleted_at": None,
"state": "ONGOING",
"updated_at": None,
"created_at": "2016-10-18T09:52:05Z",
"state_update": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"old_state": "PENDING",
"state": "ONGOING"
},
"watcher_object.name": "ActionPlanStateUpdatePayload"
},
},
"watcher_object.name": "ActionPlanUpdatePayload"
},
payload
)
def test_send_action_plan_create(self):
action_plan = utils.get_test_action_plan(
mock.Mock(), state=objects.action_plan.State.PENDING,
audit_id=self.audit.id, strategy_id=self.strategy.id,
audit=self.audit.as_dict(), strategy=self.strategy.as_dict())
notifications.action_plan.send_create(
mock.MagicMock(), action_plan, host='node0')
self.assertEqual(2, self.m_notifier.info.call_count)
notification = self.m_notifier.info.call_args[1]
payload = notification['payload']
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"global_efficacy": {},
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"updated_at": None,
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"name": "TEST",
"parameters_spec": {},
"created_at": "2016-10-18T09:52:05Z",
"display_name": "test strategy",
"deleted_at": None
},
"watcher_object.name": "StrategyPayload"
},
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.data": {
"interval": None,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
"goal_uuid": (
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
"deleted_at": None,
"scope": [],
"state": "PENDING",
"updated_at": None,
"created_at": "2016-10-18T09:52:05Z",
"audit_type": "ONESHOT"
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"deleted_at": None,
"state": "PENDING",
"updated_at": None,
"created_at": None,
},
"watcher_object.name": "ActionPlanCreatePayload"
},
payload
)
def test_send_action_plan_delete(self):
action_plan = utils.create_test_action_plan(
mock.Mock(), state=objects.action_plan.State.DELETED,
audit_id=self.audit.id, strategy_id=self.strategy.id)
notifications.action_plan.send_delete(
mock.MagicMock(), action_plan, host='node0')
# The 1st notification is because we created the audit object.
# The 2nd notification is because we created the action plan object.
self.assertEqual(3, self.m_notifier.info.call_count)
notification = self.m_notifier.info.call_args[1]
payload = notification['payload']
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"global_efficacy": {},
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"updated_at": None,
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"name": "TEST",
"parameters_spec": {},
"created_at": "2016-10-18T09:52:05Z",
"display_name": "test strategy",
"deleted_at": None
},
"watcher_object.name": "StrategyPayload"
},
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": {
"watcher_object.data": {
"interval": None,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
"goal_uuid": (
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
"deleted_at": None,
"scope": [],
"state": "PENDING",
"updated_at": None,
"created_at": "2016-10-18T09:52:05Z",
"audit_type": "ONESHOT"
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"deleted_at": None,
"state": "DELETED",
"updated_at": None,
"created_at": "2016-10-18T09:52:05Z",
},
"watcher_object.name": "ActionPlanDeletePayload"
},
payload
)

View File

@ -43,8 +43,8 @@ class TestAuditNotification(base.DbTestCase):
self.strategy = utils.create_test_strategy(mock.Mock())
def test_send_invalid_audit(self):
audit = utils.get_test_audit(mock.Mock(), state='DOESNOTMATTER',
goal_id=1)
audit = utils.get_test_audit(
mock.Mock(), interval=None, state='DOESNOTMATTER', goal_id=1)
self.assertRaises(
exception.InvalidAudit,
@ -53,7 +53,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_update_with_strategy(self):
audit = utils.create_test_audit(
mock.Mock(), state=objects.audit.State.ONGOING,
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
goal_id=self.goal.id, strategy_id=self.strategy.id,
goal=self.goal, strategy=self.strategy)
notifications.audit.send_update(
@ -71,7 +71,8 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"interval": None,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -88,6 +89,7 @@ class TestAuditNotification(base.DbTestCase):
},
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -125,7 +127,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_update_without_strategy(self):
audit = utils.get_test_audit(
mock.Mock(), state=objects.audit.State.ONGOING,
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
goal_id=self.goal.id, goal=self.goal)
notifications.audit.send_update(
mock.MagicMock(), audit, host='node0',
@ -141,9 +143,10 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"interval": None,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -158,6 +161,7 @@ class TestAuditNotification(base.DbTestCase):
},
"watcher_object.name": "GoalPayload"
},
"strategy_uuid": None,
"strategy": None,
"deleted_at": None,
"scope": [],
@ -182,7 +186,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_create(self):
audit = utils.get_test_audit(
mock.Mock(), state=objects.audit.State.PENDING,
mock.Mock(), interval=None, state=objects.audit.State.PENDING,
goal_id=self.goal.id, strategy_id=self.strategy.id,
goal=self.goal.as_dict(), strategy=self.strategy.as_dict())
notifications.audit.send_create(
@ -198,7 +202,8 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"interval": None,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -215,6 +220,7 @@ class TestAuditNotification(base.DbTestCase):
},
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -243,7 +249,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_delete(self):
audit = utils.create_test_audit(
mock.Mock(), state=objects.audit.State.DELETED,
mock.Mock(), interval=None, state=objects.audit.State.DELETED,
goal_id=self.goal.id, strategy_id=self.strategy.id)
notifications.audit.send_delete(
mock.MagicMock(), audit, host='node0')
@ -259,7 +265,8 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"interval": None,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -276,6 +283,7 @@ class TestAuditNotification(base.DbTestCase):
},
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
@ -304,7 +312,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_action(self):
audit = utils.create_test_audit(
mock.Mock(), state=objects.audit.State.ONGOING,
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
goal_id=self.goal.id, strategy_id=self.strategy.id,
goal=self.goal, strategy=self.strategy)
notifications.audit.send_action_notification(
@ -326,6 +334,7 @@ class TestAuditNotification(base.DbTestCase):
"created_at": "2016-10-18T09:52:05Z",
"deleted_at": None,
"fault": None,
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z",
@ -340,10 +349,12 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"interval": 3600,
"interval": None,
"parameters": {},
"scope": [],
"state": "ONGOING",
"strategy_uuid": (
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
"strategy": {
"watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z",
@ -371,7 +382,7 @@ class TestAuditNotification(base.DbTestCase):
def test_send_audit_action_with_error(self):
audit = utils.create_test_audit(
mock.Mock(), state=objects.audit.State.ONGOING,
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
goal_id=self.goal.id, strategy_id=self.strategy.id,
goal=self.goal, strategy=self.strategy)
@ -407,6 +418,7 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"goal": {
"watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z",
@ -421,10 +433,12 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
},
"interval": 3600,
"interval": None,
"parameters": {},
"scope": [],
"state": "ONGOING",
"strategy_uuid": (
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
"strategy": {
"watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z",

View File

@ -250,10 +250,11 @@ class TestNotificationBase(testbase.TestCase):
expected_notification_fingerprints = {
'EventType': '1.2-633c2d32fa849d2a6f8bda3b0db88332',
'EventType': '1.3-4258a2c86eca79fd34a7dffe1278eab9',
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
'TerseAuditPayload': '1.0-aaf31166b8698f08d12cae98c380b8e0',
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
@ -266,6 +267,15 @@ expected_notification_fingerprints = {
'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52',
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1',
'ActionPlanActionPayload': '1.0-34871caf18e9b43a28899953c1c9733a',
'ActionPlanCreateNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ActionPlanCreatePayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
'ActionPlanDeleteNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ActionPlanDeletePayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
'ActionPlanPayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
'ActionPlanStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
'ActionPlanUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ActionPlanUpdatePayload': '1.0-7912a45fe53775c721f42aa87f06a023',
}

View File

@ -20,6 +20,7 @@ import mock
from watcher.common import exception
from watcher.db.sqlalchemy import api as db_api
from watcher import notifications
from watcher import objects
from watcher.tests.db import base
from watcher.tests.db import utils
@ -34,16 +35,19 @@ class TestActionPlanObject(base.DbTestCase):
('non_eager', dict(
eager=False,
fake_action_plan=utils.get_test_action_plan(
created_at=datetime.datetime.utcnow(),
audit_id=audit_id,
strategy_id=strategy_id))),
('eager_with_non_eager_load', dict(
eager=True,
fake_action_plan=utils.get_test_action_plan(
created_at=datetime.datetime.utcnow(),
audit_id=audit_id,
strategy_id=strategy_id))),
('eager_with_eager_load', dict(
eager=True,
fake_action_plan=utils.get_test_action_plan(
created_at=datetime.datetime.utcnow(),
strategy_id=strategy_id,
strategy=utils.get_test_strategy(id=strategy_id),
audit_id=audit_id,
@ -52,6 +56,13 @@ class TestActionPlanObject(base.DbTestCase):
def setUp(self):
super(TestActionPlanObject, self).setUp()
p_action_plan_notifications = mock.patch.object(
notifications, 'action_plan', autospec=True)
self.m_action_plan_notifications = p_action_plan_notifications.start()
self.addCleanup(p_action_plan_notifications.stop)
self.m_send_update = self.m_action_plan_notifications.send_update
self.fake_audit = utils.create_test_audit(id=self.audit_id)
self.fake_strategy = utils.create_test_strategy(
id=self.strategy_id, name="DUMMY")
@ -80,6 +91,7 @@ class TestActionPlanObject(base.DbTestCase):
self.context, action_plan_id, eager=self.eager)
self.assertEqual(self.context, action_plan._context)
self.eager_load_action_plan_assert(action_plan)
self.assertEqual(0, self.m_send_update.call_count)
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
def test_get_by_uuid(self, mock_get_action_plan):
@ -91,6 +103,7 @@ class TestActionPlanObject(base.DbTestCase):
self.context, uuid, eager=self.eager)
self.assertEqual(self.context, action_plan._context)
self.eager_load_action_plan_assert(action_plan)
self.assertEqual(0, self.m_send_update.call_count)
def test_get_bad_id_and_uuid(self):
self.assertRaises(exception.InvalidIdentity,
@ -107,14 +120,26 @@ class TestActionPlanObject(base.DbTestCase):
self.assertEqual(self.context, action_plans[0]._context)
for action_plan in action_plans:
self.eager_load_action_plan_assert(action_plan)
self.assertEqual(0, self.m_send_update.call_count)
@mock.patch.object(db_api.Connection, 'update_action_plan')
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
def test_save(self, mock_get_action_plan, mock_update_action_plan):
mock_get_action_plan.return_value = self.fake_action_plan
fake_saved_action_plan = self.fake_action_plan.copy()
fake_saved_action_plan['deleted_at'] = datetime.datetime.utcnow()
fake_saved_action_plan['state'] = objects.action_plan.State.SUCCEEDED
fake_saved_action_plan['updated_at'] = datetime.datetime.utcnow()
mock_update_action_plan.return_value = fake_saved_action_plan
expected_action_plan = fake_saved_action_plan.copy()
expected_action_plan[
'created_at'] = expected_action_plan['created_at'].replace(
tzinfo=iso8601.iso8601.Utc())
expected_action_plan[
'updated_at'] = expected_action_plan['updated_at'].replace(
tzinfo=iso8601.iso8601.Utc())
uuid = self.fake_action_plan['uuid']
action_plan = objects.ActionPlan.get_by_uuid(
self.context, uuid, eager=self.eager)
@ -127,6 +152,14 @@ class TestActionPlanObject(base.DbTestCase):
uuid, {'state': objects.action_plan.State.SUCCEEDED})
self.assertEqual(self.context, action_plan._context)
self.eager_load_action_plan_assert(action_plan)
self.m_send_update.assert_called_once_with(
self.context, action_plan,
old_state=self.fake_action_plan['state'])
self.assertEqual(
{k: v for k, v in expected_action_plan.items()
if k not in action_plan.object_fields},
{k: v for k, v in action_plan.as_dict().items()
if k not in action_plan.object_fields})
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
def test_refresh(self, mock_get_action_plan):
@ -150,6 +183,13 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
def setUp(self):
super(TestCreateDeleteActionPlanObject, self).setUp()
p_action_plan_notifications = mock.patch.object(
notifications, 'action_plan', autospec=True)
self.m_action_plan_notifications = p_action_plan_notifications.start()
self.addCleanup(p_action_plan_notifications.stop)
self.m_send_update = self.m_action_plan_notifications.send_update
self.fake_strategy = utils.create_test_strategy(name="DUMMY")
self.fake_audit = utils.create_test_audit()
self.fake_action_plan = utils.get_test_action_plan(
@ -202,7 +242,8 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
del expected_action_plan['strategy']
m_get_efficacy_indicator_list.return_value = [efficacy_indicator]
action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid)
action_plan = objects.ActionPlan.get_by_uuid(
self.context, uuid, eager=False)
action_plan.soft_delete()
m_get_action_plan.assert_called_once_with(

View File

@ -282,6 +282,7 @@ class TestAuditObjectSendNotifications(base.DbTestCase):
def test_send_create_notification(self, m_create_audit):
audit = objutils.get_test_audit(
self.context,
id=1,
goal_id=self.fake_goal.id,
strategy_id=self.fake_strategy.id,
goal=self.fake_goal.as_dict(),

View File

@ -30,21 +30,27 @@ def _load_related_objects(context, cls, db_data):
return obj_data
def _load_test_obj(context, cls, obj_data, **kw):
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del obj_data['id']
obj = cls(context)
for key in obj_data:
setattr(obj, key, obj_data[key])
return obj
def get_test_audit_template(context, **kw):
"""Return a AuditTemplate object with appropriate attributes.
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_audit_template = db_utils.get_test_audit_template(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_audit_template['id']
audit_template = objects.AuditTemplate(context)
for key in db_audit_template:
setattr(audit_template, key, db_audit_template[key])
obj_cls = objects.AuditTemplate
db_data = db_utils.get_test_audit_template(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return audit_template
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_audit_template(context, **kw):
@ -64,16 +70,11 @@ def get_test_audit(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_audit = db_utils.get_test_audit(**kw)
obj_data = _load_related_objects(context, objects.Audit, db_audit)
obj_cls = objects.Audit
db_data = db_utils.get_test_audit(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_audit['id']
audit = objects.Audit(context)
for key in obj_data:
setattr(audit, key, obj_data[key])
return audit
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_audit(context, **kw):
@ -93,14 +94,11 @@ def get_test_action_plan(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_action_plan = db_utils.get_test_action_plan(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_action_plan['id']
action_plan = objects.ActionPlan(context)
for key in db_action_plan:
setattr(action_plan, key, db_action_plan[key])
return action_plan
obj_cls = objects.ActionPlan
db_data = db_utils.get_test_action_plan(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_action_plan(context, **kw):
@ -120,14 +118,11 @@ def get_test_action(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_action = db_utils.get_test_action(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_action['id']
action = objects.Action(context)
for key in db_action:
setattr(action, key, db_action[key])
return action
obj_cls = objects.Action
db_data = db_utils.get_test_action(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_action(context, **kw):
@ -147,14 +142,11 @@ def get_test_goal(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_goal = db_utils.get_test_goal(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_goal['id']
goal = objects.Goal(context)
for key in db_goal:
setattr(goal, key, db_goal[key])
return goal
obj_cls = objects.Goal
db_data = db_utils.get_test_goal(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_goal(context, **kw):
@ -174,11 +166,11 @@ def get_test_scoring_engine(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_scoring_engine = db_utils.get_test_scoring_engine(**kw)
scoring_engine = objects.ScoringEngine(context)
for key in db_scoring_engine:
setattr(scoring_engine, key, db_scoring_engine[key])
return scoring_engine
obj_cls = objects.ScoringEngine
db_data = db_utils.get_test_scoring_engine(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_scoring_engine(context, **kw):
@ -198,13 +190,11 @@ def get_test_service(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_service = db_utils.get_test_service(**kw)
service = objects.Service(context)
for key in db_service:
if key == 'last_seen_up':
db_service[key] = None
setattr(service, key, db_service[key])
return service
obj_cls = objects.Service
db_data = db_utils.get_test_service(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_service(context, **kw):
@ -224,22 +214,11 @@ def get_test_strategy(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_strategy = db_utils.get_test_strategy(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_strategy['id']
strategy = objects.Strategy(context)
for key in db_strategy:
setattr(strategy, key, db_strategy[key])
obj_cls = objects.Strategy
db_data = db_utils.get_test_strategy(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
# ObjectField checks for the object type, so if we want to simulate a
# non-eager object loading, the field should not be referenced at all.
# Contrarily, eager loading need the data to be casted to the object type
# that was specified by the ObjectField.
if kw.get('goal'):
strategy.goal = objects.Goal(context, **kw.get('goal'))
return strategy
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_strategy(context, **kw):
@ -259,14 +238,11 @@ def get_test_efficacy_indicator(context, **kw):
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_efficacy_indicator = db_utils.get_test_efficacy_indicator(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_efficacy_indicator['id']
efficacy_indicator = objects.EfficacyIndicator(context)
for key in db_efficacy_indicator:
setattr(efficacy_indicator, key, db_efficacy_indicator[key])
return efficacy_indicator
obj_cls = objects.EfficacyIndicator
db_data = db_utils.get_test_efficacy_indicator(**kw)
obj_data = _load_related_objects(context, obj_cls, db_data)
return _load_test_obj(context, obj_cls, obj_data, **kw)
def create_test_efficacy_indicator(context, **kw):