Passed
Push — develop ( b8d4ca...421369 )
by Plexxi
07:01 queued 03:57
created

test_chain_runner_success_path_with_wait()   A

Complexity

Conditions 1

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 1
c 3
b 1
f 0
dl 0
loc 17
rs 9.4285
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
import action_chain_runner as acr
18
from st2actions.container.service import RunnerContainerService
19
from st2common.constants.action import LIVEACTION_STATUS_RUNNING
20
from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
21
from st2common.constants.action import LIVEACTION_STATUS_CANCELED
22
from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT
23
from st2common.constants.action import LIVEACTION_STATUS_FAILED
24
from st2common.exceptions import actionrunner as runnerexceptions
25
from st2common.models.api.notification import NotificationsHelper
26
from st2common.models.db.liveaction import LiveActionDB
27
from st2common.models.db.keyvalue import KeyValuePairDB
28
from st2common.models.system.common import ResourceReference
29
from st2common.persistence.keyvalue import KeyValuePair
30
from st2common.persistence.runner import RunnerType
31
from st2common.services import action as action_service
32
from st2common.util import action_db as action_db_util
33
from st2common.exceptions.action import ParameterRenderingFailedException
34
from st2tests import DbTestCase
35
from st2tests.fixturesloader import FixturesLoader
36
37
38
class DummyActionExecution(object):
39
    def __init__(self, status=LIVEACTION_STATUS_SUCCEEDED, result=''):
40
        self.id = None
41
        self.status = status
42
        self.result = result
43
44
45
FIXTURES_PACK = 'generic'
46
47
TEST_MODELS = {
48
    'actions': ['a1.yaml', 'a2.yaml', 'action_4_action_context_param.yaml'],
49
    'runners': ['testrunner1.yaml']
50
}
51
52
MODELS = FixturesLoader().load_models(fixtures_pack=FIXTURES_PACK,
53
                                      fixtures_dict=TEST_MODELS)
54
ACTION_1 = MODELS['actions']['a1.yaml']
55
ACTION_2 = MODELS['actions']['a2.yaml']
56
ACTION_3 = MODELS['actions']['action_4_action_context_param.yaml']
57
RUNNER = MODELS['runners']['testrunner1.yaml']
58
59
CHAIN_1_PATH = FixturesLoader().get_fixture_file_path_abs(
60
    FIXTURES_PACK, 'actionchains', 'chain1.yaml')
61
CHAIN_2_PATH = FixturesLoader().get_fixture_file_path_abs(
62
    FIXTURES_PACK, 'actionchains', 'chain2.yaml')
63
CHAIN_ACTION_CALL_NO_PARAMS_PATH = FixturesLoader().get_fixture_file_path_abs(
64
    FIXTURES_PACK, 'actionchains', 'chain_action_call_no_params.yaml')
65
CHAIN_NO_DEFAULT = FixturesLoader().get_fixture_file_path_abs(
66
    FIXTURES_PACK, 'actionchains', 'no_default_chain.yaml')
67
CHAIN_NO_DEFAULT_2 = FixturesLoader().get_fixture_file_path_abs(
68
    FIXTURES_PACK, 'actionchains', 'no_default_chain_2.yaml')
69
CHAIN_BAD_DEFAULT = FixturesLoader().get_fixture_file_path_abs(
70
    FIXTURES_PACK, 'actionchains', 'bad_default_chain.yaml')
71
CHAIN_BROKEN_ON_SUCCESS_PATH_STATIC_TASK_NAME = FixturesLoader().get_fixture_file_path_abs(
72
    FIXTURES_PACK, 'actionchains', 'chain_broken_on_success_path_static_task_name.yaml')
73
CHAIN_BROKEN_ON_FAILURE_PATH_STATIC_TASK_NAME = FixturesLoader().get_fixture_file_path_abs(
74
    FIXTURES_PACK, 'actionchains', 'chain_broken_on_failure_path_static_task_name.yaml')
75
CHAIN_FIRST_TASK_RENDER_FAIL_PATH = FixturesLoader().get_fixture_file_path_abs(
76
    FIXTURES_PACK, 'actionchains', 'chain_first_task_parameter_render_fail.yaml')
77
CHAIN_SECOND_TASK_RENDER_FAIL_PATH = FixturesLoader().get_fixture_file_path_abs(
78
    FIXTURES_PACK, 'actionchains', 'chain_second_task_parameter_render_fail.yaml')
79
CHAIN_LIST_TEMP_PATH = FixturesLoader().get_fixture_file_path_abs(
80
    FIXTURES_PACK, 'actionchains', 'chain_list_template.yaml')
81
CHAIN_DICT_TEMP_PATH = FixturesLoader().get_fixture_file_path_abs(
82
    FIXTURES_PACK, 'actionchains', 'chain_dict_template.yaml')
83
CHAIN_DEP_INPUT = FixturesLoader().get_fixture_file_path_abs(
84
    FIXTURES_PACK, 'actionchains', 'chain_dependent_input.yaml')
