Merge "Make webhook API compatible with Aodh"
This commit is contained in:
commit
a7fea71b9e
|
@ -276,7 +276,8 @@ webhook_params:
|
|||
type: object
|
||||
in: query
|
||||
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:
|
||||
type: string
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
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
|
||||
======================
|
||||
|
|
|
@ -105,3 +105,9 @@ it can be used by both users and developers.
|
|||
trigger the immediate deletion of the nodes identified for deferred
|
||||
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.
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class VersionController(object):
|
|||
# This includes any semantic changes which may not affect the input or
|
||||
# output formats or even originate in the API code layer.
|
||||
_MIN_API_VERSION = "1.0"
|
||||
_MAX_API_VERSION = "1.9"
|
||||
_MAX_API_VERSION = "1.10"
|
||||
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class WebhookController(wsgi.Controller):
|
|||
|
||||
REQUEST_SCOPE = 'webhooks'
|
||||
|
||||
@wsgi.Controller.api_version("1.0", "1.9")
|
||||
@util.policy_enforce
|
||||
def trigger(self, req, webhook_id, body=None):
|
||||
if body is None:
|
||||
|
@ -39,3 +40,15 @@ class WebhookController(wsgi.Controller):
|
|||
location = {'location': '/actions/%s' % res['action']}
|
||||
res.update(location)
|
||||
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
|
||||
|
|
|
@ -2493,7 +2493,11 @@ class EngineService(service.Service):
|
|||
:return: A dictionary contains the ID of the action fired.
|
||||
"""
|
||||
identity = req.identity
|
||||
params = req.body.params
|
||||
if hasattr(req.body, 'params'):
|
||||
# API version < 1.10
|
||||
params = req.body.params
|
||||
else:
|
||||
params = req.body
|
||||
|
||||
LOG.info("Triggering webhook (%s)", identity)
|
||||
receiver = receiver_obj.Receiver.find(ctx, identity)
|
||||
|
|
|
@ -14,6 +14,15 @@ from senlin.objects import base
|
|||
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
|
||||
class WebhookTriggerRequest(base.SenlinObject):
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
|
|||
}
|
||||
|
||||
req = self._post('/webhooks/test_webhook_id/trigger',
|
||||
jsonutils.dumps(body))
|
||||
jsonutils.dumps(body), version='1.10')
|
||||
mock_call.return_value = engine_response
|
||||
obj = mock.Mock()
|
||||
mock_parse.return_value = obj
|
||||
|
@ -58,7 +58,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
|
|||
self.assertEqual(action_id, resp['action'])
|
||||
self.assertEqual('/actions/test_action_id', resp['location'])
|
||||
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.patch.object(util, 'parse_request')
|
||||
|
@ -72,7 +72,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
|
|||
engine_response = {'action': 'FAKE_ACTION'}
|
||||
|
||||
req = self._post('/webhooks/test_webhook_id/trigger',
|
||||
jsonutils.dumps(body))
|
||||
jsonutils.dumps(body), version='1.10')
|
||||
mock_call.return_value = engine_response
|
||||
obj = mock.Mock()
|
||||
mock_parse.return_value = obj
|
||||
|
@ -82,7 +82,7 @@ class WebhookControllerTest(shared.ControllerTest, base.SenlinTestCase):
|
|||
self.assertEqual('FAKE_ACTION', resp['action'])
|
||||
self.assertEqual('/actions/FAKE_ACTION', resp['location'])
|
||||
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.patch.object(util, 'parse_request')
|
||||
|
|
|
@ -33,6 +33,105 @@ class WebhookTest(base.SenlinTestCase):
|
|||
self.ctx = utils.dummy_context(project='webhook_test_project')
|
||||
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(action_mod.Action, 'create')
|
||||
@mock.patch.object(co.Cluster, 'find')
|
||||
|
|
|
@ -60,3 +60,32 @@ class TestWebhookTrigger(test_base.SenlinTestCase):
|
|||
self.assertEqual('1.0', res['senlin_object.version'])
|
||||
self.assertEqual('senlin', res['senlin_object.namespace'])
|
||||
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'])
|
||||
|
|
Loading…
Reference in New Issue