Browse Source

Merge "Return the result of the MistralHTTPAction"

tags/7.0.0.0b3
Zuul 1 year ago
parent
commit
e788284086

+ 1
- 1
mistral/actions/std_actions.py View File

@@ -272,7 +272,7 @@ class MistralHTTPAction(HTTPAction):
272 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 277
     def is_sync(self):
278 278
         return False

+ 6
- 0
mistral/tests/releasenotes/notes/return-errors-for-std-mistral-http-b852b6d8f0034477.yaml View File

@@ -0,0 +1,6 @@
1
+---
2
+features:
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.

+ 124
- 0
mistral/tests/unit/actions/test_std_mistral_http_action.py View File

@@ -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
+
13
+import json
14
+
15
+import mock
16
+import requests
17
+
18
+from mistral.actions import std_actions as std
19
+from mistral.tests.unit import base
20
+from mistral_lib import actions as mistral_lib_actions
21
+
22
+
23
+URL = 'http://some_url'
24
+
25
+DATA = {
26
+    'server': {
27
+        'id': '12345',
28
+        'metadata': {
29
+            'name': 'super_server'
30
+        }
31
+    }
32
+}
33
+
34
+
35
+def get_fake_response(content, code, **kwargs):
36
+    return base.FakeHTTPResponse(
37
+        content,
38
+        code,
39
+        **kwargs
40
+    )
41
+
42
+
43
+def get_success_fake_response():
44
+    return get_fake_response(
45
+        json.dumps(DATA),
46
+        200,
47
+        headers={'Content-Type': 'application/json'}
48
+    )
49
+
50
+
51
+def get_error_fake_response():
52
+    return get_fake_response(
53
+        json.dumps(DATA),
54
+        401
55
+    )
56
+
57
+
58
+class 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'])

+ 53
- 4
mistral/tests/unit/engine/test_error_result.py View File

@@ -38,7 +38,7 @@ wf:
38 38
 
39 39
   tasks:
40 40
     task1:
41
-      action: my_action
41
+      action: {action_name}
42 42
       input:
43 43
         success_result: <% $.success_result %>
44 44
         error_result: <% $.error_result %>
@@ -71,14 +71,21 @@ class MyAction(actions_base.Action):
71 71
         raise NotImplementedError
72 72
 
73 73
 
74
+class MyAsyncAction(MyAction):
75
+
76
+    def is_sync(self):
77
+        return False
78
+
79
+
74 80
 class ErrorResultTest(base.EngineTestCase):
75 81
     def setUp(self):
76 82
         super(ErrorResultTest, self).setUp()
77 83
 
78 84
         test_base.register_action_class('my_action', MyAction)
85
+        test_base.register_action_class('my_async_action', MyAsyncAction)
79 86
 
80 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 90
         # Start workflow.
84 91
         wf_ex = self.engine.start_workflow(
@@ -111,7 +118,7 @@ class ErrorResultTest(base.EngineTestCase):
111 118
             self.assertEqual(2, data_flow.get_task_execution_result(task1))
112 119
 
113 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 123
         # Start workflow.
117 124
         wf_ex = self.engine.start_workflow(
@@ -144,7 +151,7 @@ class ErrorResultTest(base.EngineTestCase):
144 151
             self.assertEqual(3, data_flow.get_task_execution_result(task1))
145 152
 
146 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 156
         # Start workflow.
150 157
         wf_ex = self.engine.start_workflow(
@@ -176,3 +183,45 @@ class ErrorResultTest(base.EngineTestCase):
176 183
                 'success',
177 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)

Loading…
Cancel
Save