85
CHAIN_DEP_RESULTS_INPUT = FixturesLoader().get_fixture_file_path_abs(
86
    FIXTURES_PACK, 'actionchains', 'chain_dep_result_input.yaml')
87
MALFORMED_CHAIN_PATH = FixturesLoader().get_fixture_file_path_abs(
88
    FIXTURES_PACK, 'actionchains', 'malformedchain.yaml')
89
CHAIN_TYPED_PARAMS = FixturesLoader().get_fixture_file_path_abs(
90
    FIXTURES_PACK, 'actionchains', 'chain_typed_params.yaml')
91
CHAIN_SYSTEM_PARAMS = FixturesLoader().get_fixture_file_path_abs(
92
    FIXTURES_PACK, 'actionchains', 'chain_typed_system_params.yaml')
93
CHAIN_WITH_ACTIONPARAM_VARS = FixturesLoader().get_fixture_file_path_abs(
94
    FIXTURES_PACK, 'actionchains', 'chain_with_actionparam_vars.yaml')
95
CHAIN_WITH_SYSTEM_VARS = FixturesLoader().get_fixture_file_path_abs(
96
    FIXTURES_PACK, 'actionchains', 'chain_with_system_vars.yaml')
97
CHAIN_WITH_PUBLISH = FixturesLoader().get_fixture_file_path_abs(
98
    FIXTURES_PACK, 'actionchains', 'chain_with_publish.yaml')
99
CHAIN_WITH_PUBLISH_PARAM_RENDERING_FAILURE = FixturesLoader().get_fixture_file_path_abs(
100
    FIXTURES_PACK, 'actionchains', 'chain_publish_params_rendering_failure.yaml')
101
CHAIN_WITH_INVALID_ACTION = FixturesLoader().get_fixture_file_path_abs(
102
    FIXTURES_PACK, 'actionchains', 'chain_with_invalid_action.yaml')
103
CHAIN_ACTION_PARAMS_AND_PARAMETERS_ATTRIBUTE = FixturesLoader().get_fixture_file_path_abs(
104
    FIXTURES_PACK, 'actionchains', 'chain_action_params_and_parameters.yaml')
105
CHAIN_ACTION_PARAMS_ATTRIBUTE = FixturesLoader().get_fixture_file_path_abs(
106
    FIXTURES_PACK, 'actionchains', 'chain_action_params_attribute.yaml')
107
CHAIN_ACTION_PARAMETERS_ATTRIBUTE = FixturesLoader().get_fixture_file_path_abs(
108
    FIXTURES_PACK, 'actionchains', 'chain_action_parameters_attribute.yaml')
109
CHAIN_ACTION_INVALID_PARAMETER_TYPE = FixturesLoader().get_fixture_file_path_abs(
110
    FIXTURES_PACK, 'actionchains', 'chain_invalid_parameter_type_passed_to_action.yaml')
111
112
CHAIN_NOTIFY_API = {'notify': {'on-complete': {'message': 'foo happened.'}}}
113
CHAIN_NOTIFY_DB = NotificationsHelper.to_model(CHAIN_NOTIFY_API)
114
115
116
@mock.patch.object(action_db_util, 'get_runnertype_by_name',
117
                   mock.MagicMock(return_value=RUNNER))
118
class TestActionChainRunner(DbTestCase):
119
120
    def test_runner_creation(self):
121
        runner = acr.get_runner()
122
        self.assertTrue(runner)
123
        self.assertTrue(runner.runner_id)
124
125
    def test_malformed_chain(self):
126
        try:
127
            chain_runner = acr.get_runner()
128
            chain_runner.entry_point = MALFORMED_CHAIN_PATH
129
            chain_runner.action = ACTION_1
130
            chain_runner.container_service = RunnerContainerService()
131
            chain_runner.pre_run()
132
            self.assertTrue(False, 'Expected pre_run to fail.')
133
        except runnerexceptions.ActionRunnerPreRunError:
134
            self.assertTrue(True)
135
136
    @mock.patch.object(action_db_util, 'get_action_by_ref',
137
                       mock.MagicMock(return_value=ACTION_1))
138
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
139
    def test_chain_runner_success_path(self, request):
140
        chain_runner = acr.get_runner()
141
        chain_runner.entry_point = CHAIN_1_PATH
142
        chain_runner.action = ACTION_1
143
        action_ref = ResourceReference.to_string_reference(name=ACTION_1.name,
144
                                                           pack=ACTION_1.pack)
145
        chain_runner.liveaction = LiveActionDB(action=action_ref)
146
        chain_runner.liveaction.notify = CHAIN_NOTIFY_DB
147
        chain_runner.container_service = RunnerContainerService()
148
        chain_runner.pre_run()
149
        chain_runner.run({})
150
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
151
        # based on the chain the callcount is known to be 3. Not great but works.
152
        self.assertEqual(request.call_count, 3)
153
154
    @mock.patch.object(action_db_util, 'get_action_by_ref',
155
                       mock.MagicMock(return_value=ACTION_1))
156
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
157
    def test_chain_runner_chain_second_task_times_out(self, request):
