diff --git a/doc/notification_samples/audit-strategy-end.json b/doc/notification_samples/audit-strategy-end.json new file mode 100644 index 000000000..3cf13b8c2 --- /dev/null +++ b/doc/notification_samples/audit-strategy-end.json @@ -0,0 +1,70 @@ +{ + "priority": "INFO", + "payload": { + "watcher_object.data": { + "audit_type": "ONESHOT", + "parameters": { + "para2": "hello", + "para1": 3.2 + }, + "state": "ONGOING", + "updated_at": null, + "deleted_at": null, + "fault": null, + "goal": { + "watcher_object.data": { + "uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", + "name": "dummy", + "updated_at": null, + "deleted_at": null, + "efficacy_specification": [], + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy goal" + }, + "watcher_object.name": "GoalPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "interval": null, + "scope": [], + "strategy": { + "watcher_object.data": { + "parameters_spec": { + "properties": { + "para2": { + "type": "string", + "default": "hello", + "description": "string parameter example" + }, + "para1": { + "description": "number parameter example", + "maximum": 10.2, + "type": "number", + "default": 3.2, + "minimum": 1.0 + } + } + }, + "name": "dummy", + "uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", + "updated_at": null, + "deleted_at": null, + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy strategy" + }, + "watcher_object.name": "StrategyPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "created_at": "2016-11-04T16:29:20Z", + "uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6" + }, + "watcher_object.name": "AuditActionPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "publisher_id": "infra-optim:localhost", + "timestamp": "2016-11-04 16:31:36.264673 ", + "event_type": "audit.strategy.end", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/doc/notification_samples/audit-strategy-error.json b/doc/notification_samples/audit-strategy-error.json new file mode 100644 index 000000000..91bb6f8b6 --- /dev/null +++ b/doc/notification_samples/audit-strategy-error.json @@ -0,0 +1,80 @@ +{ + "priority": "ERROR", + "payload": { + "watcher_object.data": { + "audit_type": "ONESHOT", + "parameters": { + "para2": "hello", + "para1": 3.2 + }, + "state": "ONGOING", + "updated_at": null, + "deleted_at": null, + "fault": { + "watcher_object.data": { + "exception": "WatcherException", + "exception_message": "TEST", + "function_name": "test_send_audit_action_with_error", + "module_name": "watcher.tests.notifications.test_audit_notification" + }, + "watcher_object.name": "ExceptionPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "goal": { + "watcher_object.data": { + "uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", + "name": "dummy", + "updated_at": null, + "deleted_at": null, + "efficacy_specification": [], + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy goal" + }, + "watcher_object.name": "GoalPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "interval": null, + "scope": [], + "strategy": { + "watcher_object.data": { + "parameters_spec": { + "properties": { + "para2": { + "type": "string", + "default": "hello", + "description": "string parameter example" + }, + "para1": { + "description": "number parameter example", + "maximum": 10.2, + "type": "number", + "default": 3.2, + "minimum": 1.0 + } + } + }, + "name": "dummy", + "uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", + "updated_at": null, + "deleted_at": null, + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy strategy" + }, + "watcher_object.name": "StrategyPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "created_at": "2016-11-04T16:29:20Z", + "uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6" + }, + "watcher_object.name": "AuditActionPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "publisher_id": "infra-optim:localhost", + "timestamp": "2016-11-04 16:31:36.264673 ", + "event_type": "audit.strategy.error", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/doc/notification_samples/audit-strategy-start.json b/doc/notification_samples/audit-strategy-start.json new file mode 100644 index 000000000..37646e37c --- /dev/null +++ b/doc/notification_samples/audit-strategy-start.json @@ -0,0 +1,70 @@ +{ + "priority": "INFO", + "payload": { + "watcher_object.data": { + "audit_type": "ONESHOT", + "parameters": { + "para2": "hello", + "para1": 3.2 + }, + "state": "ONGOING", + "updated_at": null, + "deleted_at": null, + "fault": null, + "goal": { + "watcher_object.data": { + "uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", + "name": "dummy", + "updated_at": null, + "deleted_at": null, + "efficacy_specification": [], + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy goal" + }, + "watcher_object.name": "GoalPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "interval": null, + "scope": [], + "strategy": { + "watcher_object.data": { + "parameters_spec": { + "properties": { + "para2": { + "type": "string", + "default": "hello", + "description": "string parameter example" + }, + "para1": { + "description": "number parameter example", + "maximum": 10.2, + "type": "number", + "default": 3.2, + "minimum": 1.0 + } + } + }, + "name": "dummy", + "uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", + "updated_at": null, + "deleted_at": null, + "created_at": "2016-11-04T16:25:35Z", + "display_name": "Dummy strategy" + }, + "watcher_object.name": "StrategyPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "created_at": "2016-11-04T16:29:20Z", + "uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6" + }, + "watcher_object.name": "AuditActionPayload", + "watcher_object.version": "1.0", + "watcher_object.namespace": "watcher" + }, + "publisher_id": "infra-optim:localhost", + "timestamp": "2016-11-04 16:31:36.264673 ", + "event_type": "audit.strategy.start", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/watcher/decision_engine/strategy/context/base.py b/watcher/decision_engine/strategy/context/base.py index ca89d85d0..37286b2b1 100644 --- a/watcher/decision_engine/strategy/context/base.py +++ b/watcher/decision_engine/strategy/context/base.py @@ -2,12 +2,13 @@ # Copyright (c) 2015 b<>com # # Authors: Jean-Emile DARTOIS +# Vincent FRANCOISE # # 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 +# 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, @@ -15,18 +16,50 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -# + import abc import six +from watcher import notifications +from watcher.objects import fields + @six.add_metaclass(abc.ABCMeta) -class BaseStrategyContext(object): +class StrategyContext(object): - @abc.abstractmethod def execute_strategy(self, audit, request_context): """Execute the strategy for the given an audit + :param audit: Audit object + :type audit: :py:class:`~.objects.audit.Audit` instance + :param request_context: Current request context + :type request_context: :py:class:`~.RequestContext` instance + :returns: The computed solution + :rtype: :py:class:`~.BaseSolution` instance + """ + try: + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.STRATEGY, + phase=fields.NotificationPhase.START) + solution = self.do_execute_strategy(audit, request_context) + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.STRATEGY, + phase=fields.NotificationPhase.END) + return solution + except Exception: + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.STRATEGY, + priority=fields.NotificationPriority.ERROR, + phase=fields.NotificationPhase.ERROR) + raise + + @abc.abstractmethod + def do_execute_strategy(self, audit, request_context): + """Execute the strategy for the given an audit + :param audit: Audit object :type audit: :py:class:`~.objects.audit.Audit` instance :param request_context: Current request context diff --git a/watcher/decision_engine/strategy/context/default.py b/watcher/decision_engine/strategy/context/default.py index e20797372..e0b56ba33 100644 --- a/watcher/decision_engine/strategy/context/default.py +++ b/watcher/decision_engine/strategy/context/default.py @@ -25,12 +25,12 @@ from watcher import objects LOG = log.getLogger(__name__) -class DefaultStrategyContext(base.BaseStrategyContext): +class DefaultStrategyContext(base.StrategyContext): def __init__(self): super(DefaultStrategyContext, self).__init__() LOG.debug("Initializing Strategy Context") - def execute_strategy(self, audit, request_context): + def do_execute_strategy(self, audit, request_context): osc = clients.OpenStackClients() # todo(jed) retrieve in audit parameters (threshold,...) # todo(jed) create ActionPlan @@ -42,8 +42,8 @@ class DefaultStrategyContext(base.BaseStrategyContext): # it could specify the Strategy uuid in the Audit. strategy_name = None if audit.strategy_id: - strategy = objects.Strategy.get_by_id(request_context, - audit.strategy_id) + strategy = objects.Strategy.get_by_id( + request_context, audit.strategy_id) strategy_name = strategy.name strategy_selector = default.DefaultStrategySelector( diff --git a/watcher/notifications/audit.py b/watcher/notifications/audit.py index 63d8663e0..1b05333fd 100644 --- a/watcher/notifications/audit.py +++ b/watcher/notifications/audit.py @@ -20,6 +20,7 @@ from oslo_config import cfg from watcher.common import exception from watcher.notifications import base as notificationbase +from watcher.notifications import exception as exception_notifications from watcher.notifications import goal as goal_notifications from watcher.notifications import strategy as strategy_notifications from watcher.objects import base @@ -38,8 +39,6 @@ class AuditPayload(notificationbase.NotificationPayloadBase): 'parameters': ('audit', 'parameters'), 'interval': ('audit', 'interval'), 'scope': ('audit', 'scope'), - # 'goal_uuid': ('audit', 'goal_uuid'), - # 'strategy_uuid': ('audit', 'strategy_uuid'), 'created_at': ('audit', 'created_at'), 'updated_at': ('audit', 'updated_at'), @@ -111,6 +110,22 @@ class AuditUpdatePayload(AuditPayload): strategy=strategy) +@base.WatcherObjectRegistry.register_notification +class AuditActionPayload(AuditPayload): + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'fault': wfields.ObjectField('ExceptionPayload', nullable=True), + } + + def __init__(self, audit, goal, strategy, **kwargs): + super(AuditActionPayload, self).__init__( + audit=audit, + goal=goal, + strategy=strategy, + **kwargs) + + @base.WatcherObjectRegistry.register_notification class AuditDeletePayload(AuditPayload): # Version 1.0: Initial version @@ -124,16 +139,17 @@ class AuditDeletePayload(AuditPayload): strategy=strategy) -# @notificationbase.notification_sample('audit-create.json') -# @notificationbase.notification_sample('audit-delete.json') -# @base.WatcherObjectRegistry.register_notification -# class AuditActionNotification(notificationbase.NotificationBase): -# # Version 1.0: Initial version -# VERSION = '1.0' +@notificationbase.notification_sample('audit-strategy-error.json') +@notificationbase.notification_sample('audit-strategy-end.json') +@notificationbase.notification_sample('audit-strategy-start.json') +@base.WatcherObjectRegistry.register_notification +class AuditActionNotification(notificationbase.NotificationBase): + # Version 1.0: Initial version + VERSION = '1.0' -# fields = { -# 'payload': wfields.ObjectField('AuditActionPayload') -# } + fields = { + 'payload': wfields.ObjectField('AuditActionPayload') + } @notificationbase.notification_sample('audit-create.json') @@ -261,3 +277,34 @@ def send_delete(context, audit, service='infra-optim', host=None): payload=versioned_payload) notification.emit(context) + + +def send_action_notification(context, audit, action, phase=None, + priority=wfields.NotificationPriority.INFO, + service='infra-optim', host=None): + """Emit an audit action notification.""" + goal_payload, strategy_payload = _get_common_payload(audit) + + fault = None + if phase == wfields.NotificationPhase.ERROR: + fault = exception_notifications.ExceptionPayload.from_exception() + + versioned_payload = AuditActionPayload( + audit=audit, + goal=goal_payload, + strategy=strategy_payload, + fault=fault, + ) + + notification = AuditActionNotification( + priority=priority, + event_type=notificationbase.EventType( + object='audit', + action=action, + phase=phase), + publisher=notificationbase.NotificationPublisher( + host=host or CONF.host, + binary=service), + payload=versioned_payload) + + notification.emit(context) diff --git a/watcher/notifications/base.py b/watcher/notifications/base.py index ec0a3b966..cc3d7a273 100644 --- a/watcher/notifications/base.py +++ b/watcher/notifications/base.py @@ -55,8 +55,10 @@ class NotificationObject(base.WatcherObject): @base.WatcherObjectRegistry.register_notification class EventType(NotificationObject): + # Version 1.0: Initial version - VERSION = '1.0' + # Version 1.1: Added STRATEGY action in NotificationAction enum + VERSION = '1.1' fields = { 'object': wfields.StringField(), diff --git a/watcher/notifications/exception.py b/watcher/notifications/exception.py index be6d4bb85..68fc1eb03 100644 --- a/watcher/notifications/exception.py +++ b/watcher/notifications/exception.py @@ -11,6 +11,7 @@ # under the License. import inspect +import sys import six @@ -31,7 +32,8 @@ class ExceptionPayload(notificationbase.NotificationPayloadBase): } @classmethod - def from_exception(cls, fault): + def from_exception(cls, fault=None): + fault = fault or sys.exc_info()[1] trace = inspect.trace()[-1] # TODO(gibi): apply strutils.mask_password on exception_message and # consider emitting the exception_message only if the safe flag is diff --git a/watcher/objects/fields.py b/watcher/objects/fields.py index 3ff2e538a..07a7aadba 100644 --- a/watcher/objects/fields.py +++ b/watcher/objects/fields.py @@ -126,7 +126,9 @@ class NotificationAction(BaseWatcherEnum): EXCEPTION = 'exception' DELETE = 'delete' - ALL = (CREATE, UPDATE, EXCEPTION, DELETE) + STRATEGY = 'strategy' + + ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY) class NotificationPriorityField(BaseEnumField): diff --git a/watcher/tests/decision_engine/audit/test_audit_handlers.py b/watcher/tests/decision_engine/audit/test_audit_handlers.py index be9956296..319f886b6 100644 --- a/watcher/tests/decision_engine/audit/test_audit_handlers.py +++ b/watcher/tests/decision_engine/audit/test_audit_handlers.py @@ -21,7 +21,9 @@ from oslo_utils import uuidutils from watcher.decision_engine.audit import continuous from watcher.decision_engine.audit import oneshot from watcher.decision_engine.model.collector import manager -from watcher.objects import audit as audit_objects +from watcher.decision_engine.strategy.strategies import dummy_strategy +from watcher import notifications +from watcher import objects from watcher.tests.db import base from watcher.tests.decision_engine.model import faker_cluster_state as faker from watcher.tests.objects import utils as obj_utils @@ -31,10 +33,16 @@ class TestOneShotAuditHandler(base.DbTestCase): def setUp(self): super(TestOneShotAuditHandler, self).setUp() + p_audit_notifications = mock.patch.object( + notifications, 'audit', autospec=True) + self.m_audit_notifications = p_audit_notifications.start() + self.addCleanup(p_audit_notifications.stop) + self.goal = obj_utils.create_test_goal( - self.context, id=1, name="dummy") + self.context, id=1, name=dummy_strategy.DummyStrategy.get_name()) self.strategy = obj_utils.create_test_strategy( - self.context, name='dummy', goal_id=self.goal.id) + self.context, name=dummy_strategy.DummyStrategy.get_name(), + goal_id=self.goal.id) audit_template = obj_utils.create_test_audit_template( self.context, strategy_id=self.strategy.id) self.audit = obj_utils.create_test_audit( @@ -46,18 +54,82 @@ class TestOneShotAuditHandler(base.DbTestCase): goal=self.goal) @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") - def test_trigger_audit_without_errors(self, mock_collector): - mock_collector.return_value = faker.FakerModelCollector() + def test_trigger_audit_without_errors(self, m_collector): + m_collector.return_value = faker.FakerModelCollector() audit_handler = oneshot.OneShotAuditHandler(mock.MagicMock()) audit_handler.execute(self.audit, self.context) + expected_calls = [ + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END)] + + self.assertEqual( + expected_calls, + self.m_audit_notifications.send_action_notification.call_args_list) + + @mock.patch.object(dummy_strategy.DummyStrategy, "do_execute") @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") - def test_trigger_audit_state_succeeded(self, mock_collector): - mock_collector.return_value = faker.FakerModelCollector() + def test_trigger_audit_with_error(self, m_collector, m_do_execute): + m_collector.return_value = faker.FakerModelCollector() + m_do_execute.side_effect = Exception audit_handler = oneshot.OneShotAuditHandler(mock.MagicMock()) audit_handler.execute(self.audit, self.context) - audit = audit_objects.Audit.get_by_uuid(self.context, self.audit.uuid) - self.assertEqual(audit_objects.State.SUCCEEDED, audit.state) + + expected_calls = [ + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + priority=objects.fields.NotificationPriority.ERROR, + phase=objects.fields.NotificationPhase.ERROR)] + + self.assertEqual( + expected_calls, + self.m_audit_notifications.send_action_notification.call_args_list) + + @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") + def test_trigger_audit_state_succeeded(self, m_collector): + m_collector.return_value = faker.FakerModelCollector() + audit_handler = oneshot.OneShotAuditHandler(mock.MagicMock()) + audit_handler.execute(self.audit, self.context) + audit = objects.audit.Audit.get_by_uuid(self.context, self.audit.uuid) + self.assertEqual(objects.audit.State.SUCCEEDED, audit.state) + + expected_calls = [ + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END)] + + self.assertEqual( + expected_calls, + self.m_audit_notifications.send_action_notification.call_args_list) + + @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") + def test_trigger_audit_send_notification(self, m_collector): + messaging = mock.MagicMock() + m_collector.return_value = faker.FakerModelCollector() + audit_handler = oneshot.OneShotAuditHandler(messaging) + audit_handler.execute(self.audit, self.context) + + expected_calls = [ + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END)] + + self.assertEqual( + expected_calls, + self.m_audit_notifications.send_action_notification.call_args_list) class TestContinuousAuditHandler(base.DbTestCase): @@ -65,7 +137,7 @@ class TestContinuousAuditHandler(base.DbTestCase): def setUp(self): super(TestContinuousAuditHandler, self).setUp() self.goal = obj_utils.create_test_goal( - self.context, id=1, name="dummy") + self.context, id=1, name=dummy_strategy.DummyStrategy.get_name()) audit_template = obj_utils.create_test_audit_template( self.context) self.audits = [ @@ -75,31 +147,31 @@ class TestContinuousAuditHandler(base.DbTestCase): uuid=uuidutils.generate_uuid(), audit_template_id=audit_template.id, goal_id=self.goal.id, - audit_type=audit_objects.AuditType.CONTINUOUS.value, + audit_type=objects.audit.AuditType.CONTINUOUS.value, goal=self.goal) for id_ in range(2, 4)] @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") @mock.patch.object(background.BackgroundScheduler, 'add_job') @mock.patch.object(background.BackgroundScheduler, 'get_jobs') - @mock.patch.object(audit_objects.Audit, 'list') + @mock.patch.object(objects.audit.Audit, 'list') def test_launch_audits_periodically(self, mock_list, mock_jobs, - mock_add_job, mock_collector): + m_add_job, m_collector): audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) mock_list.return_value = self.audits mock_jobs.return_value = mock.MagicMock() - mock_add_job.return_value = audit_handler.execute_audit( + m_add_job.return_value = audit_handler.execute_audit( self.audits[0], self.context) - mock_collector.return_value = faker.FakerModelCollector() + m_collector.return_value = faker.FakerModelCollector() audit_handler.launch_audits_periodically() - mock_add_job.assert_called() + m_add_job.assert_called() @mock.patch.object(background.BackgroundScheduler, 'add_job') @mock.patch.object(background.BackgroundScheduler, 'get_jobs') - @mock.patch.object(audit_objects.Audit, 'list') + @mock.patch.object(objects.audit.Audit, 'list') def test_launch_multiply_audits_periodically(self, mock_list, - mock_jobs, mock_add_job): + mock_jobs, m_add_job): audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) mock_list.return_value = self.audits mock_jobs.return_value = mock.MagicMock() @@ -109,26 +181,26 @@ class TestContinuousAuditHandler(base.DbTestCase): name='execute_audit', next_run_time=mock.ANY) for audit in self.audits] audit_handler.launch_audits_periodically() - mock_add_job.assert_has_calls(calls) + m_add_job.assert_has_calls(calls) @mock.patch.object(background.BackgroundScheduler, 'add_job') @mock.patch.object(background.BackgroundScheduler, 'get_jobs') - @mock.patch.object(audit_objects.Audit, 'list') + @mock.patch.object(objects.audit.Audit, 'list') def test_period_audit_not_called_when_deleted(self, mock_list, - mock_jobs, mock_add_job): + mock_jobs, m_add_job): audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) mock_list.return_value = self.audits mock_jobs.return_value = mock.MagicMock() - self.audits[1].state = audit_objects.State.CANCELLED + self.audits[1].state = objects.audit.State.CANCELLED calls = [mock.call(audit_handler.execute_audit, 'interval', args=[mock.ANY, mock.ANY], seconds=3600, name='execute_audit', next_run_time=mock.ANY)] audit_handler.launch_audits_periodically() - mock_add_job.assert_has_calls(calls) + m_add_job.assert_has_calls(calls) audit_handler.update_audit_state(self.audits[1], - audit_objects.State.CANCELLED) + objects.audit.State.CANCELLED) is_inactive = audit_handler._is_audit_inactive(self.audits[1]) self.assertTrue(is_inactive) diff --git a/watcher/tests/decision_engine/strategy/context/test_strategy_context.py b/watcher/tests/decision_engine/strategy/context/test_strategy_context.py index f22781595..b6b5c805f 100644 --- a/watcher/tests/decision_engine/strategy/context/test_strategy_context.py +++ b/watcher/tests/decision_engine/strategy/context/test_strategy_context.py @@ -13,6 +13,7 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + import mock from watcher.common import utils @@ -26,6 +27,7 @@ from watcher.tests.objects import utils as obj_utils class TestStrategyContext(base.DbTestCase): + def setUp(self): super(TestStrategyContext, self).setUp() obj_utils.create_test_goal(self.context, id=1, name="DUMMY") diff --git a/watcher/tests/notifications/test_audit_notification.py b/watcher/tests/notifications/test_audit_notification.py index 29afb24c4..62ded61d0 100644 --- a/watcher/tests/notifications/test_audit_notification.py +++ b/watcher/tests/notifications/test_audit_notification.py @@ -293,3 +293,157 @@ class TestAuditNotification(base.DbTestCase): }, payload ) + + @freezegun.freeze_time('2016-10-18T09:52:05.219414') + @mock.patch.object(notifications.audit.AuditActionNotification, '_emit') + def test_send_audit_action(self, mock_emit): + goal = utils.create_test_goal(mock.Mock()) + strategy = utils.create_test_strategy(mock.Mock()) + audit = utils.create_test_audit( + mock.Mock(), state=objects.audit.State.ONGOING, + goal_id=goal.id, strategy_id=strategy.id, + goal=goal, strategy=strategy) + notifications.audit.send_action_notification( + mock.MagicMock(), audit, host='node0', + action='strategy', phase='start') + + self.assertEqual(1, mock_emit.call_count) + notification = mock_emit.call_args_list[0][1] + + self.assertDictEqual( + { + "event_type": "audit.strategy.start", + "payload": { + "watcher_object.data": { + "audit_type": "ONESHOT", + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "fault": None, + "goal": { + "watcher_object.data": { + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "display_name": "test goal", + "efficacy_specification": [], + "name": "TEST", + "updated_at": None, + "uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652" + }, + "watcher_object.name": "GoalPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "interval": 3600, + "parameters": {}, + "scope": [], + "state": "ONGOING", + "strategy": { + "watcher_object.data": { + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "display_name": "test strategy", + "name": "TEST", + "parameters_spec": {}, + "updated_at": None, + "uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3" + }, + "watcher_object.name": "StrategyPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "updated_at": None, + "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d" + }, + "watcher_object.name": "AuditActionPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "publisher_id": "infra-optim:node0" + }, + notification + ) + + @freezegun.freeze_time('2016-10-18T09:52:05.219414') + @mock.patch.object(notifications.audit.AuditActionNotification, '_emit') + def test_send_audit_action_with_error(self, mock_emit): + goal = utils.create_test_goal(mock.Mock()) + strategy = utils.create_test_strategy(mock.Mock()) + audit = utils.create_test_audit( + mock.Mock(), state=objects.audit.State.ONGOING, + goal_id=goal.id, strategy_id=strategy.id, + goal=goal, strategy=strategy) + + try: + # This is to load the exception in sys.exc_info() + raise exception.WatcherException("TEST") + except exception.WatcherException: + notifications.audit.send_action_notification( + mock.MagicMock(), audit, host='node0', + action='strategy', priority='error', phase='error') + + self.assertEqual(1, mock_emit.call_count) + notification = mock_emit.call_args_list[0][1] + self.assertDictEqual( + { + "event_type": "audit.strategy.error", + "payload": { + "watcher_object.data": { + "audit_type": "ONESHOT", + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "fault": { + "watcher_object.data": { + "exception": "WatcherException", + "exception_message": "TEST", + "function_name": ( + "test_send_audit_action_with_error"), + "module_name": "watcher.tests.notifications." + "test_audit_notification" + }, + "watcher_object.name": "ExceptionPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "goal": { + "watcher_object.data": { + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "display_name": "test goal", + "efficacy_specification": [], + "name": "TEST", + "updated_at": None, + "uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652" + }, + "watcher_object.name": "GoalPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "interval": 3600, + "parameters": {}, + "scope": [], + "state": "ONGOING", + "strategy": { + "watcher_object.data": { + "created_at": "2016-10-18T09:52:05Z", + "deleted_at": None, + "display_name": "test strategy", + "name": "TEST", + "parameters_spec": {}, + "updated_at": None, + "uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3" + }, + "watcher_object.name": "StrategyPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "updated_at": None, + "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d" + }, + "watcher_object.name": "AuditActionPayload", + "watcher_object.namespace": "watcher", + "watcher_object.version": "1.0" + }, + "publisher_id": "infra-optim:node0" + }, + notification + ) diff --git a/watcher/tests/notifications/test_notification.py b/watcher/tests/notifications/test_notification.py index 3cc0ced2a..0386ad59c 100644 --- a/watcher/tests/notifications/test_notification.py +++ b/watcher/tests/notifications/test_notification.py @@ -250,7 +250,7 @@ class TestNotificationBase(testbase.TestCase): expected_notification_fingerprints = { - 'EventType': '1.0-92100a9f0908da98dfcfff9c42e0018c', + 'EventType': '1.1-652f407fcf72d2045d65974d23c78173', 'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866', 'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b', 'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545', @@ -262,6 +262,8 @@ expected_notification_fingerprints = { 'AuditCreatePayload': '1.0-30c85c834648c8ca11f54fc5e084d86b', 'AuditDeleteNotification': '1.0-9b69de0724fda8310d05e18418178866', 'AuditDeletePayload': '1.0-30c85c834648c8ca11f54fc5e084d86b', + 'AuditActionNotification': '1.0-9b69de0724fda8310d05e18418178866', + 'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52', 'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a', 'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1', }