Passed
Pull Request — master (#3163)
by W
04:43
created

MistralRunnerCallbackTest   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 270
rs 10
c 1
b 1
f 0
wmc 16

12 Methods

Rating   Name   Duplication   Size   Complexity  
A test_callback_handler_with_result_as_json_str() 0 16 1
A setUpClass() 0 22 2
A test_callback_handler_with_result_as_text() 0 14 1
B test_callback_incomplete_state() 0 32 1
A get_runner_class() 0 3 1
B test_callback_retry_exhausted() 0 43 2
A test_callback_handler_with_result_as_list_str() 0 14 1
A test_callback_handler_with_result_as_dict() 0 13 1
B test_callback() 0 34 2
A test_callback_handler_status_map() 0 4 1
B test_callback_retry() 0 36 2
A test_callback_handler_with_result_as_list() 0 14 1
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import mock
17
from mock import call
18
import requests
19
import uuid
20
21
from mistralclient.api.v2 import action_executions
22
from oslo_config import cfg
23
24
# XXX: actionsensor import depends on config being setup.
25
import st2tests.config as tests_config
26
tests_config.parse_args()
27
28
from st2actions.handlers.mistral import MistralCallbackHandler
29
from st2actions.handlers.mistral import STATUS_MAP as mistral_status_map
30
from st2common.bootstrap import actionsregistrar
31
from st2common.bootstrap import runnersregistrar
32
from st2common.constants import action as action_constants
33
from st2common.models.db.execution import ActionExecutionDB
34
from st2common.models.db.liveaction import LiveActionDB
35
from st2common.persistence.execution import ActionExecution
36
from st2common.persistence.liveaction import LiveAction
37
from st2common.runners import base as runners
38
from st2common.services import action as action_service
39
from st2common.services import trace as trace_service
40
from st2common.transport.liveaction import LiveActionPublisher
41
from st2common.transport.publishers import CUDPublisher
42
from st2tests import DbTestCase
43
from st2tests import fixturesloader
44
from st2tests.mocks.liveaction import MockLiveActionPublisher
45
46
47
TEST_PACK = 'mistral_tests'
48
TEST_PACK_PATH = fixturesloader.get_fixtures_packs_base_path() + '/' + TEST_PACK
49
50
PACKS = [
51
    TEST_PACK_PATH,
52
    fixturesloader.get_fixtures_packs_base_path() + '/core'
53
]
54
55
NON_EMPTY_RESULT = 'non-empty'
56
57
MOCK_ACTION_EXEC_DB = ActionExecutionDB(
58
    action={'ref': 'mock.workflow'},
59
    runner={'ref': 'mock.runner'},
60
    liveaction={'id': uuid.uuid4().hex},
61
    status=action_constants.LIVEACTION_STATUS_RUNNING,
62
    context={'mistral': {'auth_token': uuid.uuid4().hex}}
63
)
64
65
66
@mock.patch.object(
67
    CUDPublisher,
68
    'publish_update',
69
    mock.MagicMock(return_value=None))
70
@mock.patch.object(
71
    CUDPublisher,
72
    'publish_create',
73
    mock.MagicMock(side_effect=MockLiveActionPublisher.publish_create))
74
@mock.patch.object(
75
    LiveActionPublisher,
76
    'publish_state',
77
    mock.MagicMock(side_effect=MockLiveActionPublisher.publish_state))
78
class MistralRunnerCallbackTest(DbTestCase):
79
80
    @classmethod
81
    def setUpClass(cls):
82
        super(MistralRunnerCallbackTest, cls).setUpClass()
83
84
        # Override the retry configuration here otherwise st2tests.config.parse_args
85
        # in DbTestCase.setUpClass will reset these overrides.
86
        cfg.CONF.set_override('retry_exp_msec', 100, group='mistral')
87
        cfg.CONF.set_override('retry_exp_max_msec', 200, group='mistral')
88
        cfg.CONF.set_override('retry_stop_max_msec', 200, group='mistral')
89
        cfg.CONF.set_override('api_url', 'http://0.0.0.0:9101', group='auth')
90
91
        # Register runners.
92
        runnersregistrar.register_runners()
93
94
        # Register test pack(s).
95
        actions_registrar = actionsregistrar.ActionsRegistrar(
96
            use_pack_cache=False,
97
            fail_on_failure=True
98
        )
99
100
        for pack in PACKS:
101
            actions_registrar.register_from_pack(pack)
102
103
    @classmethod
104
    def get_runner_class(cls, runner_name):
105
        return runners.get_runner(runner_name).__class__
106
107
    def test_callback_handler_status_map(self):
108
        # Ensure all StackStorm status are mapped otherwise leads to zombie workflow.
109
        self.assertListEqual(sorted(mistral_status_map.keys()),
110
                             sorted(action_constants.LIVEACTION_STATUSES))
111
112
    @mock.patch.object(
113
        trace_service, 'get_trace_db_by_live_action',
114
        mock.MagicMock(return_value=(None, None)))
115
    @mock.patch.object(
116
        ActionExecution, 'get_by_id',
117
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
118
    @mock.patch.object(
119
        action_executions.ActionExecutionManager, 'update',
120
        mock.MagicMock(return_value=None))
121
    def test_callback_handler_with_result_as_text(self):
122
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
123
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
124
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED,
125
                                        '<html></html>')
126
127
    @mock.patch.object(
128
        trace_service, 'get_trace_db_by_live_action',
129
        mock.MagicMock(return_value=(None, None)))
130
    @mock.patch.object(
131
        ActionExecution, 'get_by_id',
132
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
133
    @mock.patch.object(
134
        action_executions.ActionExecutionManager, 'update',
135
        mock.MagicMock(return_value=None))
136
    def test_callback_handler_with_result_as_dict(self):
137
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
138
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
139
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED, {'a': 1})
140
141
    @mock.patch.object(
142
        trace_service, 'get_trace_db_by_live_action',
143
        mock.MagicMock(return_value=(None, None)))
144
    @mock.patch.object(
145
        ActionExecution, 'get_by_id',
146
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
147
    @mock.patch.object(
148
        action_executions.ActionExecutionManager, 'update',
149
        mock.MagicMock(return_value=None))
150
    def test_callback_handler_with_result_as_json_str(self):
151
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
152
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
153
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED, '{"a": 1}')
154
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
155
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
156
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED, "{'a': 1}")
157
158
    @mock.patch.object(
159
        trace_service, 'get_trace_db_by_live_action',
160
        mock.MagicMock(return_value=(None, None)))