158
        # Second task in the chain times out so the action chain status should be timeout
159
        chain_runner = acr.get_runner()
160
        chain_runner.entry_point = CHAIN_2_PATH
161
        chain_runner.action = ACTION_1
162
163
        original_run_action = chain_runner._run_action
164
165
        def mock_run_action(*args, **kwargs):
166
            original_live_action = args[0]
167
            liveaction = original_run_action(*args, **kwargs)
168
            if original_live_action.action == 'wolfpack.a2':
169
                # Mock a timeout for second task
170
                liveaction.status = LIVEACTION_STATUS_TIMED_OUT
171
            return liveaction
172
173
        chain_runner._run_action = mock_run_action
174
175
        action_ref = ResourceReference.to_string_reference(name=ACTION_1.name,
176
                                                           pack=ACTION_1.pack)
177
        chain_runner.liveaction = LiveActionDB(action=action_ref)
178
        chain_runner.container_service = RunnerContainerService()
179
        chain_runner.pre_run()
180
        status, _, _ = chain_runner.run({})
181
182
        self.assertEqual(status, LIVEACTION_STATUS_TIMED_OUT)
183
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
184
        # based on the chain the callcount is known to be 3. Not great but works.
185
        self.assertEqual(request.call_count, 3)
186
187
    @mock.patch.object(action_db_util, 'get_action_by_ref',
188
                       mock.MagicMock(return_value=ACTION_1))
189
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
190
    def test_chain_runner_task_is_canceled_while_running(self, request):
191
        # Second task in the action is CANCELED, make sure runner doesn't get stuck in an infinite
192
        # loop
193
        chain_runner = acr.get_runner()
194
        chain_runner.entry_point = CHAIN_2_PATH
195
        chain_runner.action = ACTION_1
196
197
        original_run_action = chain_runner._run_action
198
199
        def mock_run_action(*args, **kwargs):
200
            original_live_action = args[0]
201
            if original_live_action.action == 'wolfpack.a2':
202
                status = LIVEACTION_STATUS_CANCELED
203
            else:
204
                status = LIVEACTION_STATUS_SUCCEEDED
205
            request.return_value = (DummyActionExecution(status=status), None)
206
            liveaction = original_run_action(*args, **kwargs)
207
            return liveaction
208
209
        chain_runner._run_action = mock_run_action
210
211
        action_ref = ResourceReference.to_string_reference(name=ACTION_1.name,
212
                                                           pack=ACTION_1.pack)
213
        chain_runner.liveaction = LiveActionDB(action=action_ref)
214
        chain_runner.container_service = RunnerContainerService()
215
        chain_runner.pre_run()
216
        status, _, _ = chain_runner.run({})
217
218
        self.assertEqual(status, LIVEACTION_STATUS_CANCELED)
219
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
220
        # Chain count should be 2 since the last task doesn't get called since the second one was
221
        # canceled
222
        self.assertEqual(request.call_count, 2)
223
224
    @mock.patch.object(action_db_util, 'get_action_by_ref',
225
                       mock.MagicMock(return_value=ACTION_1))
226
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
227
    def test_chain_runner_success_task_action_call_with_no_params(self, request):
228
        # Make sure that the runner doesn't explode if task definition contains
229
        # no "params" section
230
        chain_runner = acr.get_runner()
231
        chain_runner.entry_point = CHAIN_ACTION_CALL_NO_PARAMS_PATH
232
        chain_runner.action = ACTION_1
233
        action_ref = ResourceReference.to_string_reference(name=ACTION_1.name,
234
                                                           pack=ACTION_1.pack)
235
        chain_runner.liveaction = LiveActionDB(action=action_ref)
236
        chain_runner.liveaction.notify = CHAIN_NOTIFY_DB
237
        chain_runner.container_service = RunnerContainerService()
238
        chain_runner.pre_run()
239
        chain_runner.run({})
240
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
241
        # based on the chain the callcount is known to be 3. Not great but works.
242
        self.assertEqual(request.call_count, 3)
243
244
    @mock.patch.object(action_db_util, 'get_action_by_ref',
245
                       mock.MagicMock(return_value=ACTION_1))
246
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
247
    def test_chain_runner_no_default(self, request):
248
        chain_runner = acr.get_runner()
249
        chain_runner.entry_point = CHAIN_NO_DEFAULT
250
        chain_runner.action = ACTION_1
251
        chain_runner.container_service = RunnerContainerService()
252
        chain_runner.pre_run()
253
        chain_runner.run({})
254
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
255
        # In case of this chain default_node is the first_node.
256
        default_node = chain_runner.chain_holder.actionchain.default
257
        first_node = chain_runner.chain_holder.actionchain.chain[0]
258
        self.assertEqual(default_node, first_node.name)
259
        # based on the chain the callcount is known to be 3. Not great but works.
260
        self.assertEqual(request.call_count, 3)
261
262
    @mock.patch.object(action_db_util, 'get_action_by_ref',
263
                       mock.MagicMock(return_value=ACTION_1))
