diff --git a/doc/notification_samples/audit-planner-end.json b/doc/notification_samples/audit-planner-end.json new file mode 100644 index 000000000..8b820ee30 --- /dev/null +++ b/doc/notification_samples/audit-planner-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.planner.end", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/doc/notification_samples/audit-planner-error.json b/doc/notification_samples/audit-planner-error.json new file mode 100644 index 000000000..5f56604bd --- /dev/null +++ b/doc/notification_samples/audit-planner-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.planner.error", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/doc/notification_samples/audit-planner-start.json b/doc/notification_samples/audit-planner-start.json new file mode 100644 index 000000000..e5a93806e --- /dev/null +++ b/doc/notification_samples/audit-planner-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.planner.start", + "message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6" +} diff --git a/watcher/decision_engine/audit/base.py b/watcher/decision_engine/audit/base.py index 3163feca5..f9ffdbc3a 100644 --- a/watcher/decision_engine/audit/base.py +++ b/watcher/decision_engine/audit/base.py @@ -24,13 +24,16 @@ from oslo_log import log from watcher.decision_engine.planner import manager as planner_manager from watcher.decision_engine.strategy.context import default as default_context +from watcher import notifications from watcher import objects +from watcher.objects import fields LOG = log.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class BaseAuditHandler(object): + @abc.abstractmethod def execute(self, audit_uuid, request_context): raise NotImplementedError() @@ -70,6 +73,25 @@ class AuditHandler(BaseAuditHandler): def strategy_context(self): return self._strategy_context + def do_schedule(self, request_context, audit, solution): + try: + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.PLANNER, + phase=fields.NotificationPhase.START) + self.planner.schedule(request_context, audit.id, solution) + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.PLANNER, + phase=fields.NotificationPhase.END) + except Exception: + notifications.audit.send_action_notification( + request_context, audit, + action=fields.NotificationAction.PLANNER, + priority=fields.NotificationPriority.ERROR, + phase=fields.NotificationPhase.ERROR) + raise + @staticmethod def update_audit_state(audit, state): LOG.debug("Update audit state: %s", state) @@ -82,8 +104,7 @@ class AuditHandler(BaseAuditHandler): self.update_audit_state(audit, objects.audit.State.ONGOING) def post_execute(self, audit, solution, request_context): - self.planner.schedule(request_context, audit.id, solution) - + self.do_schedule(request_context, audit, solution) # change state of the audit to SUCCEEDED self.update_audit_state(audit, objects.audit.State.SUCCEEDED) diff --git a/watcher/decision_engine/audit/continuous.py b/watcher/decision_engine/audit/continuous.py index 362a4167b..046d1833f 100644 --- a/watcher/decision_engine/audit/continuous.py +++ b/watcher/decision_engine/audit/continuous.py @@ -91,7 +91,7 @@ class ContinuousAuditHandler(base.AuditHandler): self.execute(audit, request_context) def post_execute(self, audit, solution, request_context): - self.planner.schedule(request_context, audit.id, solution) + self.do_schedule(request_context, audit, solution) def launch_audits_periodically(self): audit_context = context.RequestContext(is_admin=True) diff --git a/watcher/notifications/base.py b/watcher/notifications/base.py index cc3d7a273..69b66a0ef 100644 --- a/watcher/notifications/base.py +++ b/watcher/notifications/base.py @@ -58,7 +58,8 @@ class EventType(NotificationObject): # Version 1.0: Initial version # Version 1.1: Added STRATEGY action in NotificationAction enum - VERSION = '1.1' + # Version 1.2: Added PLANNER action in NotificationAction enum + VERSION = '1.2' fields = { 'object': wfields.StringField(), diff --git a/watcher/objects/fields.py b/watcher/objects/fields.py index 07a7aadba..04db1da22 100644 --- a/watcher/objects/fields.py +++ b/watcher/objects/fields.py @@ -127,8 +127,9 @@ class NotificationAction(BaseWatcherEnum): DELETE = 'delete' STRATEGY = 'strategy' + PLANNER = 'planner' - ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY) + ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER) 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 319f886b6..6e15178b8 100644 --- a/watcher/tests/decision_engine/audit/test_audit_handlers.py +++ b/watcher/tests/decision_engine/audit/test_audit_handlers.py @@ -65,6 +65,12 @@ class TestOneShotAuditHandler(base.DbTestCase): phase=objects.fields.NotificationPhase.START), mock.call(self.context, self.audit, action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, phase=objects.fields.NotificationPhase.END)] self.assertEqual( @@ -106,6 +112,12 @@ class TestOneShotAuditHandler(base.DbTestCase): phase=objects.fields.NotificationPhase.START), mock.call(self.context, self.audit, action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, phase=objects.fields.NotificationPhase.END)] self.assertEqual( @@ -125,6 +137,12 @@ class TestOneShotAuditHandler(base.DbTestCase): phase=objects.fields.NotificationPhase.START), mock.call(self.context, self.audit, action=objects.fields.NotificationAction.STRATEGY, + phase=objects.fields.NotificationPhase.END), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, + phase=objects.fields.NotificationPhase.START), + mock.call(self.context, self.audit, + action=objects.fields.NotificationAction.PLANNER, phase=objects.fields.NotificationPhase.END)] self.assertEqual( diff --git a/watcher/tests/notifications/test_audit_notification.py b/watcher/tests/notifications/test_audit_notification.py index 62ded61d0..1573dc5b1 100644 --- a/watcher/tests/notifications/test_audit_notification.py +++ b/watcher/tests/notifications/test_audit_notification.py @@ -14,43 +14,58 @@ 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 TestAuditNotification(base.DbTestCase): - @mock.patch.object(notifications.audit.AuditUpdateNotification, '_emit') - def test_send_version_invalid_audit(self, mock_emit): + def setUp(self): + super(TestAuditNotification, 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.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) self.assertRaises( exception.InvalidAudit, notifications.audit.send_update, - mock.MagicMock(), audit, 'host', 'node0') + mock.MagicMock(), audit, host='node0') - @freezegun.freeze_time('2016-10-18T09:52:05.219414') - @mock.patch.object(notifications.audit.AuditUpdateNotification, '_emit') - def test_send_version_audit_update_with_strategy(self, mock_emit): - goal = utils.create_test_goal(mock.Mock()) - strategy = utils.create_test_strategy(mock.Mock()) + def test_send_audit_update_with_strategy(self): audit = utils.create_test_audit( mock.Mock(), state=objects.audit.State.ONGOING, - goal_id=goal.id, strategy_id=strategy.id, - goal=goal, strategy=strategy) + goal_id=self.goal.id, strategy_id=self.strategy.id, + goal=self.goal, strategy=self.strategy) notifications.audit.send_update( - mock.MagicMock(), audit, 'host', 'node0', + mock.MagicMock(), audit, host='node0', old_state=objects.audit.State.PENDING) - self.assertEqual(1, mock_emit.call_count) - notification = mock_emit.call_args_list[0][1] + # The 1st notification is because we created the object. + 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", @@ -108,21 +123,19 @@ class TestAuditNotification(base.DbTestCase): payload ) - @freezegun.freeze_time('2016-10-18T09:52:05.219414') - @mock.patch.object(notifications.audit.AuditUpdateNotification, '_emit') - def test_send_version_audit_update_without_strategy(self, mock_emit): - goal = utils.create_test_goal(mock.Mock(), id=1) + def test_send_audit_update_without_strategy(self): audit = utils.get_test_audit( mock.Mock(), state=objects.audit.State.ONGOING, - goal_id=goal.id, goal=goal) + goal_id=self.goal.id, goal=self.goal) notifications.audit.send_update( - mock.MagicMock(), audit, 'host', 'node0', + mock.MagicMock(), audit, host='node0', old_state=objects.audit.State.PENDING) - self.assertEqual(1, mock_emit.call_count) - notification = mock_emit.call_args_list[0][1] + self.assertEqual(1, 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", @@ -167,22 +180,19 @@ class TestAuditNotification(base.DbTestCase): payload ) - @freezegun.freeze_time('2016-10-18T09:52:05.219414') - @mock.patch.object(notifications.audit.AuditCreateNotification, '_emit') - def test_send_version_audit_create(self, mock_emit): - goal = utils.create_test_goal(mock.Mock()) - strategy = utils.create_test_strategy(mock.Mock()) + def test_send_audit_create(self): audit = utils.get_test_audit( mock.Mock(), state=objects.audit.State.PENDING, - goal_id=goal.id, strategy_id=strategy.id, - goal=goal.as_dict(), strategy=strategy.as_dict()) + goal_id=self.goal.id, strategy_id=self.strategy.id, + goal=self.goal.as_dict(), strategy=self.strategy.as_dict()) notifications.audit.send_create( - mock.MagicMock(), audit, 'host', 'node0') + mock.MagicMock(), audit, host='node0') - self.assertEqual(1, mock_emit.call_count) - notification = mock_emit.call_args_list[0][1] + self.assertEqual(1, 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", @@ -231,21 +241,19 @@ class TestAuditNotification(base.DbTestCase): payload ) - @freezegun.freeze_time('2016-10-18T09:52:05.219414') - @mock.patch.object(notifications.audit.AuditDeleteNotification, '_emit') - def test_send_version_audit_delete(self, mock_emit): - goal = utils.create_test_goal(mock.Mock()) - strategy = utils.create_test_strategy(mock.Mock()) + def test_send_audit_delete(self): audit = utils.create_test_audit( mock.Mock(), state=objects.audit.State.DELETED, - goal_id=goal.id, strategy_id=strategy.id) + goal_id=self.goal.id, strategy_id=self.strategy.id) notifications.audit.send_delete( - mock.MagicMock(), audit, 'host', 'node0') + mock.MagicMock(), audit, host='node0') - self.assertEqual(1, mock_emit.call_count) - notification = mock_emit.call_args_list[0][1] + # The 1st notification is because we created the object. + 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", @@ -294,22 +302,21 @@ 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()) + def test_send_audit_action(self): audit = utils.create_test_audit( mock.Mock(), state=objects.audit.State.ONGOING, - goal_id=goal.id, strategy_id=strategy.id, - goal=goal, strategy=strategy) + goal_id=self.goal.id, strategy_id=self.strategy.id, + goal=self.goal, strategy=self.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] + # The 1st notification is because we created the object. + self.assertEqual(2, self.m_notifier.info.call_count) + notification = self.m_notifier.info.call_args[1] + notification = self.m_notifier.info.call_args[1] + self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id) self.assertDictEqual( { "event_type": "audit.strategy.start", @@ -357,21 +364,16 @@ class TestAuditNotification(base.DbTestCase): "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()) + def test_send_audit_action_with_error(self): audit = utils.create_test_audit( mock.Mock(), state=objects.audit.State.ONGOING, - goal_id=goal.id, strategy_id=strategy.id, - goal=goal, strategy=strategy) + goal_id=self.goal.id, strategy_id=self.strategy.id, + goal=self.goal, strategy=self.strategy) try: # This is to load the exception in sys.exc_info() @@ -381,8 +383,9 @@ class TestAuditNotification(base.DbTestCase): 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.assertEqual(1, self.m_notifier.error.call_count) + notification = self.m_notifier.error.call_args[1] + self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id) self.assertDictEqual( { "event_type": "audit.strategy.error", @@ -442,8 +445,7 @@ class TestAuditNotification(base.DbTestCase): "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 0386ad59c..770d25270 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.1-652f407fcf72d2045d65974d23c78173', + 'EventType': '1.2-633c2d32fa849d2a6f8bda3b0db88332', 'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866', 'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b', 'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',