[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
This commit is contained in:
Nikolay Mahotkin 2017-07-24 14:51:49 +03:00
parent 7fad3e7b18
commit e780ffb06d
6 changed files with 105 additions and 5 deletions

View File

@ -58,6 +58,7 @@
"workflows:update": "rule:admin_or_owner", "workflows:update": "rule:admin_or_owner",
"event_triggers:create": "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:delete": "rule:admin_or_owner",
"event_triggers:get": "rule:admin_or_owner", "event_triggers:get": "rule:admin_or_owner",
"event_triggers:list": "rule:admin_or_owner", "event_triggers:list": "rule:admin_or_owner",

View File

@ -61,6 +61,9 @@ class EventTriggersController(rest.RestController):
CREATE_MANDATORY CREATE_MANDATORY
) )
if values.get('scope') == 'public':
acl.enforce('event_triggers:create:public', auth_ctx.ctx())
LOG.info('Create event trigger: %s', values) LOG.info('Create event trigger: %s', values)
db_model = triggers.create_event_trigger( db_model = triggers.create_event_trigger(
@ -69,6 +72,7 @@ class EventTriggersController(rest.RestController):
values.get('topic'), values.get('topic'),
values.get('event'), values.get('event'),
values.get('workflow_id'), values.get('workflow_id'),
values.get('scope'),
workflow_input=values.get('workflow_input'), workflow_input=values.get('workflow_input'),
workflow_params=values.get('workflow_params'), workflow_params=values.get('workflow_params'),
) )

View File

@ -271,12 +271,26 @@ class DefaultEventEngine(base.EventEngine):
# There may be more projects registered the same event. # There may be more projects registered the same event.
project_ids = [t['project_id'] for t in triggers] 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. # 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): context.get('project_id', '') not in project_ids):
self.event_queue.task_done() self.event_queue.task_done()
continue 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) ' LOG.debug('Start to handle event: %s, %d trigger(s) '
'registered.', event_type, len(triggers)) 'registered.', event_type, len(triggers))
@ -285,7 +299,7 @@ class DefaultEventEngine(base.EventEngine):
event event
) )
self._start_workflow(triggers, event_params) self._start_workflow(triggers_to_call, event_params)
self.event_queue.task_done() self.event_queue.task_done()

View File

@ -147,7 +147,8 @@ def delete_cron_trigger(name, trust_id=None):
def create_event_trigger(name, exchange, topic, event, workflow_id, 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(): with db_api.transaction():
wf_def = db_api.get_workflow_definition_by_id(workflow_id) 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, 'exchange': exchange,
'topic': topic, 'topic': topic,
'event': event, 'event': event,
'scope': scope,
} }
security.add_trust_id(values) security.add_trust_id(values)

View File

@ -19,6 +19,7 @@ import mock
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral.db.v2.sqlalchemy import models from mistral.db.v2.sqlalchemy import models
from mistral import exceptions as exc from mistral import exceptions as exc
from mistral.services import triggers
from mistral.tests.unit.api import base from mistral.tests.unit.api import base
from mistral.tests.unit import base as unit_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] 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): def test_post_no_workflow_id(self):
CREATE_TRIGGER = copy.deepcopy(TRIGGER) CREATE_TRIGGER = copy.deepcopy(TRIGGER)
CREATE_TRIGGER.pop('id') CREATE_TRIGGER.pop('id')

View File

@ -13,11 +13,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import time import time
import mock import mock
from oslo_config import cfg from oslo_config import cfg
from mistral import context as auth_context
from mistral.db.v2.sqlalchemy import api as db_api from mistral.db.v2.sqlalchemy import api as db_api
from mistral.event_engine import default_event_engine as evt_eng from mistral.event_engine import default_event_engine as evt_eng
from mistral.rpc import clients as rpc 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)) 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('mistral.messaging.start_listener')
@mock.patch.object(rpc, 'get_engine_client', mock.Mock()) @mock.patch.object(rpc, 'get_engine_client', mock.Mock())
def test_process_event_queue(self, mock_start): def test_process_event_queue(self, mock_start):
EVENT_TRIGGER['project_id'] = self.ctx.project_id
db_api.create_event_trigger(EVENT_TRIGGER) db_api.create_event_trigger(EVENT_TRIGGER)
e_engine = evt_eng.DefaultEventEngine() e_engine = evt_eng.DefaultEventEngine()
@ -106,7 +166,10 @@ class EventEngineTest(base.DbTestCase):
'payload': {}, 'payload': {},
'publisher': 'fake_publisher', 'publisher': 'fake_publisher',
'timestamp': '', '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: with mock.patch.object(e_engine, 'engine_client') as client_mock:
@ -122,7 +185,7 @@ class EventEngineTest(base.DbTestCase):
self.assertDictEqual( self.assertDictEqual(
{ {
'service': 'fake_publisher', 'service': 'fake_publisher',
'project_id': 'fake_project', 'project_id': '%s' % self.ctx.project_id,
'user_id': 'fake_user', 'user_id': 'fake_user',
'timestamp': '' 'timestamp': ''
}, },