Passed
Pull Request — master (#3457)
by W
05:00
created

LocalShellCommandRunnerTestCase.test_shutdown()   A

Complexity

Conditions 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
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 os
17
import uuid
18
19
import mock
20
21
import st2tests.config as tests_config
22
tests_config.parse_args()
23
24
from unittest2 import TestCase
25
from st2actions.container.service import RunnerContainerService
26
from st2common.constants import action as action_constants
27
from st2tests.fixturesloader import FixturesLoader
28
from st2tests.fixturesloader import get_fixtures_base_path
29
from st2common.util.api import get_full_public_api_url
30
from st2common.util.green import shell
31
from st2common.constants.runners import LOCAL_RUNNER_DEFAULT_ACTION_TIMEOUT
32
import local_runner
33
34
35
class LocalShellCommandRunnerTestCase(TestCase):
36
    fixtures_loader = FixturesLoader()
37
38
    def test_shell_command_action_basic(self):
39
        models = self.fixtures_loader.load_models(
40
            fixtures_pack='generic', fixtures_dict={'actions': ['local.yaml']})
41
        action_db = models['actions']['local.yaml']
42
        runner = self._get_runner(action_db, cmd='echo 10')
43
        runner.pre_run()
44
        status, result, _ = runner.run({})
45
        runner.post_run(status, result)
46
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
47
        self.assertEquals(result['stdout'], 10)
48
49
    def test_shell_script_action(self):
50
        models = self.fixtures_loader.load_models(
51
            fixtures_pack='localrunner_pack', fixtures_dict={'actions': ['text_gen.yml']})
52
        action_db = models['actions']['text_gen.yml']
53
        entry_point = self.fixtures_loader.get_fixture_file_path_abs(
54
            'localrunner_pack', 'actions', 'text_gen.py')
55
        runner = self._get_runner(action_db, entry_point=entry_point)
56
        runner.pre_run()
57
        status, result, _ = runner.run({'chars': 1000})
58
        runner.post_run(status, result)
59
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
60
        self.assertEquals(len(result['stdout']), 1000)
61
62
    def test_timeout(self):
63
        models = self.fixtures_loader.load_models(
64
            fixtures_pack='generic', fixtures_dict={'actions': ['local.yaml']})
65
        action_db = models['actions']['local.yaml']
66
        # smaller timeout == faster tests.
67
        runner = self._get_runner(action_db, cmd='sleep 10', timeout=0.01)
68
        runner.pre_run()
69
        status, result, _ = runner.run({})
70
        runner.post_run(status, result)
71
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_TIMED_OUT)
72
73
    @mock.patch.object(
74
        shell, 'run_command',
75
        mock.MagicMock(return_value=(-15, '', '', False)))
76
    def test_shutdown(self):
77
        models = self.fixtures_loader.load_models(
78
            fixtures_pack='generic', fixtures_dict={'actions': ['local.yaml']})
79
        action_db = models['actions']['local.yaml']
80
        runner = self._get_runner(action_db, cmd='sleep 0.1')
81
        runner.pre_run()
82
        status, result, _ = runner.run({})
83
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_ABANDONED)
84
85
    def test_large_stdout(self):
86
        models = self.fixtures_loader.load_models(
87
            fixtures_pack='localrunner_pack', fixtures_dict={'actions': ['text_gen.yml']})
88
        action_db = models['actions']['text_gen.yml']
89
        entry_point = self.fixtures_loader.get_fixture_file_path_abs(
90
            'localrunner_pack', 'actions', 'text_gen.py')
91
        runner = self._get_runner(action_db, entry_point=entry_point)
92
        runner.pre_run()
93
        char_count = 10 ** 6  # Note 10^7 succeeds but ends up being slow.
94
        status, result, _ = runner.run({'chars': char_count})
95
        runner.post_run(status, result)
96
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
97
        self.assertEquals(len(result['stdout']), char_count)
98
99
    def test_common_st2_env_vars_are_available_to_the_action(self):
100
        models = self.fixtures_loader.load_models(
101
            fixtures_pack='generic', fixtures_dict={'actions': ['local.yaml']})
102
        action_db = models['actions']['local.yaml']
103
104
        runner = self._get_runner(action_db, cmd='echo $ST2_ACTION_API_URL')
105
        runner.pre_run()
106
        status, result, _ = runner.run({})
107
        runner.post_run(status, result)
108
109
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
110
        self.assertEqual(result['stdout'].strip(), get_full_public_api_url())
111
112
        runner = self._get_runner(action_db, cmd='echo $ST2_ACTION_AUTH_TOKEN')
113
        runner.pre_run()
114
        status, result, _ = runner.run({})
115
        runner.post_run(status, result)
116
117
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
118
        self.assertEqual(result['stdout'].strip(), 'mock-token')
119
120
    def test_sudo_and_env_variable_preservation(self):
121
        # Verify that the environment environment are correctly preserved when running as a
122
        # root / non-system user
123
        # Note: This test will fail if SETENV option is not present in the sudoers file
124
        models = self.fixtures_loader.load_models(
125
            fixtures_pack='generic', fixtures_dict={'actions': ['local.yaml']})
126
        action_db = models['actions']['local.yaml']
127
128
        cmd = 'echo `whoami` ; echo ${VAR1}'