161
    @mock.patch.object(
162
        ActionExecution, 'get_by_id',
163
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
164
    @mock.patch.object(
165
        action_executions.ActionExecutionManager, 'update',
166
        mock.MagicMock(return_value=None))
167
    def test_callback_handler_with_result_as_list(self):
168
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
169
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
170
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED,
171
                                        ["a", "b", "c"])
172
173
    @mock.patch.object(
174
        trace_service, 'get_trace_db_by_live_action',
175
        mock.MagicMock(return_value=(None, None)))
176
    @mock.patch.object(
177
        ActionExecution, 'get_by_id',
178
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
179
    @mock.patch.object(
180
        action_executions.ActionExecutionManager, 'update',
181
        mock.MagicMock(return_value=None))
182
    def test_callback_handler_with_result_as_list_str(self):
183
        MistralCallbackHandler.callback('http://127.0.0.1:8989/v2/action_executions/12345',
184
                                        {'parent': {'execution_id': uuid.uuid4().hex}},
185
                                        action_constants.LIVEACTION_STATUS_SUCCEEDED,
186
                                        '["a", "b", "c"]')
187
188
    @mock.patch.object(
189
        trace_service, 'get_trace_db_by_live_action',
190
        mock.MagicMock(return_value=(None, None)))
191
    @mock.patch.object(
192
        ActionExecution, 'get_by_id',
193
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
194
    @mock.patch.object(
195
        action_executions.ActionExecutionManager, 'update',
196
        mock.MagicMock(return_value=None))
197
    def test_callback(self):
198
        local_runner_cls = self.get_runner_class('local_runner')
199
200
        liveaction = LiveActionDB(
201
            action='core.local',
202
            parameters={'cmd': 'uname -a'},
203
            context={
204
                'parent': {
205
                    'execution_id': uuid.uuid4().hex
206
                }
207
            },
208
            callback={
209
                'source': 'mistral',
210
                'url': 'http://127.0.0.1:8989/v2/action_executions/12345'
211
            }
212
        )
213
214
        for status in action_constants.LIVEACTION_COMPLETED_STATES:
215
            expected_mistral_status = mistral_status_map[status]
216
            local_runner_cls.run = mock.Mock(return_value=(status, NON_EMPTY_RESULT, None))
217
            liveaction, execution = action_service.request(liveaction)
218
            liveaction = LiveAction.get_by_id(str(liveaction.id))
219
            self.assertEqual(liveaction.status, status)
220
            action_executions.ActionExecutionManager.update.assert_called_with(
221
                '12345', state=expected_mistral_status, output=NON_EMPTY_RESULT)
222
223
    @mock.patch.object(
224
        trace_service, 'get_trace_db_by_live_action',
225
        mock.MagicMock(return_value=(None, None)))
226
    @mock.patch.object(
227
        ActionExecution, 'get_by_id',
228
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
229
    @mock.patch.object(
230
        action_executions.ActionExecutionManager, 'update',
231
        mock.MagicMock(return_value=None))
232
    def test_callback_incomplete_state(self):
233
        local_runner_cls = self.get_runner_class('local_runner')
234
        local_run_result = (action_constants.LIVEACTION_STATUS_RUNNING, NON_EMPTY_RESULT, None)
235
        local_runner_cls.run = mock.Mock(return_value=local_run_result)
236
237
        liveaction = LiveActionDB(
238
            action='core.local',
239
            parameters={'cmd': 'uname -a'},
240
            context={
241
                'parent': {
242
                    'execution_id': uuid.uuid4().hex
243
                }
244
            },
245
            callback={
246
                'source': 'mistral',
247
                'url': 'http://127.0.0.1:8989/v2/action_executions/12345'
248
            }
249
        )
250
251
        liveaction, execution = action_service.request(liveaction)
252
        liveaction = LiveAction.get_by_id(str(liveaction.id))
253
        self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING)
254
        self.assertFalse(action_executions.ActionExecutionManager.update.called)
255
256
    @mock.patch.object(
257
        trace_service, 'get_trace_db_by_live_action',
258
        mock.MagicMock(return_value=(None, None)))
259
    @mock.patch.object(
260
        ActionExecution, 'get_by_id',
261
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
262
    @mock.patch.object(
263
        action_executions.ActionExecutionManager, 'update',
264
        mock.MagicMock(side_effect=[
265
            requests.exceptions.ConnectionError(),
266
            None]))
267
    def test_callback_retry(self):
268
        local_runner_cls = self.get_runner_class('local_runner')
269
        local_run_result = (action_constants.LIVEACTION_STATUS_SUCCEEDED, NON_EMPTY_RESULT, None)
270
        local_runner_cls.run = mock.Mock(return_value=local_run_result)
271
272
        liveaction = LiveActionDB(
273
            action='core.local',
274
            parameters={'cmd': 'uname -a'},
275
            context={
276
                'parent': {
277
                    'execution_id': uuid.uuid4().hex
278
                }
279
            },
280
            callback={
281
                'source': 'mistral',
282
                'url': 'http://127.0.0.1:8989/v2/action_executions/12345'
283
            }
284
        )
285
286
        liveaction, execution = action_service.request(liveaction)
287
        liveaction = LiveAction.get_by_id(str(liveaction.id))
288
        self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
289
290
        calls = [call('12345', state='SUCCESS', output=NON_EMPTY_RESULT) for i in range(0, 2)]
291
        action_executions.ActionExecutionManager.update.assert_has_calls(calls)
292
293
    @mock.patch.object(
294
        trace_service, 'get_trace_db_by_live_action',
295
        mock.MagicMock(return_value=(None, None)))
296
    @mock.patch.object(
297
        ActionExecution, 'get_by_id',
298
        mock.MagicMock(return_value=MOCK_ACTION_EXEC_DB))
299
    @mock.patch.object(
300
        action_executions.ActionExecutionManager, 'update',
301
        mock.MagicMock(side_effect=[
302
            requests.exceptions.ConnectionError(),
303
            requests.exceptions.ConnectionError(),
304
            requests.exceptions.ConnectionError(),
305
            requests.exceptions.ConnectionError(),
306
            None]))
307
    def test_callback_retry_exhausted(self):
308
        local_runner_cls = self.get_runner_class('local_runner')
309
        local_run_result = (action_constants.LIVEACTION_STATUS_SUCCEEDED, NON_EMPTY_RESULT, None)
310
        local_runner_cls.run = mock.Mock(return_value=local_run_result)
311
312
        liveaction = LiveActionDB(
313
            action='core.local',
314
            parameters={'cmd': 'uname -a'},
315
            context={
316
                'parent': {
317
                    'execution_id': uuid.uuid4().hex
318
                }
319
            },
320
            callback={
321
                'source': 'mistral',
322
                'url': 'http://127.0.0.1:8989/v2/action_executions/12345'
323
            }
324
        )
325
326
        liveaction, execution = action_service.request(liveaction)
327
        liveaction = LiveAction.get_by_id(str(liveaction.id))
328
329
        self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
330
331
        # This test initially setup mock for action_executions.ActionExecutionManager.update
332
        # to fail the first 4 times and return success on the 5th times. The max attempts
333
        # is set to 3. We expect only 3 calls to pass thru the update method.
334
        calls = [call('12345', state='SUCCESS', output=NON_EMPTY_RESULT) for i in range(0, 2)]
335
        action_executions.ActionExecutionManager.update.assert_has_calls(calls)
336