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 | from __future__ import absolute_import |
||
17 | import os |
||
18 | from unittest2 import TestCase |
||
19 | |||
20 | import mock |
||
21 | from six.moves import zip |
||
22 | |||
23 | from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED |
||
24 | from st2common.constants.action import LIVEACTION_STATUS_FAILED |
||
25 | from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT |
||
26 | |||
27 | from windows_runner.windows_command_runner import BaseWindowsRunner |
||
28 | from windows_runner.windows_script_runner import WindowsScriptRunner |
||
29 | |||
30 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
||
31 | FIXTURES_DIR = os.path.abspath(os.path.join(BASE_DIR, '../fixtures/windows')) |
||
32 | |||
33 | |||
34 | class WindowsRunnerTestCase(TestCase): |
||
35 | def test_get_winexe_command_args(self): |
||
36 | arguments = [ |
||
37 | { |
||
38 | 'host': 'localhost', |
||
39 | 'username': 'Administrator1', |
||
40 | 'password': 'bar1', |
||
41 | 'command': 'powershell.exe "C:\\\\myscript.ps1"' |
||
42 | }, |
||
43 | { |
||
44 | 'host': '127.0.0.1', |
||
45 | 'username': 'Administrator2', |
||
46 | 'password': 'bar2', |
||
47 | 'command': 'dir' |
||
48 | }, |
||
49 | { |
||
50 | 'host': 'localhost', |
||
51 | 'username': 'Administrator3', |
||
52 | 'password': 'bar3', |
||
53 | 'command': 'dir', |
||
54 | 'domain': 'MyDomain' |
||
55 | } |
||
56 | ] |
||
57 | expected_values = [ |
||
58 | [ |
||
59 | 'winexe', |
||
60 | '--interactive', '0', |
||
61 | '-U', 'Administrator1%bar1', |
||
62 | '//localhost', |
||
63 | 'powershell.exe "C:\\\\myscript.ps1"' |
||
64 | ], |
||
65 | [ |
||
66 | 'winexe', |
||
67 | '--interactive', '0', |
||
68 | '-U', 'Administrator2%bar2', |
||
69 | '//127.0.0.1', |
||
70 | 'dir' |
||
71 | ], |
||
72 | [ |
||
73 | 'winexe', |
||
74 | '--interactive', '0', |
||
75 | '-U', 'MyDomain\Administrator3%bar3', |
||
76 | '//localhost', |
||
77 | 'dir' |
||
78 | ] |
||
79 | ] |
||
80 | |||
81 | runner = self._get_base_runner() |
||
82 | for arguments, expected_value in zip(arguments, expected_values): |
||
83 | actual_value = runner._get_winexe_command_args(**arguments) |
||
84 | self.assertEqual(actual_value, expected_value) |
||
85 | |||
86 | def test_get_smbclient_command_args(self): |
||
87 | arguments = [ |
||
88 | { |
||
89 | 'host': 'localhost', |
||
90 | 'username': 'Administrator1', |
||
91 | 'password': 'bar1', |
||
92 | 'command': 'put /home/1.txt 1.txt', |
||
93 | 'share': 'C$' |
||
94 | }, |
||
95 | { |
||
96 | 'host': 'localhost', |
||
97 | 'username': 'Administrator2', |
||
98 | 'password': 'bar2', |
||
99 | 'command': 'put /home/2.txt 2.txt', |
||
100 | 'share': 'D$' |
||
101 | }, |
||
102 | { |
||
103 | 'host': 'localhost', |
||
104 | 'username': 'Administrator3', |
||
105 | 'password': 'bar3', |
||
106 | 'command': 'dir', |
||
107 | 'share': 'E$', |
||
108 | 'domain': 'MyDomain' |
||
109 | } |
||
110 | ] |
||
111 | expected_values = [ |
||
112 | [ |
||
113 | 'smbclient', |
||
114 | '-U', 'Administrator1%bar1', |
||
115 | '//localhost/C$', |
||
116 | '-c', 'put /home/1.txt 1.txt' |
||
117 | ], |
||
118 | [ |
||
119 | 'smbclient', |
||
120 | '-U', 'Administrator2%bar2', |
||
121 | '//localhost/D$', |
||
122 | '-c', 'put /home/2.txt 2.txt' |
||
123 | ], |
||
124 | [ |
||
125 | 'smbclient', |
||
126 | '-U', 'MyDomain\Administrator3%bar3', |
||
127 | '//localhost/E$', |
||
128 | '-c', 'dir' |
||
129 | ], |
||
130 | ] |
||
131 | |||
132 | runner = self._get_base_runner() |
||
133 | for arguments, expected_value in zip(arguments, expected_values): |
||
134 | actual_value = runner._get_smbclient_command_args(**arguments) |
||
135 | self.assertEqual(actual_value, expected_value) |
||
136 | |||
137 | def test_get_script_args(self): |
||
138 | arguments = [ |
||
139 | { |
||
140 | 'positional_args': 'a b c', |
||
141 | 'named_args': { |
||
142 | 'arg1': 'value1', |
||
143 | 'arg2': 'value2' |
||
144 | } |
||
145 | }, |
||
146 | { |
||
147 | 'positional_args': 'a b c', |
||
148 | 'named_args': { |
||
149 | 'arg1': 'value1', |
||
150 | 'arg2': True, |
||
151 | 'arg3': False, |
||
152 | 'arg4': ['foo', 'bar', 'baz'] |
||
153 | } |
||
154 | } |
||
155 | ] |
||
156 | expected_values = [ |
||
157 | 'a b c -arg1 value1 -arg2 value2', |
||
158 | 'a b c -arg1 value1 -arg2 -arg3:$false -arg4 foo,bar,baz' |
||
159 | ] |
||
160 | |||
161 | runner = self._get_mock_script_runner() |
||
162 | for arguments, expected_value in zip(arguments, expected_values): |
||
163 | actual_value = runner._get_script_arguments(**arguments) |
||
0 ignored issues
–
show
|
|||
164 | self.assertEqual(actual_value, expected_value) |
||
165 | |||
166 | def test_parse_share_information(self): |
||
167 | runner = self._get_mock_script_runner() |
||
168 | |||
169 | fixture_path = os.path.join(FIXTURES_DIR, 'net_share_C_stdout.txt') |
||
170 | with open(fixture_path, 'r') as fp: |
||
171 | stdout = fp.read() |
||
172 | |||
173 | result = runner._parse_share_information(stdout=stdout) |
||
174 | |||
175 | expected_keys = ['share_name', 'path', 'remark', 'maximum_users', 'users', 'caching', |
||
176 | 'permission'] |
||
177 | for key in expected_keys: |
||
178 | self.assertTrue(key in result) |
||
179 | |||
180 | self.assertEqual(result['share_name'], 'C$') |
||
181 | self.assertEqual(result['path'], 'C:\\') |
||
182 | self.assertEqual(result['users'], None) |
||
183 | |||
184 | @mock.patch('windows_runner.windows_script_runner.run_command') |
||
185 | def test_get_share_absolute_path(self, mock_run_command): |
||
186 | runner = self._get_mock_script_runner() |
||
187 | |||
188 | fixture_path = os.path.join(FIXTURES_DIR, 'net_share_C_stdout.txt') |
||
189 | with open(fixture_path, 'r') as fp: |
||
190 | stdout = fp.read() |
||
191 | |||
192 | # Failure, non-zero status code |
||
193 | mock_run_command.return_value = (2, '', '', False) |
||
194 | self.assertRaises(Exception, runner._get_share_absolute_path, share='C$') |
||
195 | |||
196 | # Failure, missing / corrupted data |
||
197 | mock_run_command.return_value = (0, '', '', False) |
||
198 | self.assertRaises(Exception, runner._get_share_absolute_path, share='C$') |
||
199 | |||
200 | # Success, everything OK |
||
201 | mock_run_command.return_value = (0, stdout, '', False) |
||
202 | share_path = runner._get_share_absolute_path(share='C$') |
||
203 | self.assertEqual(share_path, 'C:\\') |
||
204 | |||
205 | def test_run_output_object_and_status(self): |
||
206 | runner = self._get_mock_script_runner() |
||
207 | |||
208 | runner._upload_file = mock.Mock(return_value=('/tmp/a', '/tmp/b')) |
||
209 | runner._delete_directory = mock.Mock() |
||
210 | runner._get_share_absolute_path = mock.Mock(return_value='/tmp') |
||
211 | runner._parse_winexe_error = mock.Mock(return_value='') |
||
212 | runner._verify_winexe_exists = mock.Mock() |
||
213 | runner._verify_smbclient_exists = mock.Mock() |
||
214 | |||
215 | # success |
||
216 | exit_code, stdout, stderr, timed_out = 0, 'stdout foo', 'stderr bar', False |
||
217 | runner._run_script = mock.Mock(return_value=(exit_code, stdout, stderr, timed_out)) |
||
218 | |||
219 | runner.runner_parameters = {} |
||
220 | (status, output, _) = runner.run({}) |
||
221 | |||
222 | expected_output = { |
||
223 | 'stdout': 'stdout foo', |
||
224 | 'stderr': 'stderr bar', |
||
225 | 'return_code': 0, |
||
226 | 'succeeded': True, |
||
227 | 'failed': False |
||
228 | } |
||
229 | |||
230 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
231 | self.assertDictEqual(output, expected_output) |
||
232 | |||
233 | # failure |
||
234 | exit_code, stdout, stderr, timed_out = 1, 'stdout fail', 'stderr fail', False |
||
235 | runner._run_script = mock.Mock(return_value=(exit_code, stdout, stderr, timed_out)) |
||
236 | |||
237 | runner.runner_parameters = {} |
||
238 | (status, output, _) = runner.run({}) |
||
239 | |||
240 | expected_output = { |
||
241 | 'stdout': 'stdout fail', |
||
242 | 'stderr': 'stderr fail', |
||
243 | 'return_code': 1, |
||
244 | 'succeeded': False, |
||
245 | 'failed': True |
||
246 | } |
||
247 | |||
248 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
249 | self.assertDictEqual(output, expected_output) |
||
250 | |||
251 | # failure with winexe error |
||
252 | exit_code, stdout, stderr, timed_out = 1, 'stdout fail 2', 'stderr fail 2', False |
||
253 | runner._run_script = mock.Mock(return_value=(exit_code, stdout, stderr, timed_out)) |
||
254 | runner._parse_winexe_error = mock.Mock(return_value='winexe error 2') |
||
255 | |||
256 | runner.runner_parameters = {} |
||
257 | (status, output, _) = runner.run({}) |
||
258 | |||
259 | expected_output = { |
||
260 | 'stdout': 'stdout fail 2', |
||
261 | 'stderr': 'stderr fail 2', |
||
262 | 'return_code': 1, |
||
263 | 'succeeded': False, |
||
264 | 'failed': True, |
||
265 | 'error': 'winexe error 2' |
||
266 | } |
||
267 | |||
268 | # timeout with non zero exit code |
||
269 | exit_code, stdout, stderr, timed_out = 200, 'stdout timeout', 'stderr timeout', True |
||
270 | runner._run_script = mock.Mock(return_value=(exit_code, stdout, stderr, timed_out)) |
||
271 | runner._parse_winexe_error = mock.Mock(return_value=None) |
||
272 | runner._timeout = 5 |
||
273 | |||
274 | runner.runner_parameters = {} |
||
275 | (status, output, _) = runner.run({}) |
||
276 | |||
277 | expected_output = { |
||
278 | 'stdout': 'stdout timeout', |
||
279 | 'stderr': 'stderr timeout', |
||
280 | 'return_code': 200, |
||
281 | 'succeeded': False, |
||
282 | 'failed': True, |
||
283 | 'error': 'Action failed to complete in 5 seconds' |
||
284 | } |
||
285 | |||
286 | self.assertEqual(status, LIVEACTION_STATUS_TIMED_OUT) |
||
287 | self.assertDictEqual(output, expected_output) |
||
288 | |||
289 | # timeout with zero exit code |
||
290 | exit_code, stdout, stderr, timed_out = 0, 'stdout timeout', 'stderr timeout', True |
||
291 | runner._run_script = mock.Mock(return_value=(exit_code, stdout, stderr, timed_out)) |
||
292 | runner._timeout = 5 |
||
293 | runner._parse_winexe_error = mock.Mock(return_value='winexe error') |
||
294 | |||
295 | runner.runner_parameters = {} |
||
296 | (status, output, _) = runner.run({}) |
||
297 | |||
298 | expected_output = { |
||
299 | 'stdout': 'stdout timeout', |
||
300 | 'stderr': 'stderr timeout', |
||
301 | 'return_code': 0, |
||
302 | 'succeeded': False, |
||
303 | 'failed': True, |
||
304 | 'error': 'Action failed to complete in 5 seconds: winexe error' |
||
305 | } |
||
306 | |||
307 | self.assertEqual(status, LIVEACTION_STATUS_TIMED_OUT) |
||
308 | self.assertDictEqual(output, expected_output) |
||
309 | |||
310 | def test_shell_command_parameter_escaping(self): |
||
311 | pass |
||
312 | |||
313 | def _get_base_runner(self): |
||
314 | class Runner(BaseWindowsRunner): |
||
315 | def pre_run(self): |
||
316 | pass |
||
317 | |||
318 | def run(self): |
||
319 | pass |
||
320 | |||
321 | runner = Runner('id') |
||
322 | return runner |
||
323 | |||
324 | def _get_mock_script_runner(self, action_parameters=None): |
||
325 | runner = WindowsScriptRunner('id') |
||
326 | runner._host = None |
||
327 | runner._username = None |
||
328 | runner._password = None |
||
329 | runner._timeout = None |
||
330 | runner._share = None |
||
331 | |||
332 | action_db = mock.Mock() |
||
333 | action_db.pack = 'dummy_pack_1' |
||
334 | action_db.entry_point = 'foo.py' |
||
335 | action_db.parameters = action_parameters or {} |
||
336 | |||
337 | runner.action = action_db |
||
338 | |||
339 | return runner |
||
340 |
Prefixing a member variable
_
is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class: