Skip to content

Commit 1a52bda

Browse files
authored
Merge pull request #4713 from StackStorm/various_misc_fixes
Various changes and fixes which make StackStorm code more re-usable in different contexts
2 parents 17febe1 + f927f34 commit 1a52bda

File tree

16 files changed

+294
-68
lines changed

16 files changed

+294
-68
lines changed

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Changed
1111
* Update requests library to latest version (2.22.0) in requirements. (improvement) #4680
1212
* Disallow "decrypt_kv" filter to be specified in the config for values that are marked as
1313
"secret: True" in the schema. (improvement) #4709
14+
* Upgrade ``tooz`` library to latest stable version (1.65.0) so it uses latest version of
15+
``grpcio`` library. (improvement) #4713
16+
* Update ``st2-pack-install`` and ``st2-pack-download`` CLI command so it supports installing
17+
packs from local directories which are not git repositories. (improvement) #4713
1418

1519
Fixed
1620
~~~~~
@@ -19,6 +23,14 @@ Fixed
1923
* Allow tasks defined in the same task transition with ``fail`` to run for orquesta. (bug fix)
2024
* Fix workflow service to handle unexpected coordinator and database errors. (bug fix) #4704 #4705
2125
* Fix filter ``to_yaml_string`` to handle mongoengine base types for dict and list. (bug fix) #4700
26+
* Fix timeout handling in the Python runner. In some scenarios where action would time out before
27+
producing any output (stdout, stder), timeout was not correctly propagated to the user. (bug fix)
28+
#4713
29+
* Update ``st2common/setup.py`` file so it correctly declares all the dependencies and script
30+
files it provides. This way ``st2-pack-*`` commands can be used in a standalone fashion just by
31+
installing ``st2common`` Python package and nothing else. (bug fix) #4713
32+
* Fix ``st2-pack-download`` command so it works in the environments where ``sudo`` binary is not
33+
available (e.g. Docker). (bug fix) #4713
2234

2335
3.0.1 - May 24, 2019
2436
--------------------
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
name : test4
3+
description : st2 pack to test package management pipeline
4+
keywords:
5+
- some
6+
- search
7+
- another
8+
- terms
9+
version : "0.4.0"
10+
author : st2-dev
11+

contrib/packs/tests/test_action_download.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
from pack_mgmt.download import DownloadGitRepoAction
3737

38+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39+
3840
PACK_INDEX = {
3941
"test": {
4042
"version": "0.4.0",
@@ -67,6 +69,18 @@
6769
}
6870

6971

72+
original_is_dir_func = os.path.isdir
73+
74+
75+
def mock_is_dir_func(path):
76+
"""
77+
Mock function which returns True if path ends with .git
78+
"""
79+
if path.endswith('.git'):
80+
return True
81+
return original_is_dir_func(path)
82+
83+
7084
@mock.patch.object(pack_service, 'fetch_pack_index', mock.MagicMock(return_value=(PACK_INDEX, {})))
7185
class DownloadGitRepoActionTestCase(BaseActionTestCase):
7286
action_cls = DownloadGitRepoAction
@@ -79,8 +93,9 @@ def setUp(self):
7993
self.addCleanup(clone_from.stop)
8094
self.clone_from = clone_from.start()
8195

96+
self.expand_user_path = tempfile.mkdtemp()
8297
expand_user = mock.patch.object(os.path, 'expanduser',
83-
mock.MagicMock(return_value=tempfile.mkdtemp()))
98+
mock.MagicMock(return_value=self.expand_user_path))
8499

85100
self.addCleanup(expand_user.stop)
86101
self.expand_user = expand_user.start()
@@ -522,16 +537,38 @@ def fake_commit(arg_ref):
522537
result = action.run(packs=packs, abs_repo_base=self.repo_base)
523538
self.assertEqual(result, {'test': 'Success.'})
524539

540+
@mock.patch('os.path.isdir', mock_is_dir_func)
525541
def test_run_pack_dowload_local_git_repo_detached_head_state(self):
526542
action = self.get_action_instance()
527543