264
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
265
    def test_chain_runner_no_default_multiple_options(self, request):
266
        # subtle difference is that when there are multiple possible default nodes
267
        # the order per chain definition may not be preseved. This is really a
268
        # poorly formatted chain but we still the best attempt to work.
269
        chain_runner = acr.get_runner()
270
        chain_runner.entry_point = CHAIN_NO_DEFAULT_2
271
        chain_runner.action = ACTION_1
272
        chain_runner.container_service = RunnerContainerService()
273
        chain_runner.pre_run()
274
        chain_runner.run({})
275
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
276
        # In case of this chain default_node is the first_node.
277
        default_node = chain_runner.chain_holder.actionchain.default
278
        first_node = chain_runner.chain_holder.actionchain.chain[0]
279
        self.assertEqual(default_node, first_node.name)
280
        # based on the chain the callcount is known to be 2.
281
        self.assertEqual(request.call_count, 2)
282
283
    @mock.patch.object(action_db_util, 'get_action_by_ref',
284
                       mock.MagicMock(return_value=ACTION_1))
285
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
286
    def test_chain_runner_bad_default(self, request):
287
        chain_runner = acr.get_runner()
288
        chain_runner.entry_point = CHAIN_BAD_DEFAULT
289
        chain_runner.action = ACTION_1
290
        chain_runner.container_service = RunnerContainerService()
291
        expected_msg = 'Unable to find node with name "bad_default" referenced in "default".'
292
        self.assertRaisesRegexp(runnerexceptions.ActionRunnerPreRunError,
293
                                expected_msg, chain_runner.pre_run)
294
295
    @mock.patch('eventlet.sleep', mock.MagicMock())
296
    @mock.patch.object(action_db_util, 'get_liveaction_by_id', mock.MagicMock(
297
        return_value=DummyActionExecution()))
298
    @mock.patch.object(action_db_util, 'get_action_by_ref',
299
                       mock.MagicMock(return_value=ACTION_1))
300
    @mock.patch.object(action_service, 'request',
301
                       return_value=(DummyActionExecution(status=LIVEACTION_STATUS_RUNNING), None))
302
    def test_chain_runner_success_path_with_wait(self, request):
303
        chain_runner = acr.get_runner()
304
        chain_runner.entry_point = CHAIN_1_PATH
305
        chain_runner.action = ACTION_1
306
        chain_runner.container_service = RunnerContainerService()
307
        chain_runner.pre_run()
308
        chain_runner.run({})
309
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
310
        # based on the chain the callcount is known to be 3. Not great but works.
311
        self.assertEqual(request.call_count, 3)
312
313
    @mock.patch.object(action_db_util, 'get_action_by_ref',
314
                       mock.MagicMock(return_value=ACTION_1))
315
    @mock.patch.object(action_service, 'request',
316
                       return_value=(DummyActionExecution(status=LIVEACTION_STATUS_FAILED), None))
317
    def test_chain_runner_failure_path(self, request):
318
        chain_runner = acr.get_runner()
319
        chain_runner.entry_point = CHAIN_1_PATH
320
        chain_runner.action = ACTION_1
321
        chain_runner.container_service = RunnerContainerService()
322
        chain_runner.pre_run()
323
        status, _, _ = chain_runner.run({})
324
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
325
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
326
        # based on the chain the callcount is known to be 2. Not great but works.
327
        self.assertEqual(request.call_count, 2)
328
329
    @mock.patch.object(action_db_util, 'get_action_by_ref',
330
                       mock.MagicMock(return_value=ACTION_1))
331
    @mock.patch.object(action_service, 'request',
332
                       return_value=(DummyActionExecution(), None))
333
    def test_chain_runner_broken_on_success_path_static_task_name(self, request):
334
        chain_runner = acr.get_runner()
335
        chain_runner.entry_point = CHAIN_BROKEN_ON_SUCCESS_PATH_STATIC_TASK_NAME
336
        chain_runner.action = ACTION_1
337
        chain_runner.container_service = RunnerContainerService()
338
339
        expected_msg = ('Unable to find node with name "c5" referenced in "on-success" '
340
                        'in task "c2"')
341
        self.assertRaisesRegexp(runnerexceptions.ActionRunnerPreRunError,
342
                                expected_msg, chain_runner.pre_run)
343
344
    @mock.patch.object(action_db_util, 'get_action_by_ref',
345
                       mock.MagicMock(return_value=ACTION_1))
346
    @mock.patch.object(action_service, 'request',
347
                       return_value=(DummyActionExecution(), None))
348
    def test_chain_runner_broken_on_failure_path_static_task_name(self, request):
349
        chain_runner = acr.get_runner()
350
        chain_runner.entry_point = CHAIN_BROKEN_ON_FAILURE_PATH_STATIC_TASK_NAME
351
        chain_runner.action = ACTION_1
352
        chain_runner.container_service = RunnerContainerService()
353
354
        expected_msg = ('Unable to find node with name "c6" referenced in "on-failure" '
355
                        'in task "c2"')
