Merge "Make webhook API compatible with Aodh"

This commit is contained in:
Zuul 2018-05-01 09:44:28 +00:00 committed by Gerrit Code Review
commit a7fea71b9e
10 changed files with 173 additions and 8 deletions

View File

@ -276,7 +276,8 @@ webhook_params:
type: object type: object
in: query in: query
description: | description: |
The query string that forms the inputs to use for the targeted action. The query string that forms the inputs to use for the targeted action
for API microversion less than 1.10.
webhook_version: webhook_version:
type: string type: string

View File

@ -2,7 +2,11 @@
Webhooks (webhooks) Webhooks (webhooks)
=================== ===================
Triggers an action represented by a webhook. Triggers an action represented by a webhook. For API microversion less than
1.10, optional params in the query are sent as inputs to be used by the
targeted action. For API microversion equal or greater than 1.10, any
key-value pairs in the request body are sent as inputs to be used by the
targeted action.
Trigger webhook action Trigger webhook action
====================== ======================

View File

@ -105,3 +105,9 @@ it can be used by both users and developers.
trigger the immediate deletion of the nodes identified for deferred trigger the immediate deletion of the nodes identified for deferred
deletion during scale-in operation. deletion during scale-in operation.
1.10
---
- Modified the ``webhook_trigger`` API. Inputs for the targeted action
are now sent directly in the query body rather than in the params
field.

View File

@ -24,7 +24,7 @@ class VersionController(object):
# This includes any semantic changes which may not affect the input or # This includes any semantic changes which may not affect the input or
# output formats or even originate in the API code layer. # output formats or even originate in the API code layer.
_MIN_API_VERSION = "1.0" _MIN_API_VERSION = "1.0"
_MAX_API_VERSION = "1.9" _MAX_API_VERSION = "1.10"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -24,6 +24,7 @@ class WebhookController(wsgi.Controller):
REQUEST_SCOPE = 'webhooks' REQUEST_SCOPE = 'webhooks'
@wsgi.Controller.api_version("1.0", "1.9")
@util.policy_enforce @util.policy_enforce
def trigger(self, req, webhook_id, body=None): def trigger(self, req, webhook_id, body=None):
if body is None: if body is None:
@ -39,3 +40,15 @@ class WebhookController(wsgi.Controller):
location = {'location': '/actions/%s' % res['action']} location = {'location': '/actions/%s' % res['action']}
res.update(location) res.update(location)
return res return res
@wsgi.Controller.api_version("1.10") # noqa
@util.policy_enforce
def trigger(self, req, webhook_id, body=None):
obj = util.parse_request(
'WebhookTriggerRequestParamsInBody', req, {'identity': webhook_id,
'body': body})
res = self.rpc_client.call(req.context, 'webhook_trigger', obj)
location = {'location': '/actions/%s' % res['action']}
res.update(location)
return res

View File

@ -2493,7 +2493,11 @@ class EngineService(service.Service):
:return: A dictionary contains the ID of the action fired. :return: A dictionary contains the ID of the action fired.
""" """
identity = req.identity identity = req.identity
if hasattr(req.body, 'params'):
# API version < 1.10
params = req.body.params params = req.body.params
else:
params = req.body
LOG.info("Triggering webhook (%s)", identity) LOG.info("Triggering webhook (%s)", identity)
receiver = receiver_obj.Receiver.find(ctx, identity) receiver = receiver_obj.Receiver.find(ctx, identity)

View File

@ -14,6 +14,15 @@ from senlin.objects import base
from senlin.objects import fields from senlin.objects import fields
@base.SenlinObjectRegistry.register
class WebhookTriggerRequestParamsInBody(base.SenlinObject):
fields = {
'identity': fields.StringField(),
'body': fields.JsonField(nullable=True, default={})
}
@base.SenlinObjectRegistry.register @base.SenlinObjectRegistry.register
class WebhookTriggerRequest(base.SenlinObject): class WebhookTriggerRequest(base.SenlinObject):

View File

