Implemented audit.update notification

In this changeset, I implemented the sending of update notifications
whenever an audit is modified.

Change-Id: I5ccc2516ce896ae7d4ef542b133e8f052eaed602
Partially-Implements: blueprint audit-versioned-notifications-api
This commit is contained in:
Vincent Françoise
2016-10-17 17:48:39 +02:00
parent 54c45a2738
commit 9405eb0806
25 changed files with 722 additions and 85 deletions

View File

@@ -252,7 +252,7 @@ class TestPatch(api_base.FunctionalTest):
obj_utils.create_test_goal(self.context)
obj_utils.create_test_strategy(self.context)
obj_utils.create_test_audit_template(self.context)
self.audit = obj_utils.create_test_audit(self.context, )
self.audit = obj_utils.create_test_audit(self.context)
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
self.mock_audit_update = p.start()
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update

View File

@@ -75,7 +75,6 @@ def get_test_audit(**kwargs):
'strategy_id': kwargs.get('strategy_id', None),
'scope': kwargs.get('scope', []),
}
# ObjectField doesn't allow None nor dict, so if we want to simulate a
# non-eager object loading, the field should not be referenced at all.
if kwargs.get('goal'):

View File

@@ -14,10 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import uuid
from apscheduler.schedulers import background
import mock
from oslo_utils import uuidutils
from watcher.decision_engine.audit import continuous
from watcher.decision_engine.audit import oneshot
@@ -32,15 +31,19 @@ class TestOneShotAuditHandler(base.DbTestCase):
def setUp(self):
super(TestOneShotAuditHandler, self).setUp()
obj_utils.create_test_goal(self.context, id=1, name="dummy")
self.goal = obj_utils.create_test_goal(
self.context, id=1, name="dummy")
self.strategy = obj_utils.create_test_strategy(
self.context, name='dummy')
self.context, name='dummy', 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(
self.context,
uuid=uuidutils.generate_uuid(),
goal_id=self.goal.id,
strategy_id=self.strategy.id,
audit_template_id=audit_template.id)
audit_template_id=audit_template.id,
goal=self.goal)
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector")
def test_trigger_audit_without_errors(self, mock_collector):
@@ -58,18 +61,23 @@ class TestOneShotAuditHandler(base.DbTestCase):
class TestContinuousAuditHandler(base.DbTestCase):
def setUp(self):
super(TestContinuousAuditHandler, self).setUp()
obj_utils.create_test_goal(self.context, id=1, name="dummy")
self.goal = obj_utils.create_test_goal(
self.context, id=1, name="dummy")
audit_template = obj_utils.create_test_audit_template(
self.context)
self.audits = [
obj_utils.create_test_audit(
self.context,
uuid=uuid.uuid4(),
id=id_,
uuid=uuidutils.generate_uuid(),
audit_template_id=audit_template.id,
audit_type=audit_objects.AuditType.CONTINUOUS.value)
for i in range(2)]
goal_id=self.goal.id,
audit_type=audit_objects.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')
@@ -78,9 +86,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
def test_launch_audits_periodically(self, mock_list, mock_jobs,
mock_add_job, mock_collector):
audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock())
audits = [audit_objects.Audit.get_by_uuid(self.context,
self.audits[0].uuid)]
mock_list.return_value = audits
mock_list.return_value = self.audits
mock_jobs.return_value = mock.MagicMock()
mock_add_job.return_value = audit_handler.execute_audit(
self.audits[0], self.context)
@@ -95,10 +101,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
def test_launch_multiply_audits_periodically(self, mock_list,
mock_jobs, mock_add_job):
audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock())
audits = [audit_objects.Audit.get_by_uuid(
self.context,
audit.uuid) for audit in self.audits]
mock_list.return_value = audits
mock_list.return_value = self.audits
mock_jobs.return_value = mock.MagicMock()
calls = [mock.call(audit_handler.execute_audit, 'interval',
args=[mock.ANY, mock.ANY],
@@ -114,12 +117,9 @@ class TestContinuousAuditHandler(base.DbTestCase):
def test_period_audit_not_called_when_deleted(self, mock_list,
mock_jobs, mock_add_job):
audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock())
audits = [audit_objects.Audit.get_by_uuid(
self.context,
audit.uuid) for audit in self.audits]
mock_list.return_value = audits
mock_list.return_value = self.audits
mock_jobs.return_value = mock.MagicMock()
audits[1].state = audit_objects.State.CANCELLED
self.audits[1].state = audit_objects.State.CANCELLED
calls = [mock.call(audit_handler.execute_audit, 'interval',
args=[mock.ANY, mock.ANY],
seconds=3600,
@@ -128,7 +128,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
audit_handler.launch_audits_periodically()
mock_add_job.assert_has_calls(calls)
audit_handler.update_audit_state(audits[1],
audit_handler.update_audit_state(self.audits[1],
audit_objects.State.CANCELLED)
is_inactive = audit_handler._is_audit_inactive(audits[1])
is_inactive = audit_handler._is_audit_inactive(self.audits[1])
self.assertTrue(is_inactive)

View File

@@ -0,0 +1,163 @@
# 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
from watcher.common import exception
from watcher.notifications import audit as auditnotifs
from watcher.tests import base as testbase
from watcher.tests.objects import utils
class TestAuditNotification(testbase.TestCase):
@mock.patch.object(auditnotifs.AuditUpdateNotification, '_emit')
def test_send_version_invalid_audit(self, mock_emit):
audit = utils.get_test_audit(mock.Mock(), state='DOESNOTMATTER',
goal_id=1)
self.assertRaises(
exception.InvalidAudit,
auditnotifs.send_update,
mock.MagicMock(), audit, 'host', 'node0')
@freezegun.freeze_time('2016-10-18T09:52:05.219414')
@mock.patch.object(auditnotifs.AuditUpdateNotification, '_emit')
def test_send_version_audit_update_with_strategy(self, mock_emit):
goal = utils.get_test_goal(mock.Mock(), id=1)
strategy = utils.get_test_strategy(mock.Mock(), id=1)
audit = utils.get_test_audit(mock.Mock(), state='ONGOING',
goal_id=goal.id, strategy_id=strategy.id,
goal=goal, strategy=strategy)
auditnotifs.send_update(
mock.MagicMock(), audit, 'host', 'node0', old_state='PENDING')
self.assertEqual(1, mock_emit.call_count)
notification = mock_emit.call_args_list[0][1]
payload = notification['payload']
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"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": None,
"display_name": "test strategy",
"deleted_at": None
},
"watcher_object.name": "StrategyPayload"
},
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"updated_at": None,
"uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"name": "TEST",
"efficacy_specification": [],
"created_at": None,
"display_name": "test goal",
"deleted_at": None
},
"watcher_object.name": "GoalPayload"
},
"deleted_at": None,
"scope": [],
"state": "ONGOING",
"updated_at": None,
"created_at": None,
"state_update": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"old_state": "PENDING",
"state": "ONGOING"
},
"watcher_object.name": "AuditStateUpdatePayload"
},
"audit_type": "ONESHOT"
},
"watcher_object.name": "AuditUpdatePayload"
},
payload
)
@freezegun.freeze_time('2016-10-18T09:52:05.219414')
@mock.patch.object(auditnotifs.AuditUpdateNotification, '_emit')
def test_send_version_audit_update_without_strategy(self, mock_emit):
goal = utils.get_test_goal(mock.Mock(), id=1)
audit = utils.get_test_audit(
mock.Mock(), state='ONGOING', goal_id=goal.id, goal=goal)
auditnotifs.send_update(
mock.MagicMock(), audit, 'host', 'node0', old_state='PENDING')
self.assertEqual(1, mock_emit.call_count)
notification = mock_emit.call_args_list[0][1]
payload = notification['payload']
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"interval": 3600,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"updated_at": None,
"uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
"name": "TEST",
"efficacy_specification": [],
"created_at": None,
"display_name": "test goal",
"deleted_at": None
},
"watcher_object.name": "GoalPayload"
},
"strategy": None,
"deleted_at": None,
"scope": [],
"state": "ONGOING",
"updated_at": None,
"created_at": None,
"state_update": {
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.data": {
"old_state": "PENDING",
"state": "ONGOING"
},
"watcher_object.name": "AuditStateUpdatePayload"
},
"audit_type": "ONESHOT"
},
"watcher_object.name": "AuditUpdatePayload"
},
payload
)

