From e780ffb06d4e2718045908982b32f3c809282cb4 Mon Sep 17 00:00:00 2001 From: Nikolay Mahotkin Date: Mon, 24 Jul 2017 14:51:49 +0300 Subject: [PATCH] [Event-triggers] Allow public triggers * Allowed creating via API * Added the corresponding processing in the event engine * Corresponding test Closes-Bug: #1704111 Change-Id: I73a1d10fe684f1ec962e42e9700064d06cb0bcbe --- etc/policy.json | 1 + mistral/api/controllers/v2/event_trigger.py | 4 ++ mistral/event_engine/default_event_engine.py | 18 ++++- mistral/services/triggers.py | 4 +- .../tests/unit/api/v2/test_event_trigger.py | 16 +++++ .../tests/unit/services/test_event_engine.py | 67 ++++++++++++++++++- 6 files changed, 105 insertions(+), 5 deletions(-) diff --git a/etc/policy.json b/etc/policy.json index becf77c8e..6a454536b 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -58,6 +58,7 @@ "workflows:update": "rule:admin_or_owner", "event_triggers:create": "rule:admin_or_owner", + "event_triggers:create:public": "rule:admin_only", "event_triggers:delete": "rule:admin_or_owner", "event_triggers:get": "rule:admin_or_owner", "event_triggers:list": "rule:admin_or_owner", diff --git a/mistral/api/controllers/v2/event_trigger.py b/mistral/api/controllers/v2/event_trigger.py index 05f88d2c3..71c4a118b 100644 --- a/mistral/api/controllers/v2/event_trigger.py +++ b/mistral/api/controllers/v2/event_trigger.py @@ -61,6 +61,9 @@ class EventTriggersController(rest.RestController): CREATE_MANDATORY ) + if values.get('scope') == 'public': + acl.enforce('event_triggers:create:public', auth_ctx.ctx()) + LOG.info('Create event trigger: %s', values) db_model = triggers.create_event_trigger( @@ -69,6 +72,7 @@ class EventTriggersController(rest.RestController): values.get('topic'), values.get('event'), values.get('workflow_id'), + values.get('scope'), workflow_input=values.get('workflow_input'), workflow_params=values.get('workflow_params'), ) diff --git a/mistral/event_engine/default_event_engine.py b/mistral/event_engine/default_event_engine.py index f0b3f9e89..ac9754b59 100644 --- a/mistral/event_engine/default_event_engine.py +++ b/mistral/event_engine/default_event_engine.py @@ -271,12 +271,26 @@ class DefaultEventEngine(base.EventEngine): # There may be more projects registered the same event. project_ids = [t['project_id'] for t in triggers] + any_public = any( + [t['scope'] == 'public' for t in triggers] + ) + # Skip the event doesn't belong to any event trigger owner. - if (CONF.pecan.auth_enable and + if (not any_public and CONF.pecan.auth_enable and context.get('project_id', '') not in project_ids): self.event_queue.task_done() continue + # Need to choose what trigger(s) should be called exactly. + triggers_to_call = [] + for t in triggers: + project_trigger = ( + t['project_id'] == context.get('project_id') + ) + public_trigger = t['scope'] == 'public' + if project_trigger or public_trigger: + triggers_to_call.append(t) + LOG.debug('Start to handle event: %s, %d trigger(s) ' 'registered.', event_type, len(triggers)) @@ -285,7 +299,7 @@ class DefaultEventEngine(base.EventEngine): event ) - self._start_workflow(triggers, event_params) + self._start_workflow(triggers_to_call, event_params) self.event_queue.task_done() diff --git a/mistral/services/triggers.py b/mistral/services/triggers.py index b6497b4a7..0aac20f03 100644 --- a/mistral/services/triggers.py +++ b/mistral/services/triggers.py @@ -147,7 +147,8 @@ def delete_cron_trigger(name, trust_id=None): def create_event_trigger(name, exchange, topic, event, workflow_id, - workflow_input=None, workflow_params=None): + scope='private', workflow_input=None, + workflow_params=None): with db_api.transaction(): wf_def = db_api.get_workflow_definition_by_id(workflow_id) @@ -172,6 +173,7 @@ def create_event_trigger(name, exchange, topic, event, workflow_id, 'exchange': exchange, 'topic': topic, 'event': event, + 'scope': scope, } security.add_trust_id(values) diff --git a/mistral/tests/unit/api/v2/test_event_trigger.py b/mistral/tests/unit/api/v2/test_event_trigger.py index 2ff10282e..d705c78e9 100644 --- a/mistral/tests/unit/api/v2/test_event_trigger.py +++ b/mistral/tests/unit/api/v2/test_event_trigger.py @@ -19,6 +19,7 @@ import mock from mistral.db.v2 import api as db_api from mistral.db.v2.sqlalchemy import models from mistral import exceptions as exc +from mistral.services import triggers from mistral.tests.unit.api import base from mistral.tests.unit import base as unit_base @@ -108,6 +109,21 @@ class TestEventTriggerController(base.APITest): client.create_event_trigger.call_args[0][1] ) + @mock.patch.object(db_api, "get_workflow_definition_by_id", MOCK_WF) + @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF) + @mock.patch.object(triggers, "create_event_trigger") + def test_post_public(self, create_trigger): + trigger = copy.deepcopy(TRIGGER) + trigger['scope'] = 'public' + trigger.pop('id') + + resp = self.app.post_json('/v2/event_triggers', trigger) + + self.assertEqual(201, resp.status_int) + + self.assertTrue(create_trigger.called) + self.assertEqual('public', create_trigger.call_args[0][5]) + def test_post_no_workflow_id(self): CREATE_TRIGGER = copy.deepcopy(TRIGGER) CREATE_TRIGGER.pop('id') diff --git a/mistral/tests/unit/services/test_event_engine.py b/mistral/tests/unit/services/test_event_engine.py index 652a76119..cc2773d40 100644 --- a/mistral/tests/unit/services/test_event_engine.py +++ b/mistral/tests/unit/services/test_event_engine.py @@ -13,11 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import time import mock from oslo_config import cfg +from mistral import context as auth_context from mistral.db.v2.sqlalchemy import api as db_api from mistral.event_engine import default_event_engine as evt_eng from mistral.rpc import clients as rpc @@ -92,9 +94,67 @@ class EventEngineTest(base.DbTestCase): ) self.assertEqual(1, len(e_engine.exchange_topic_listener_map)) + @mock.patch('mistral.messaging.start_listener') + @mock.patch.object(rpc, 'get_engine_client', mock.Mock()) + def test_event_engine_public_trigger(self, mock_start): + t = copy.deepcopy(EVENT_TRIGGER) + + # Create public trigger as an admin + self.ctx = base.get_context(default=False, admin=True) + auth_context.set_ctx(self.ctx) + + t['scope'] = 'public' + t['project_id'] = self.ctx.tenant + trigger = db_api.create_event_trigger(t) + + # Switch to the user. + self.ctx = base.get_context(default=True) + auth_context.set_ctx(self.ctx) + + e_engine = evt_eng.DefaultEventEngine() + + self.addCleanup(e_engine.handler_tg.stop) + + event = { + 'event_type': EVENT_TYPE, + 'payload': {}, + 'publisher': 'fake_publisher', + 'timestamp': '', + 'context': { + 'project_id': '%s' % self.ctx.project_id, + 'user_id': 'fake_user' + }, + } + + # Moreover, assert that trigger.project_id != event.project_id + self.assertNotEqual( + trigger.project_id, event['context']['project_id'] + ) + + with mock.patch.object(e_engine, 'engine_client') as client_mock: + e_engine.event_queue.put(event) + + time.sleep(1) + + self.assertEqual(1, client_mock.start_workflow.call_count) + + args, kwargs = client_mock.start_workflow.call_args + + self.assertEqual((EVENT_TRIGGER['workflow_id'], {}), args) + self.assertDictEqual( + { + 'service': 'fake_publisher', + 'project_id': '%s' % self.ctx.project_id, + 'user_id': 'fake_user', + 'timestamp': '' + }, + kwargs['event_params'] + ) + @mock.patch('mistral.messaging.start_listener') @mock.patch.object(rpc, 'get_engine_client', mock.Mock()) def test_process_event_queue(self, mock_start): + EVENT_TRIGGER['project_id'] = self.ctx.project_id db_api.create_event_trigger(EVENT_TRIGGER) e_engine = evt_eng.DefaultEventEngine() @@ -106,7 +166,10 @@ class EventEngineTest(base.DbTestCase): 'payload': {}, 'publisher': 'fake_publisher', 'timestamp': '', - 'context': {'project_id': 'fake_project', 'user_id': 'fake_user'}, + 'context': { + 'project_id': '%s' % self.ctx.project_id, + 'user_id': 'fake_user' + }, } with mock.patch.object(e_engine, 'engine_client') as client_mock: @@ -122,7 +185,7 @@ class EventEngineTest(base.DbTestCase): self.assertDictEqual( { 'service': 'fake_publisher', - 'project_id': 'fake_project', + 'project_id': '%s' % self.ctx.project_id, 'user_id': 'fake_user', 'timestamp': '' },