summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-07-20 11:47:52 +0000
committerGerrit Code Review <review@openstack.org>2018-07-20 11:47:52 +0000
commite788284086691af126fe07557952e15296011465 (patch)
tree65dfc19cbc2ab37fc213182897db2b33631e97e1
parent369c513c091018b5a8e966b1b5dce9626b385eca (diff)
parent549ec1f3bfcaa0015cfcba8619cba3b09f5c54f9 (diff)
Merge "Return the result of the MistralHTTPAction"
-rw-r--r--mistral/actions/std_actions.py2
-rw-r--r--mistral/tests/releasenotes/notes/return-errors-for-std-mistral-http-b852b6d8f0034477.yaml6
-rw-r--r--mistral/tests/unit/actions/test_std_mistral_http_action.py124
-rw-r--r--mistral/tests/unit/engine/test_error_result.py57
4 files changed, 184 insertions, 5 deletions
diff --git a/mistral/actions/std_actions.py b/mistral/actions/std_actions.py
index da72576..e5c0be4 100644
--- a/mistral/actions/std_actions.py
+++ b/mistral/actions/std_actions.py
@@ -272,7 +272,7 @@ class MistralHTTPAction(HTTPAction):
272 'Mistral-Callback-URL': exec_ctx.callback_url, 272 'Mistral-Callback-URL': exec_ctx.callback_url,
273 }) 273 })
274 274
275 super(MistralHTTPAction, self).run(context) 275 return super(MistralHTTPAction, self).run(context)
276 276
277 def is_sync(self): 277 def is_sync(self):
278 return False 278 return False
diff --git a/mistral/tests/releasenotes/notes/return-errors-for-std-mistral-http-b852b6d8f0034477.yaml b/mistral/tests/releasenotes/notes/return-errors-for-std-mistral-http-b852b6d8f0034477.yaml
new file mode 100644
index 0000000..64ab544
--- /dev/null
+++ b/mistral/tests/releasenotes/notes/return-errors-for-std-mistral-http-b852b6d8f0034477.yaml
@@ -0,0 +1,6 @@
1---
2features:
3 - |
4 The action std.mistral_http will now retrun an error if the HTTP request
5 fails. Previously the task would still go into the RUNNING state and wait
6 to be completed by the external resource.
diff --git a/mistral/tests/unit/actions/test_std_mistral_http_action.py b/mistral/tests/unit/actions/test_std_mistral_http_action.py
new file mode 100644
index 0000000..7d0fbdd
--- /dev/null
+++ b/mistral/tests/unit/actions/test_std_mistral_http_action.py
@@ -0,0 +1,124 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13import json
14
15import mock
16import requests
17
18from mistral.actions import std_actions as std
19from mistral.tests.unit import base
20from mistral_lib import actions as mistral_lib_actions
21
22
23URL = 'http://some_url'
24
25DATA = {
26 'server': {
27 'id': '12345',
28 'metadata': {
29 'name': 'super_server'
30 }
31 }
32}
33
34
35def get_fake_response(content, code, **kwargs):
36 return base.FakeHTTPResponse(
37 content,
38 code,
39 **kwargs
40 )
41
42
43def get_success_fake_response():
44 return get_fake_response(
45 json.dumps(DATA),
46 200,
47 headers={'Content-Type': 'application/json'}
48 )
49
50
51def get_error_fake_response():
52 return get_fake_response(
53 json.dumps(DATA),
54 401
55 )
56
57
58class MistralHTTPActionTest(base.BaseTest):
59 @mock.patch.object(requests, 'request')
60 def test_http_action(self, mocked_method):
61 mocked_method.return_value = get_success_fake_response()
62 mock_ctx = mock.Mock()
63
64 action = std.MistralHTTPAction(
65 url=URL,
66 method='POST',
67 body=DATA,
68 timeout=20,
69 allow_redirects=True
70 )
71
72 DATA_STR = json.dumps(DATA)
73
74 self.assertEqual(DATA_STR, action.body)
75 self.assertEqual(URL, action.url)
76
77 result = action.run(mock_ctx)
78
79 self.assertIsInstance(result, dict)
80 self.assertEqual(DATA, result['content'])
81 self.assertIn('headers', result)
82 self.assertEqual(200, result['status'])
83
84 mock_ex = mock_ctx.execution
85
86 headers = {
87 'Mistral-Workflow-Name': mock_ex.workflow_name,
88 'Mistral-Task-Id': mock_ex.task_execution_id,
89 'Mistral-Callback-URL': mock_ex.callback_url,
90 'Mistral-Action-Execution-Id': mock_ex.action_execution_id,
91 'Mistral-Workflow-Execution-Id': mock_ex.workflow_execution_id
92 }
93
94 mocked_method.assert_called_with(
95 'POST',
96 URL,
97 data=DATA_STR,
98 headers=headers,
99 cookies=None,
100 params=None,
101 timeout=20,
102 auth=None,
103 allow_redirects=True,
104 proxies=None,
105 verify=None
106 )
107
108 @mock.patch.object(requests, 'request')
109 def test_http_action_error_result(self, mocked_method):
110 mocked_method.return_value = get_error_fake_response()
111 mock_ctx = mock.Mock()
112
113 action = std.MistralHTTPAction(
114 url=URL,
115 method='POST',
116 body=DATA,
117 timeout=20,
118 allow_redirects=True
119 )
120
121 result = action.run(mock_ctx)
122
123 self.assertIsInstance(result, mistral_lib_actions.Result)
124 self.assertEqual(401, result.error['status'])
diff --git a/mistral/tests/unit/engine/test_error_result.py b/mistral/tests/unit/engine/test_error_result.py
index 29b7047..980a704 100644
--- a/mistral/tests/unit/engine/test_error_result.py
+++ b/mistral/tests/unit/engine/test_error_result.py
@@ -38,7 +38,7 @@ wf:
38 38
39 tasks: 39 tasks:
40 task1: 40 task1:
41 action: my_action 41 action: {action_name}
42 input: 42 input:
43 success_result: <% $.success_result %> 43 success_result: <% $.success_result %>
44 error_result: <% $.error_result %> 44 error_result: <% $.error_result %>
@@ -71,14 +71,21 @@ class MyAction(actions_base.Action):
71 raise NotImplementedError 71 raise NotImplementedError
72 72
73 73
74class MyAsyncAction(MyAction):
75
76 def is_sync(self):
77 return False
78
79
74class ErrorResultTest(base.EngineTestCase): 80class ErrorResultTest(base.EngineTestCase):
75 def setUp(self): 81 def setUp(self):
76 super(ErrorResultTest, self).setUp() 82 super(ErrorResultTest, self).setUp()
77 83
78 test_base.register_action_class('my_action', MyAction) 84 test_base.register_action_class('my_action', MyAction)
85 test_base.register_action_class('my_async_action', MyAsyncAction)
79 86
80 def test_error_result1(self): 87 def test_error_result1(self):
81 wf_service.create_workflows(WF) 88 wf_service.create_workflows(WF.format(action_name="my_action"))
82 89
83 # Start workflow. 90 # Start workflow.
84 wf_ex = self.engine.start_workflow( 91 wf_ex = self.engine.start_workflow(
@@ -111,7 +118,7 @@ class ErrorResultTest(base.EngineTestCase):
111 self.assertEqual(2, data_flow.get_task_execution_result(task1)) 118 self.assertEqual(2, data_flow.get_task_execution_result(task1))
112 119
113 def test_error_result2(self): 120 def test_error_result2(self):
114 wf_service.create_workflows(WF) 121 wf_service.create_workflows(WF.format(action_name="my_action"))
115 122
116 # Start workflow. 123 # Start workflow.
117 wf_ex = self.engine.start_workflow( 124 wf_ex = self.engine.start_workflow(
@@ -144,7 +151,7 @@ class ErrorResultTest(base.EngineTestCase):
144 self.assertEqual(3, data_flow.get_task_execution_result(task1)) 151 self.assertEqual(3, data_flow.get_task_execution_result(task1))
145 152
146 def test_success_result(self): 153 def test_success_result(self):
147 wf_service.create_workflows(WF) 154 wf_service.create_workflows(WF.format(action_name="my_action"))
148 155
149 # Start workflow. 156 # Start workflow.
150 wf_ex = self.engine.start_workflow( 157 wf_ex = self.engine.start_workflow(
@@ -176,3 +183,45 @@ class ErrorResultTest(base.EngineTestCase):
176 'success', 183 'success',
177 data_flow.get_task_execution_result(task1) 184 data_flow.get_task_execution_result(task1)
178 ) 185 )
186
187 def test_async_error_result(self):
188 wf_service.create_workflows(WF.format(action_name="my_async_action"))
189
190 # Start workflow.
191 wf_ex = self.engine.start_workflow(
192 'wf',
193 wf_input={
194 'success_result': None,
195 'error_result': 2
196 }
197 )
198
199 # If the action errors, we expect the workflow to continue. The
200 # on-error means the workflow ends in success.
201 self.await_workflow_success(wf_ex.id)
202
203 def test_async_success_result(self):
204 wf_service.create_workflows(WF.format(action_name="my_async_action"))
205
206 # Start workflow.
207 wf_ex = self.engine.start_workflow(
208 'wf',
209 wf_input={
210 'success_result': 'success',
211 'error_result': None
212 }
213 )
214
215 # When the action is successful, the workflow will wait in the RUNNING
216 # state for it to complete.
217 self.await_workflow_running(wf_ex.id)
218
219 with db_api.transaction():
220 # Note: We need to reread execution to access related tasks.
221 wf_ex = db_api.get_workflow_execution(wf_ex.id)
222
223 tasks = wf_ex.task_executions
224 self.assertEqual(1, len(tasks))
225
226 task1 = self._assert_single_item(tasks, name='task1')
227 self.assertEqual(states.RUNNING, task1.state)