356
        self.assertRaisesRegexp(runnerexceptions.ActionRunnerPreRunError,
357
                                expected_msg, chain_runner.pre_run)
358
359
    @mock.patch.object(action_db_util, 'get_action_by_ref',
360
                       mock.MagicMock(return_value=ACTION_1))
361
    @mock.patch.object(action_service, 'request', side_effect=RuntimeError('Test Failure.'))
362
    def test_chain_runner_action_exception(self, request):
363
        chain_runner = acr.get_runner()
364
        chain_runner.entry_point = CHAIN_1_PATH
365
        chain_runner.action = ACTION_1
366
        chain_runner.container_service = RunnerContainerService()
367
        chain_runner.pre_run()
368
        status, results, _ = chain_runner.run({})
369
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
370
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
371
372
        # based on the chain the callcount is known to be 2. Not great but works.
373
        self.assertEqual(request.call_count, 2)
374
375
        error_count = 0
376
        for task_result in results['tasks']:
377
            if task_result['result'].get('error', None):
378
                error_count += 1
379
380
        self.assertEqual(error_count, 2)
381
382
    @mock.patch.object(action_db_util, 'get_action_by_ref',
383
                       mock.MagicMock(return_value=ACTION_1))
384
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
385
    def test_chain_runner_str_param_temp(self, request):
386
        chain_runner = acr.get_runner()
387
        chain_runner.entry_point = CHAIN_FIRST_TASK_RENDER_FAIL_PATH
388
        chain_runner.action = ACTION_1
389
        chain_runner.container_service = RunnerContainerService()
390
        chain_runner.pre_run()
391
        chain_runner.run({'s1': 1, 's2': 2, 's3': 3, 's4': 4})
392
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
393
        mock_args, _ = request.call_args
394
        self.assertEqual(mock_args[0].parameters, {"p1": "1"})
395
396
    @mock.patch.object(action_db_util, 'get_action_by_ref',
397
                       mock.MagicMock(return_value=ACTION_1))
398
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
399
    def test_chain_runner_list_param_temp(self, request):
400
        chain_runner = acr.get_runner()
401
        chain_runner.entry_point = CHAIN_LIST_TEMP_PATH
402
        chain_runner.action = ACTION_1
403
        chain_runner.container_service = RunnerContainerService()
404
        chain_runner.pre_run()
405
        chain_runner.run({'s1': 1, 's2': 2, 's3': 3, 's4': 4})
406
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
407
        mock_args, _ = request.call_args
408
        self.assertEqual(mock_args[0].parameters, {"p1": "[2, 3, 4]"})
409
410
    @mock.patch.object(action_db_util, 'get_action_by_ref',
411
                       mock.MagicMock(return_value=ACTION_1))
412
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
413
    def test_chain_runner_dict_param_temp(self, request):
414
        chain_runner = acr.get_runner()
415
        chain_runner.entry_point = CHAIN_DICT_TEMP_PATH
416
        chain_runner.action = ACTION_1
417
        chain_runner.container_service = RunnerContainerService()
418
        chain_runner.pre_run()
419
        chain_runner.run({'s1': 1, 's2': 2, 's3': 3, 's4': 4})
420
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
421
        expected_value = {"p1": {"p1.3": "[3, 4]", "p1.2": "2", "p1.1": "1"}}
422
        mock_args, _ = request.call_args
423
        self.assertEqual(mock_args[0].parameters, expected_value)
424
425
    @mock.patch.object(action_db_util, 'get_action_by_ref',
426
                       mock.MagicMock(return_value=ACTION_1))
427
    @mock.patch.object(action_service, 'request',
428
                       return_value=(DummyActionExecution(result={'o1': '1'}), None))
429
    def test_chain_runner_dependent_param_temp(self, request):
430
        chain_runner = acr.get_runner()
431
        chain_runner.entry_point = CHAIN_DEP_INPUT
432
        chain_runner.action = ACTION_1
433
        chain_runner.container_service = RunnerContainerService()
434
        chain_runner.pre_run()
435
        chain_runner.run({'s1': 1, 's2': 2, 's3': 3, 's4': 4})
436
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
437
        expected_values = [{u'p1': u'1'},
438
                           {u'p1': u'1'},
439
                           {u'p2': u'1', u'p3': u'1', u'p1': u'1'}]
440
        # Each of the call_args must be one of
441
        for call_args in request.call_args_list:
442
            self.assertTrue(call_args[0][0].parameters in expected_values)
443
            expected_values.remove(call_args[0][0].parameters)
444
        self.assertEqual(len(expected_values), 0, 'Not all expected values received.')
445
446
    @mock.patch.object(action_db_util, 'get_action_by_ref',
447
                       mock.MagicMock(return_value=ACTION_1))
448
    @mock.patch.object(action_service, 'request',
449
                       return_value=(DummyActionExecution(result={'o1': '1'}), None))
450
    def test_chain_runner_dependent_results_param(self, request):
451
        chain_runner = acr.get_runner()
452
        chain_runner.entry_point = CHAIN_DEP_RESULTS_INPUT
453
        chain_runner.action = ACTION_1