528544
type(self.repo_instance).active_branch = \
529545
mock.PropertyMock(side_effect=TypeError('detached head'))
530546

531-
result = action.run(packs=['file:///stackstorm-test'], abs_repo_base=self.repo_base)
547+
pack_path = os.path.join(BASE_DIR, 'fixtures/stackstorm-test')
548+
549+
result = action.run(packs=['file://%s' % (pack_path)], abs_repo_base=self.repo_base)
532550
self.assertEqual(result, {'test': 'Success.'})
533551

534552
# Verify function has bailed out early
535553
self.repo_instance.git.checkout.assert_not_called()
536554
self.repo_instance.git.branch.assert_not_called()
537555
self.repo_instance.git.checkout.assert_not_called()
556+
557+
def test_run_pack_download_local_directory(self):
558+
action = self.get_action_instance()
559+
560+
# 1. Local directory doesn't exist
561+
expected_msg = r'Local pack directory ".*" doesn\'t exist'
562+
self.assertRaisesRegexp(ValueError, expected_msg, action.run,
563+
packs=['file://doesnt_exist'], abs_repo_base=self.repo_base)
564+
565+
# 2. Local pack which is not a git repository
566+
pack_path = os.path.join(BASE_DIR, 'fixtures/stackstorm-test4')
567+
568+
result = action.run(packs=['file://%s' % (pack_path)], abs_repo_base=self.repo_base)
569+
self.assertEqual(result, {'test4': 'Success.'})
570+
571+
# Verify pack contents have been copied over
572+
destination_path = os.path.join(self.repo_base, 'test4')
573+
self.assertTrue(os.path.exists(destination_path))
574+
self.assertTrue(os.path.exists(os.path.join(destination_path, 'pack.yaml')))

contrib/runners/local_runner/local_runner/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
import six
2424
from oslo_config import cfg
25-
from eventlet.green import subprocess
2625
from six.moves import StringIO
2726

2827
from st2common.constants import action as action_constants
@@ -35,6 +34,7 @@
3534
from st2common.util.green import shell
3635
from st2common.util.shell import kill_process
3736
from st2common.util import jsonify
37+
from st2common.util import concurrency
3838
from st2common.services.action import store_execution_output_data
3939
from st2common.runners.utils import make_read_and_store_stream_func
4040

@@ -138,13 +138,15 @@ def _run(self, action):
138138
read_and_store_stderr = make_read_and_store_stream_func(execution_db=self.execution,
139139
action_db=self.action, store_data_func=store_execution_stderr_line)
140140

141+
subprocess = concurrency.get_subprocess_module()
142+
141143
# If sudo password is provided, pass it to the subprocess via stdin>
142144
# Note: We don't need to explicitly escape the argument because we pass command as a list
143145
# to subprocess.Popen and all the arguments are escaped by the function.
144146
if self._sudo_password:
145147
LOG.debug('Supplying sudo password via stdin')
146-
echo_process = subprocess.Popen(['echo', self._sudo_password + '\n'],
147-
stdout=subprocess.PIPE)
148+
echo_process = concurrency.subprocess_popen(['echo', self._sudo_password + '\n'],
149+
stdout=subprocess.PIPE)
148150
stdin = echo_process.stdout
149151
else:
150152
stdin = None

contrib/runners/local_runner/tests/integration/test_localrunner.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ def test_sudo_and_env_variable_preservation(self):
152152
self.assertEquals(status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
153153
self.assertEqual(result['stdout'].strip(), 'root\nponiesponies')
154154

155-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
156-
@mock.patch('st2common.util.green.shell.eventlet.spawn')
155+
@mock.patch('st2common.util.concurrency.subprocess_popen')
156+
@mock.patch('st2common.util.concurrency.spawn')
157157
def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_popen):
158158
# Feature is enabled
159159
cfg.CONF.set_override(name='stream_output', group='actionrunner', override=True)
@@ -210,8 +210,8 @@ def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_pop
210210
self.assertEqual(output_dbs[1].data, mock_stderr[1])
211211
self.assertEqual(output_dbs[2].data, mock_stderr[2])
212212