View File

@@ -19,9 +19,9 @@ from oslo_versionedobjects import fixture
from watcher.common import exception
from watcher.common import rpc
from watcher.notifications import base as notificationbase
from watcher.objects import base
from watcher.objects import fields as wfields
from watcher.objects.notifications import base as notificationbase
from watcher.tests import base as testbase
from watcher.tests.objects import test_objects
@@ -251,7 +251,15 @@ class TestNotificationBase(testbase.TestCase):
expected_notification_fingerprints = {
'EventType': '1.0-92100a9f0908da98dfcfff9c42e0018c',
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
'AuditUpdatePayload': '1.0-d3aace28d9eb978c1ecf833e108f61f7',
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1',
}

View File

@@ -16,8 +16,10 @@
import mock
from watcher.common import exception
from watcher.common import rpc
from watcher.common import utils as w_utils
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
@@ -46,6 +48,12 @@ class TestAuditObject(base.DbTestCase):
def setUp(self):
super(TestAuditObject, 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.m_send_update = self.m_audit_notifications.send_update
self.fake_goal = utils.create_test_goal(**self.goal_data)
def eager_load_audit_assert(self, audit, goal):
@@ -71,6 +79,7 @@ class TestAuditObject(base.DbTestCase):
self.context, audit_id, eager=self.eager)
self.assertEqual(self.context, audit._context)
self.eager_load_audit_assert(audit, self.fake_goal)
self.assertEqual(0, self.m_send_update.call_count)
@mock.patch.object(db_api.Connection, 'get_audit_by_uuid')
def test_get_by_uuid(self, mock_get_audit):
@@ -81,6 +90,7 @@ class TestAuditObject(base.DbTestCase):
self.context, uuid, eager=self.eager)
self.assertEqual(self.context, audit._context)
self.eager_load_audit_assert(audit, self.fake_goal)
self.assertEqual(0, self.m_send_update.call_count)
def test_get_bad_id_and_uuid(self):
self.assertRaises(exception.InvalidIdentity,
@@ -99,6 +109,7 @@ class TestAuditObject(base.DbTestCase):
self.assertEqual(self.context, audits[0]._context)
for audit in audits:
self.eager_load_audit_assert(audit, self.fake_goal)
self.assertEqual(0, self.m_send_update.call_count)
@mock.patch.object(db_api.Connection, 'update_audit')
@mock.patch.object(db_api.Connection, 'get_audit_by_uuid')
@@ -106,15 +117,17 @@ class TestAuditObject(base.DbTestCase):
mock_get_audit.return_value = self.fake_audit
uuid = self.fake_audit['uuid']
audit = objects.Audit.get_by_uuid(self.context, uuid, eager=self.eager)
audit.state = 'SUCCEEDED'
audit.state = objects.audit.State.SUCCEEDED
audit.save()
mock_get_audit.assert_called_once_with(
self.context, uuid, eager=self.eager)
mock_update_audit.assert_called_once_with(
uuid, {'state': 'SUCCEEDED'})
uuid, {'state': objects.audit.State.SUCCEEDED})
self.assertEqual(self.context, audit._context)
self.eager_load_audit_assert(audit, self.fake_goal)
self.m_send_update.assert_called_once_with(
self.context, audit, old_state=self.fake_audit['state'])
@mock.patch.object(db_api.Connection, 'get_audit_by_uuid')
def test_refresh(self, mock_get_audit):
@@ -138,12 +151,18 @@ class TestCreateDeleteAuditObject(base.DbTestCase):
def setUp(self):
super(TestCreateDeleteAuditObject, 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.m_send_update = self.m_audit_notifications.send_update
self.goal_id = 1
self.goal = utils.create_test_goal(id=self.goal_id, name="DUMMY")
self.fake_audit = utils.get_test_audit(goal_id=self.goal_id)
@mock.patch.object(db_api.Connection, 'create_audit')
def test_create(self, mock_create_audit):
utils.create_test_goal(id=self.goal_id)
mock_create_audit.return_value = self.fake_audit
audit = objects.Audit(self.context, **self.fake_audit)
audit.create()
@@ -157,9 +176,9 @@ class TestCreateDeleteAuditObject(base.DbTestCase):
mock_soft_delete_audit, mock_update_audit):
mock_get_audit.return_value = self.fake_audit
uuid = self.fake_audit['uuid']
audit = objects.Audit.get_by_uuid(self.context, uuid)
audit = objects.Audit.get_by_uuid(self.context, uuid, eager=True)
audit.soft_delete()
mock_get_audit.assert_called_once_with(self.context, uuid, eager=False)
mock_get_audit.assert_called_once_with(self.context, uuid, eager=True)
mock_soft_delete_audit.assert_called_once_with(uuid)
mock_update_audit.assert_called_once_with(uuid, {'state': 'DELETED'})
self.assertEqual(self.context, audit._context)
@@ -176,3 +195,35 @@ class TestCreateDeleteAuditObject(base.DbTestCase):
self.context, uuid, eager=False)
mock_destroy_audit.assert_called_once_with(uuid)
self.assertEqual(self.context, audit._context)
class TestAuditObjectSendNotifications(base.DbTestCase):
def setUp(self):
super(TestAuditObjectSendNotifications, self).setUp()
goal_id = 1
self.fake_goal = utils.create_test_goal(id=goal_id, name="DUMMY")
self.fake_strategy = utils.create_test_strategy(
id=goal_id, name="DUMMY")
self.fake_audit = utils.get_test_audit(
goal_id=goal_id, goal=utils.get_test_goal(id=goal_id),
strategy_id=self.fake_strategy.id, strategy=self.fake_strategy)
p_get_notifier = mock.patch.object(rpc, 'get_notifier')
self.m_get_notifier = p_get_notifier.start()
self.m_get_notifier.return_value = mock.Mock(name='m_notifier')
self.m_notifier = self.m_get_notifier.return_value
self.addCleanup(p_get_notifier.stop)
@mock.patch.object(db_api.Connection, 'update_audit', mock.Mock())
@mock.patch.object(db_api.Connection, 'get_audit_by_uuid')
def test_send_update_notification(self, mock_get_audit):
mock_get_audit.return_value = self.fake_audit
uuid = self.fake_audit['uuid']
audit = objects.Audit.get_by_uuid(self.context, uuid, eager=True)
audit.state = objects.audit.State.ONGOING
audit.save()
self.assertEqual(1, self.m_notifier.info.call_count)
self.assertEqual('audit.update',
self.m_notifier.info.call_args[1]['event_type'])