@ -48,7 +48,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
} }
req = self._post('/webhooks/test_webhook_id/trigger', req = self._post('/webhooks/test_webhook_id/trigger',
jsonutils.dumps(body)) jsonutils.dumps(body), version='1.10')
mock_call.return_value = engine_response mock_call.return_value = engine_response
obj = mock.Mock() obj = mock.Mock()
mock_parse.return_value = obj mock_parse.return_value = obj
@ -58,7 +58,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
self.assertEqual(action_id, resp['action']) self.assertEqual(action_id, resp['action'])
self.assertEqual('/actions/test_action_id', resp['location']) self.assertEqual('/actions/test_action_id', resp['location'])
mock_parse.assert_called_once_with( mock_parse.assert_called_once_with(
'WebhookTriggerRequest', req, mock.ANY) 'WebhookTriggerRequestParamsInBody', req, mock.ANY)
mock_call.assert_called_once_with(req.context, 'webhook_trigger', obj) mock_call.assert_called_once_with(req.context, 'webhook_trigger', obj)
@mock.patch.object(util, 'parse_request') @mock.patch.object(util, 'parse_request')
@ -72,7 +72,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
engine_response = {'action': 'FAKE_ACTION'} engine_response = {'action': 'FAKE_ACTION'}
req = self._post('/webhooks/test_webhook_id/trigger', req = self._post('/webhooks/test_webhook_id/trigger',
jsonutils.dumps(body)) jsonutils.dumps(body), version='1.10')
mock_call.return_value = engine_response mock_call.return_value = engine_response
obj = mock.Mock() obj = mock.Mock()
mock_parse.return_value = obj mock_parse.return_value = obj
@ -82,7 +82,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
self.assertEqual('FAKE_ACTION', resp['action']) self.assertEqual('FAKE_ACTION', resp['action'])
self.assertEqual('/actions/FAKE_ACTION', resp['location']) self.assertEqual('/actions/FAKE_ACTION', resp['location'])
mock_parse.assert_called_once_with( mock_parse.assert_called_once_with(
'WebhookTriggerRequest', req, mock.ANY) 'WebhookTriggerRequestParamsInBody', req, mock.ANY)
mock_call.assert_called_once_with(req.context, 'webhook_trigger', obj) mock_call.assert_called_once_with(req.context, 'webhook_trigger', obj)
@mock.patch.object(util, 'parse_request') @mock.patch.object(util, 'parse_request')

View File