454
        chain_runner.container_service = RunnerContainerService()
455
        chain_runner.pre_run()
456
        chain_runner.run({'s1': 1})
457
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
458
        expected_values = [{u'p1': u'1'},
459
                           {u'p1': u'1'},
460
                           {u'out': u"{'c2': {'o1': '1'}, 'c1': {'o1': '1'}}"}]
461
        # Each of the call_args must be one of
462
        self.assertEqual(request.call_count, 3)
463
        for call_args in request.call_args_list:
464
            self.assertTrue(call_args[0][0].parameters in expected_values)
465
            expected_values.remove(call_args[0][0].parameters)
466
        self.assertEqual(len(expected_values), 0, 'Not all expected values received.')
467
468
    @mock.patch.object(action_db_util, 'get_action_by_ref',
469
                       mock.MagicMock(return_value=ACTION_1))
470
    @mock.patch.object(RunnerType, 'get_by_name',
471
                       mock.MagicMock(return_value=RUNNER))
472
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
473
    def test_chain_runner_missing_param_temp(self, request):
474
        chain_runner = acr.get_runner()
475
        chain_runner.entry_point = CHAIN_FIRST_TASK_RENDER_FAIL_PATH
476
        chain_runner.action = ACTION_1
477
        chain_runner.container_service = RunnerContainerService()
478
        chain_runner.pre_run()
479
        chain_runner.run({})
480
        self.assertEqual(request.call_count, 0, 'No call expected.')
481
482
    @mock.patch.object(action_db_util, 'get_action_by_ref',
483
                       mock.MagicMock(return_value=ACTION_1))
484
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
485
    def test_chain_runner_failure_during_param_rendering_single_task(self, request):
486
        # Parameter rendering should result in a top level error which aborts
487
        # the whole chain
488
        chain_runner = acr.get_runner()
489
        chain_runner.entry_point = CHAIN_FIRST_TASK_RENDER_FAIL_PATH
490
        chain_runner.action = ACTION_1
491
        chain_runner.container_service = RunnerContainerService()
492
        chain_runner.pre_run()
493
        status, result, _ = chain_runner.run({})
494
495
        # No tasks ran because rendering of parameters for the first task failed
496
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
497
        self.assertEqual(result['tasks'], [])
498
        self.assertTrue('error' in result)
499
        self.assertTrue('traceback' in result)
500
        self.assertTrue('Failed to run task "c1". Parameter rendering failed' in result['error'])
501
        self.assertTrue('Traceback' in result['traceback'])
502
503
    @mock.patch.object(action_db_util, 'get_action_by_ref',
504
                       mock.MagicMock(return_value=ACTION_1))
505
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
506
    def test_chain_runner_failure_during_param_rendering_multiple_tasks(self, request):
507
        # Parameter rendering should result in a top level error which aborts
508
        # the whole chain
509
        chain_runner = acr.get_runner()
510
        chain_runner.entry_point = CHAIN_SECOND_TASK_RENDER_FAIL_PATH
511
        chain_runner.action = ACTION_1
512
        chain_runner.container_service = RunnerContainerService()
513
        chain_runner.pre_run()
514
        status, result, _ = chain_runner.run({})
515
516
        # Verify that only first task has ran
517
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
518
        self.assertEqual(len(result['tasks']), 1)
519
        self.assertEqual(result['tasks'][0]['name'], 'c1')
520
521
        expected_error = ('Failed rendering value for action parameter "p1" in '
522
                          'task "c2" (template string={{s1}}):')
523
524
        self.assertTrue('error' in result)
525
        self.assertTrue('traceback' in result)
526
        self.assertTrue('Failed to run task "c2". Parameter rendering failed' in result['error'])
527
        self.assertTrue(expected_error in result['error'])
528
        self.assertTrue('Traceback' in result['traceback'])
529
530
    @mock.patch.object(action_db_util, 'get_action_by_ref',
531
                       mock.MagicMock(return_value=ACTION_2))
532
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
533
    def test_chain_runner_typed_params(self, request):
534
        chain_runner = acr.get_runner()
535
        chain_runner.entry_point = CHAIN_TYPED_PARAMS
536
        chain_runner.action = ACTION_2
537
        chain_runner.container_service = RunnerContainerService()
538
        chain_runner.pre_run()
539
        chain_runner.run({'s1': 1, 's2': 'two', 's3': 3.14})
540
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
541
        expected_value = {'booltype': True,
542
                          'inttype': 1,
543
                          'numbertype': 3.14,
544
                          'strtype': 'two',
545
                          'arrtype': ['1', 'two'],
546
                          'objtype': {'s2': 'two',
547
                                      'k1': '1'}}
548
        mock_args, _ = request.call_args
549
        self.assertEqual(mock_args[0].parameters, expected_value)
550
551
    @mock.patch.object(action_db_util, 'get_action_by_ref',
552
                       mock.MagicMock(return_value=ACTION_2))
553
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
554
    def test_chain_runner_typed_system_params(self, request):
555
        kvps = []
556
        try:
557
            kvps.append(KeyValuePair.add_or_update(KeyValuePairDB(name='a', value='1')))
558
            kvps.append(KeyValuePair.add_or_update(KeyValuePairDB(name='a.b.c', value='two')))
559
            chain_runner = acr.get_runner()
560
            chain_runner.entry_point = CHAIN_SYSTEM_PARAMS
561
            chain_runner.action = ACTION_2
562
            chain_runner.container_service = RunnerContainerService()
563
            chain_runner.pre_run()
564
            chain_runner.run({})
565
            self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
566
            expected_value = {'inttype': 1,
567
                              'strtype': 'two'}
568
            mock_args, _ = request.call_args
569
            self.assertEqual(mock_args[0].parameters, expected_value)
570
        finally:
571
            for kvp in kvps:
572
                KeyValuePair.delete(kvp)
573
574
    @mock.patch.object(action_db_util, 'get_action_by_ref',
575
                       mock.MagicMock(return_value=ACTION_2))
576
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
577
    def test_chain_runner_vars_system_params(self, request):
578
        kvps = []
579
        try:
580
            kvps.append(KeyValuePair.add_or_update(KeyValuePairDB(name='a', value='two')))
581
            chain_runner = acr.get_runner()
582
            chain_runner.entry_point = CHAIN_WITH_SYSTEM_VARS
583
            chain_runner.action = ACTION_2
584
            chain_runner.container_service = RunnerContainerService()
585
            chain_runner.pre_run()
586
            chain_runner.run({})
587
            self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
588
            expected_value = {'inttype': 1,
589
                              'strtype': 'two',
590
                              'strtype_legacy': 'two',
591
                              'booltype': True}
592
            mock_args, _ = request.call_args
593
            self.assertEqual(mock_args[0].parameters, expected_value)
594
        finally:
595
            for kvp in kvps:
596
                KeyValuePair.delete(kvp)
597
598
    @mock.patch.object(action_db_util, 'get_action_by_ref',
599
                       mock.MagicMock(return_value=ACTION_2))
600
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
601
    def test_chain_runner_vars_action_params(self, request):
602
        chain_runner = acr.get_runner()
603
        chain_runner.entry_point = CHAIN_WITH_ACTIONPARAM_VARS
604
        chain_runner.action = ACTION_2
605
        chain_runner.container_service = RunnerContainerService()
606
        chain_runner.pre_run()
607
        chain_runner.run({'input_a': 'two'})
608
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
609
        expected_value = {'inttype': 1,
610
                          'strtype': 'two',
611
                          'booltype': True}
612
        mock_args, _ = request.call_args
613
        self.assertEqual(mock_args[0].parameters, expected_value)
614
615
    @mock.patch.object(action_db_util, 'get_action_by_ref',
616
                       mock.MagicMock(return_value=ACTION_2))
617
    @mock.patch.object(action_service, 'request',
618
                       return_value=(DummyActionExecution(result={'raw_out': 'published'}), None))
619
    def test_chain_runner_publish(self, request):
620
        chain_runner = acr.get_runner()
621
        chain_runner.entry_point = CHAIN_WITH_PUBLISH
622
        chain_runner.action = ACTION_2
623
        chain_runner.container_service = RunnerContainerService()
624
        chain_runner.runner_parameters = {'display_published': True}
625
        chain_runner.pre_run()
626
627
        action_parameters = {'action_param_1': 'test value 1'}
628
        _, result, _ = chain_runner.run(action_parameters=action_parameters)
629
630
        # We also assert that the action parameters are available in the
631
        # "publish" scope
632
        self.assertNotEqual(chain_runner.chain_holder.actionchain, None)
633
        expected_value = {'inttype': 1,
634
                          'strtype': 'published',
635
                          'booltype': True,
636
                          'published_action_param': action_parameters['action_param_1']}
637
        mock_args, _ = request.call_args
638
        self.assertEqual(mock_args[0].parameters, expected_value)
639
        # Assert that the variables are correctly published
640
        self.assertEqual(result['published'],
641
                         {'published_action_param': u'test value 1', 'o1': u'published'})
642
643
    @mock.patch.object(action_db_util, 'get_action_by_ref',
644
                       mock.MagicMock(return_value=ACTION_1))
645
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
646
    def test_chain_runner_publish_param_rendering_failure(self, request):
647
        # Parameter rendering should result in a top level error which aborts
648
        # the whole chain
649
        chain_runner = acr.get_runner()
650
        chain_runner.entry_point = CHAIN_WITH_PUBLISH_PARAM_RENDERING_FAILURE
651
        chain_runner.action = ACTION_1
652
        chain_runner.container_service = RunnerContainerService()
653
        chain_runner.pre_run()
654
655
        try:
656
            chain_runner.run({})
657
        except ParameterRenderingFailedException as e:
658
            # TODO: Should we treat this as task error? Right now it bubbles all
659
            # the way up and it's not really consistent with action param
660
            # rendering failure
661
            expected_error = ('Failed rendering value for publish parameter "p1" in '
662
                              'task "c2" (template string={{ not_defined }}):')