213-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
214-
@mock.patch('st2common.util.green.shell.eventlet.spawn')
213+
@mock.patch('st2common.util.concurrency.subprocess_popen')
214+
@mock.patch('st2common.util.concurrency.spawn')
215215
def test_action_stdout_and_stderr_is_stored_in_the_db_short_running_action(self, mock_spawn,
216216
mock_popen):
217217
# Verify that we correctly retrieve all the output and wait for stdout and stderr reading
@@ -331,7 +331,7 @@ def test_shell_command_sudo_password_is_passed_to_sudo_binary(self):
331331
self.assertEquals(result['stdout'], sudo_password)
332332

333333
# Verify new process which provides password via stdin to the command is created
334-
with mock.patch('eventlet.green.subprocess.Popen') as mock_subproc_popen:
334+
with mock.patch('st2common.util.concurrency.subprocess_popen') as mock_subproc_popen:
335335
index = 0
336336
for sudo_password in sudo_passwords:
337337
runner = self._get_runner(action_db, cmd=cmd)
@@ -510,8 +510,8 @@ def test_script_with_parameters_parameter_serialization(self):
510510
output_dbs = ActionExecutionOutput.query(output_type='stderr')
511511
self.assertEqual(len(output_dbs), 0)
512512

513-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
514-
@mock.patch('st2common.util.green.shell.eventlet.spawn')
513+
@mock.patch('st2common.util.concurrency.subprocess_popen')
514+
@mock.patch('st2common.util.concurrency.spawn')
515515
def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_popen):
516516
# Feature is enabled
517517
cfg.CONF.set_override(name='stream_output', group='actionrunner', override=True)

contrib/runners/python_runner/python_runner/python_runner.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,17 @@ def _get_output_values(self, exit_code, stdout, stderr, timed_out):
318318
action_result = split[1].strip()
319319
stdout = split[0] + split[2]
320320
else:
321+
# Timeout or similar
321322
action_result = None
322323

323-
# Parse the serialized action result object
324-
try:
325-
action_result = json.loads(action_result)
326-
except Exception as e:
327-
# Failed to de-serialize the result, probably it contains non-simple type or similar
328-
LOG.warning('Failed to de-serialize result "%s": %s' % (str(action_result),
329-
six.text_type(e)))
324+
# Parse the serialized action result object (if available)
325+
if action_result:
326+
try:
327+
action_result = json.loads(action_result)
328+
except Exception as e:
329+
# Failed to de-serialize the result, probably it contains non-simple type or similar
330+
LOG.warning('Failed to de-serialize result "%s": %s' % (str(action_result),
331+
six.text_type(e)))
330332

331333
if action_result:
332334
if isinstance(action_result, dict):

contrib/runners/python_runner/tests/unit/test_pythonrunner.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def test_simple_action_no_entry_point(self):
251251
expected_msg = 'Action .*? is missing entry_point attribute'
252252
self.assertRaisesRegexp(Exception, expected_msg, runner.run, {})
253253

254-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
254+
@mock.patch('st2common.util.concurrency.subprocess_popen')
255255
def test_action_with_user_supplied_env_vars(self, mock_popen):
256256
env_vars = {'key1': 'val1', 'key2': 'val2', 'PYTHONPATH': 'foobar'}
257257

@@ -275,8 +275,8 @@ def test_action_with_user_supplied_env_vars(self, mock_popen):
275275
else:
276276
self.assertEqual(actual_env[key], value)
277277