@ -33,6 +33,105 @@ class WebhookTest(base.SenlinTestCase):
self.ctx = utils.dummy_context(project='webhook_test_project') self.ctx = utils.dummy_context(project='webhook_test_project')
self.eng = service.EngineService('host-a', 'topic-a') self.eng = service.EngineService('host-a', 'topic-a')
@mock.patch.object(dispatcher, 'start_action')
@mock.patch.object(action_mod.Action, 'create')
@mock.patch.object(co.Cluster, 'find')
@mock.patch.object(ro.Receiver, 'find')
def test_webhook_trigger_params_in_body_with_params(
self, mock_get, mock_find, mock_action, notify):
mock_find.return_value = mock.Mock(id='FAKE_CLUSTER')
mock_get.return_value = mock.Mock(id='01234567-abcd-efef',
cluster_id='FAKE_CLUSTER',
action='DANCE',
params={'foo': 'bar'})
mock_action.return_value = 'ACTION_ID'
body = {'kee': 'vee'}
req = vorw.WebhookTriggerRequestParamsInBody(identity='FAKE_RECEIVER',
body=body)
res = self.eng.webhook_trigger(self.ctx, req.obj_to_primitive())
self.assertEqual({'action': 'ACTION_ID'}, res)
mock_get.assert_called_once_with(self.ctx, 'FAKE_RECEIVER')
mock_find.assert_called_once_with(self.ctx, 'FAKE_CLUSTER')
mock_action.assert_called_once_with(
self.ctx, 'FAKE_CLUSTER', 'DANCE',
name='webhook_01234567',
cause=consts.CAUSE_RPC,
status=action_mod.Action.READY,
inputs={'kee': 'vee', 'foo': 'bar'},
)
notify.assert_called_once_with()
@mock.patch.object(dispatcher, 'start_action')
@mock.patch.object(action_mod.Action, 'create')
@mock.patch.object(co.Cluster, 'find')
@mock.patch.object(ro.Receiver, 'find')
def test_webhook_trigger_params_in_body_no_params(
self, mock_get, mock_find, mock_action, notify):
mock_find.return_value = mock.Mock(id='FAKE_CLUSTER')
mock_get.return_value = mock.Mock(id='01234567-abcd-efef',
cluster_id='FAKE_CLUSTER',
action='DANCE',
params={'foo': 'bar'})
mock_action.return_value = 'ACTION_ID'
body = {}
req = vorw.WebhookTriggerRequestParamsInBody(identity='FAKE_RECEIVER',
body=body)
res = self.eng.webhook_trigger(self.ctx, req.obj_to_primitive())
self.assertEqual({'action': 'ACTION_ID'}, res)
mock_get.assert_called_once_with(self.ctx, 'FAKE_RECEIVER')
mock_find.assert_called_once_with(self.ctx, 'FAKE_CLUSTER')
mock_action.assert_called_once_with(
self.ctx, 'FAKE_CLUSTER', 'DANCE',
name='webhook_01234567',
cause=consts.CAUSE_RPC,
status=action_mod.Action.READY,
inputs={'foo': 'bar'},
)
notify.assert_called_once_with()
@mock.patch.object(ro.Receiver, 'find')
def test_webhook_trigger_params_in_body_receiver_not_found(
self, mock_find):
mock_find.side_effect = exception.ResourceNotFound(type='receiver',
id='RRR')
body = None
req = vorw.WebhookTriggerRequestParamsInBody(identity='RRR', body=body)
ex = self.assertRaises(rpc.ExpectedException,
self.eng.webhook_trigger, self.ctx,
req.obj_to_primitive())
self.assertEqual(exception.ResourceNotFound, ex.exc_info[0])
self.assertEqual("The receiver 'RRR' could not be found.",
six.text_type(ex.exc_info[1]))
mock_find.assert_called_once_with(self.ctx, 'RRR')
@mock.patch.object(ro.Receiver, 'find')
@mock.patch.object(co.Cluster, 'find')
def test_webhook_trigger_params_in_body_cluster_not_found(
self, mock_cluster, mock_find):
receiver = mock.Mock()
receiver.cluster_id = 'BOGUS'
mock_find.return_value = receiver
mock_cluster.side_effect = exception.ResourceNotFound(type='cluster',
id='BOGUS')
body = None
req = vorw.WebhookTriggerRequestParamsInBody(identity='RRR', body=body)
ex = self.assertRaises(rpc.ExpectedException,
self.eng.webhook_trigger, self.ctx,
req.obj_to_primitive())
self.assertEqual(exception.BadRequest, ex.exc_info[0])
self.assertEqual("The referenced cluster 'BOGUS' could not be found.",
six.text_type(ex.exc_info[1]))
mock_find.assert_called_once_with(self.ctx, 'RRR')
mock_cluster.assert_called_once_with(self.ctx, 'BOGUS')
@mock.patch.object(dispatcher, 'start_action') @mock.patch.object(dispatcher, 'start_action')
@mock.patch.object(action_mod.Action, 'create') @mock.patch.object(action_mod.Action, 'create')
@mock.patch.object(co.Cluster, 'find') @mock.patch.object(co.Cluster, 'find')

View File

@ -60,3 +60,32 @@ class TestWebhookTrigger(test_base.SenlinTestCase):
self.assertEqual('1.0', res['senlin_object.version']) self.assertEqual('1.0', res['senlin_object.version'])
self.assertEqual('senlin', res['senlin_object.namespace']) self.assertEqual('senlin', res['senlin_object.namespace'])
self.assertEqual(u'fake', res['senlin_object.data']['identity']) self.assertEqual(u'fake', res['senlin_object.data']['identity'])
def test_webhook_trigger_params_in_body_none_param(self):
body = None
sot = webhooks.WebhookTriggerRequestParamsInBody(
identity='fake', params=body)
self.assertEqual('fake', sot.identity)
self.assertIsNone(sot.params)
def test_webhook_trigger_params_in_body(self):
body = {'foo': 'boo'}
sot = webhooks.WebhookTriggerRequestParamsInBody(
identity='fake', params=body)
self.assertEqual('fake', sot.identity)
self.assertIsInstance(sot.params, dict)
def test_webhook_trigger_params_in_body_to_primitive(self):
body = {'foo': 'boo'}
sot = webhooks.WebhookTriggerRequestParamsInBody(
identity='fake', params=body)
self.assertEqual('fake', sot.identity)
self.assertIsInstance(sot.params, dict)
res = sot.obj_to_primitive()
self.assertIn('identity', res['senlin_object.changes'])
self.assertIn('WebhookTriggerRequest', res['senlin_object.name'])
self.assertEqual('1.0', res['senlin_object.version'])
self.assertEqual('senlin', res['senlin_object.namespace'])
self.assertEqual(u'fake', res['senlin_object.data']['identity'])