663
            self.assertTrue(expected_error in str(e))
664
            pass
665
        else:
666
            self.fail('Exception was not thrown')
667
668
    @mock.patch.object(action_db_util, 'get_action_by_ref',
669
                       mock.MagicMock(return_value=ACTION_2))
670
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
671
    def test_chain_task_passes_invalid_parameter_type_to_action(self, mock_request):
672
        chain_runner = acr.get_runner()
673
        chain_runner.entry_point = CHAIN_ACTION_INVALID_PARAMETER_TYPE
674
        chain_runner.action = ACTION_2
675
        chain_runner.container_service = RunnerContainerService()
676
        chain_runner.pre_run()
677
678
        action_parameters = {}
679
        expected_msg = ('Failed to cast value "stringnotanarray" \(type: str\) for parameter '
680
                        '"arrtype" of type "array"')
681
        self.assertRaisesRegexp(ValueError, expected_msg, chain_runner.run,
682
                                action_parameters=action_parameters)
683
684
    @mock.patch.object(action_db_util, 'get_action_by_ref',
685
                       mock.MagicMock(return_value=None))
686
    @mock.patch.object(action_service, 'request',
687
                       return_value=(DummyActionExecution(result={'raw_out': 'published'}), None))
688
    def test_action_chain_runner_referenced_action_doesnt_exist(self, mock_request):
689
        # Action referenced by a task doesn't exist, should result in a top level error
690
        chain_runner = acr.get_runner()
691
        chain_runner.entry_point = CHAIN_WITH_INVALID_ACTION
692
        chain_runner.action = ACTION_2
693
        chain_runner.container_service = RunnerContainerService()
694
        chain_runner.pre_run()
695
696
        action_parameters = {}
697
        status, output, _ = chain_runner.run(action_parameters=action_parameters)
698
699
        expected_error = ('Failed to run task "c1". Action with reference "wolfpack.a2" '
700
                          'doesn\'t exist.')
701
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
702
        self.assertTrue(expected_error in output['error'])
703
        self.assertTrue('Traceback' in output['traceback'], output['traceback'])
704
705
    def test_exception_is_thrown_if_both_params_and_parameters_attributes_are_provided(self):
706
        chain_runner = acr.get_runner()
707
        chain_runner.entry_point = CHAIN_ACTION_PARAMS_AND_PARAMETERS_ATTRIBUTE
708
        chain_runner.action = ACTION_2
709
        chain_runner.container_service = RunnerContainerService()
710
711
        expected_msg = ('Either "params" or "parameters" attribute needs to be provided, but '
712
                       'not both')
713
        self.assertRaisesRegexp(runnerexceptions.ActionRunnerPreRunError, expected_msg,
714
                                chain_runner.pre_run)
715
716
    @mock.patch.object(action_db_util, 'get_action_by_ref',
717
                       mock.MagicMock(return_value=ACTION_1))
718
    @mock.patch.object(action_service, 'request', return_value=(DummyActionExecution(), None))
719
    def test_params_and_parameters_attributes_both_work(self, _):
720
        # "params" attribute used
721
        chain_runner = acr.get_runner()
722
        chain_runner.entry_point = CHAIN_ACTION_PARAMS_ATTRIBUTE
723
        chain_runner.action = ACTION_2
724
        chain_runner.container_service = RunnerContainerService()
725
        chain_runner.pre_run()
726
727
        original_build_liveaction_object = chain_runner._build_liveaction_object
728
729
        def mock_build_liveaction_object(action_node, resolved_params, parent_context):
730
            # Verify parameters are correctly passed to the action
731
            self.assertEqual(resolved_params, {'pparams': 'v1'})
732
            original_build_liveaction_object(action_node=action_node,
733
                                             resolved_params=resolved_params,
734
                                             parent_context=parent_context)
735
736
        chain_runner._build_liveaction_object = mock_build_liveaction_object
737
738
        action_parameters = {}
739
        status, output, _ = chain_runner.run(action_parameters=action_parameters)
740
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
741
742
        # "parameters" attribute used
743
        chain_runner = acr.get_runner()
744
        chain_runner.entry_point = CHAIN_ACTION_PARAMETERS_ATTRIBUTE
745
        chain_runner.action = ACTION_2
746
        chain_runner.container_service = RunnerContainerService()
747
        chain_runner.pre_run()
748
749
        def mock_build_liveaction_object(action_node, resolved_params, parent_context):
750
            # Verify parameters are correctly passed to the action
751
            self.assertEqual(resolved_params, {'pparameters': 'v1'})
752
            original_build_liveaction_object(action_node=action_node,
753
                                             resolved_params=resolved_params,
754
                                             parent_context=parent_context)
755
756
        chain_runner._build_liveaction_object = mock_build_liveaction_object
757
758
        action_parameters = {}
759
        status, output, _ = chain_runner.run(action_parameters=action_parameters)
760
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
761
762
    @classmethod
763
    def tearDownClass(cls):
764
        FixturesLoader().delete_models_from_db(MODELS)
765