278-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
279-
@mock.patch('st2common.util.green.shell.eventlet.spawn')
278+
@mock.patch('st2common.util.concurrency.subprocess_popen')
279+
@mock.patch('st2common.util.concurrency.spawn')
280280
def test_action_stdout_and_stderr_is_not_stored_in_db_by_default(self, mock_spawn, mock_popen):
281281
# Feature should be disabled by default
282282
values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER}
@@ -344,8 +344,8 @@ def test_action_stdout_and_stderr_is_not_stored_in_db_by_default(self, mock_spaw
344344
output_dbs = ActionExecutionOutput.get_all()
345345
self.assertEqual(len(output_dbs), 0)
346346

347-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
348-
@mock.patch('st2common.util.green.shell.eventlet.spawn')
347+
@mock.patch('st2common.util.concurrency.subprocess_popen')
348+
@mock.patch('st2common.util.concurrency.spawn')
349349
def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_popen):
350350
# Feature is enabled
351351
cfg.CONF.set_override(name='stream_output', group='actionrunner', override=True)
@@ -406,7 +406,7 @@ def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_pop
406406
self.assertEqual(output_dbs[1].data, mock_stderr[1])
407407
self.assertEqual(output_dbs[2].data, mock_stderr[2])
408408

409-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
409+
@mock.patch('st2common.util.concurrency.subprocess_popen')
410410
def test_stdout_interception_and_parsing(self, mock_popen):
411411
values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER}
412412

@@ -478,7 +478,7 @@ def test_stdout_interception_and_parsing(self, mock_popen):
478478
self.assertEqual(output['exit_code'], 0)
479479
self.assertEqual(status, 'succeeded')
480480

481-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
481+
@mock.patch('st2common.util.concurrency.subprocess_popen')
482482
def test_common_st2_env_vars_are_available_to_the_action(self, mock_popen):
483483
mock_process = mock.Mock()
484484
mock_process.communicate.return_value = ('', '')
@@ -495,7 +495,7 @@ def test_common_st2_env_vars_are_available_to_the_action(self, mock_popen):
495495
actual_env = call_kwargs['env']
496496
self.assertCommonSt2EnvVarsAvailableInEnv(env=actual_env)
497497

498-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
498+
@mock.patch('st2common.util.concurrency.subprocess_popen')
499499
def test_pythonpath_env_var_contains_common_libs_config_enabled(self, mock_popen):
500500
mock_process = mock.Mock()
501501
mock_process.communicate.return_value = ('', '')
@@ -515,7 +515,7 @@ def test_pythonpath_env_var_contains_common_libs_config_enabled(self, mock_popen
515515
self.assertTrue('PYTHONPATH' in actual_env)
516516
self.assertTrue(pack_common_lib_path in actual_env['PYTHONPATH'])
517517

518-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
518+
@mock.patch('st2common.util.concurrency.subprocess_popen')
519519
def test_pythonpath_env_var_not_contains_common_libs_config_disabled(self, mock_popen):
520520
mock_process = mock.Mock()
521521
mock_process.communicate.return_value = ('', '')
@@ -806,7 +806,7 @@ def test_content_version_success(self, mock_get_sandbox_virtualenv_path):
806806
self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
807807

808808
@mock.patch('python_runner.python_runner.get_sandbox_virtualenv_path')
809-
@mock.patch('st2common.util.green.shell.subprocess.Popen')
809+
@mock.patch('st2common.util.concurrency.subprocess_popen')
810810
def test_content_version_contains_common_libs_config_enabled(self, mock_popen,
811811
mock_get_sandbox_virtualenv_path):
812812
# Verify that the common libs path correctly reflects directory in git worktree

fixed-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ virtualenv==16.6.0
3939
sseclient-py==1.7
4040
python-editor==1.0.4
4141
prompt-toolkit==1.0.15
42-
tooz==1.64.2
42+
tooz==1.65.0
4343
zake==0.2.2
4444
routes==2.4.1
4545
webob==1.8.4

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ semver==2.8.1
5252
six==1.12.0
5353
sseclient-py==1.7
5454
stevedore==1.30.1
55-
tooz==1.64.2
55+
tooz==1.65.0
5656
ujson==1.35
5757
unittest2
5858
webob==1.8.4

st2common/in-requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ ujson
3333
# Note: amqp is used by kombu, this needs to be added here to be picked up by
3434
# requirements fixate script.
3535
amqp
36+
# Used by st2-pack-* commands
37+
gitpython
38+
lockfile

0 commit comments

Comments
 (0)