129
        env = {'VAR1': 'poniesponies'}
130
        runner = self._get_runner(action_db, cmd=cmd, sudo=True, env=env)
131
        runner.pre_run()
132
        status, result, _ = runner.run({})
133
        runner.post_run(status, result)
134
135
        self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
136
        self.assertEqual(result['stdout'].strip(), 'root\nponiesponies')
137
138
    @staticmethod
139
    def _get_runner(action_db,
140
                    entry_point=None,
141
                    cmd=None,
142
                    on_behalf_user=None,
143
                    user=None,
144
                    kwarg_op=local_runner.DEFAULT_KWARG_OP,
145
                    timeout=LOCAL_RUNNER_DEFAULT_ACTION_TIMEOUT,
146
                    sudo=False,
147
                    env=None):
148
        runner = local_runner.LocalShellRunner(uuid.uuid4().hex)
149
        runner.container_service = RunnerContainerService()
150
        runner.action = action_db
151
        runner.action_name = action_db.name
152
        runner.liveaction_id = uuid.uuid4().hex
153
        runner.entry_point = entry_point
154
        runner.runner_parameters = {local_runner.RUNNER_COMMAND: cmd,
155
                                    local_runner.RUNNER_SUDO: sudo,
156
                                    local_runner.RUNNER_ENV: env,
157
                                    local_runner.RUNNER_ON_BEHALF_USER: user,
158
                                    local_runner.RUNNER_KWARG_OP: kwarg_op,
159
                                    local_runner.RUNNER_TIMEOUT: timeout}
160
        runner.context = dict()
161
        runner.callback = dict()
162
        runner.libs_dir_path = None
163
        runner.auth_token = mock.Mock()
164
        runner.auth_token.token = 'mock-token'
165
        return runner
166
167
168
class LocalShellScriptRunner(TestCase):
169
    fixtures_loader = FixturesLoader()
170
171
    def test_script_with_paramters_parameter_serialization(self):
172
        models = self.fixtures_loader.load_models(
173
            fixtures_pack='generic', fixtures_dict={'actions': ['local_script_with_params.yaml']})
174
        action_db = models['actions']['local_script_with_params.yaml']
175
        entry_point = os.path.join(get_fixtures_base_path(),
176
                                   'generic/actions/local_script_with_params.sh')
177
178
        action_parameters = {
179
            'param_string': 'test string',
180
            'param_integer': 1,
181
            'param_float': 2.55,
182
            'param_boolean': True,
183
            'param_list': ['a', 'b', 'c'],
184
            'param_object': {'foo': 'bar'}
185
        }
186
187
        runner = self._get_runner(action_db=action_db, entry_point=entry_point)
188
        runner.pre_run()
189
        status, result, _ = runner.run(action_parameters=action_parameters)
190
        runner.post_run(status, result)
191
192
        self.assertEqual(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
193
        self.assertTrue('PARAM_STRING=test string' in result['stdout'])
194
        self.assertTrue('PARAM_INTEGER=1' in result['stdout'])
195
        self.assertTrue('PARAM_FLOAT=2.55' in result['stdout'])
196
        self.assertTrue('PARAM_BOOLEAN=1' in result['stdout'])
197
        self.assertTrue('PARAM_LIST=a,b,c' in result['stdout'])
198
        self.assertTrue('PARAM_OBJECT={"foo": "bar"}' in result['stdout'])
199
200
        action_parameters = {
201
            'param_string': 'test string',
202
            'param_integer': 1,
203
            'param_float': 2.55,
204
            'param_boolean': False,
205
            'param_list': ['a', 'b', 'c'],
206
            'param_object': {'foo': 'bar'}
207
        }
208
209
        runner = self._get_runner(action_db=action_db, entry_point=entry_point)
210
        runner.pre_run()
211
        status, result, _ = runner.run(action_parameters=action_parameters)
212
        runner.post_run(status, result)
213
214
        self.assertEqual(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
215
        self.assertTrue('PARAM_BOOLEAN=0' in result['stdout'])
216
217
        action_parameters = {
218
            'param_string': '',
219
            'param_integer': None,
220
            'param_float': None,
221
        }
222
223
        runner = self._get_runner(action_db=action_db, entry_point=entry_point)
224
        runner.pre_run()
225
        status, result, _ = runner.run(action_parameters=action_parameters)
226
        runner.post_run(status, result)
227
228
        self.assertEqual(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
229
        self.assertTrue('PARAM_STRING=\n' in result['stdout'])
230
        self.assertTrue('PARAM_INTEGER=\n' in result['stdout'])
231
        self.assertTrue('PARAM_FLOAT=\n' in result['stdout'])
232
233
    def _get_runner(self, action_db, entry_point):
234
        runner = local_runner.LocalShellRunner(uuid.uuid4().hex)
235
        runner.container_service = RunnerContainerService()
236
        runner.action = action_db
237
        runner.action_name = action_db.name
238
        runner.liveaction_id = uuid.uuid4().hex
239
        runner.entry_point = entry_point
240
        runner.runner_parameters = {}
241
        runner.context = dict()
242
        runner.callback = dict()
243
        runner.libs_dir_path = None
244
        runner.auth_token = mock.Mock()
245
        runner.auth_token.token = 'mock-token'
246
        return runner
247