summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavanum Srinivas <davanum@gmail.com>2015-06-24 14:24:50 -0400
committerDavanum Srinivas <davanum@gmail.com>2015-07-07 10:12:58 -0700
commit36fb964f0c683a0fec478d2c1288f6d675d604f6 (patch)
tree2ccf76de9e47b2d22f4ac2496a97287fe0bd2b3a
parent45ff5571380ea4fbd1cab6dca76bd05ebe59b317 (diff)
downloadoslo-concurrency-36fb964f0c683a0fec478d2c1288f6d675d604f6.tar.gz
Allow preexec_fn method for processutils.execute
If the user specifies preexec_fn, we should call that in our existing _subprocess_setup. On windows, we silently drop this preexec_fn as subprocess.Popen raises a ValueError if we do pass it in. Change-Id: I0176c66fa2de001aa14f0d928d06fd894de55511
-rw-r--r--oslo_concurrency/processutils.py20
-rw-r--r--oslo_concurrency/tests/unit/test_processutils.py17
2 files changed, 33 insertions, 4 deletions
diff --git a/oslo_concurrency/processutils.py b/oslo_concurrency/processutils.py
index 01c02db..36ac9b0 100644
--- a/oslo_concurrency/processutils.py
+++ b/oslo_concurrency/processutils.py
@@ -17,6 +17,7 @@
System-level utilities and helper functions.
"""
+import functools
import logging
import multiprocessing
import os
@@ -87,10 +88,12 @@ class NoRootWrapSpecified(Exception):
super(NoRootWrapSpecified, self).__init__(message)
-def _subprocess_setup():
+def _subprocess_setup(on_preexec_fn):
# Python installs a SIGPIPE handler by default. This is usually not what
# non-Python subprocesses expect.
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ if on_preexec_fn:
+ on_preexec_fn()
LOG_ALL_ERRORS = 1
@@ -159,6 +162,13 @@ def execute(*cmd, **kwargs):
`processutils.execute` to track process completion
asynchronously.
:type on_completion: function(:class:`subprocess.Popen`)
+ :param preexec_fn: This function will be called
+ in the child process just before the child
+ is executed. WARNING: On windows, we silently
+ drop this preexec_fn as it is not supported by
+ subprocess.Popen on windows (throws a
+ ValueError)
+ :type preexec_fn: function()
:returns: (stdout, stderr) from process execution
:raises: :class:`UnknownArgumentError` on
receiving unknown arguments
@@ -181,6 +191,7 @@ def execute(*cmd, **kwargs):
binary = kwargs.pop('binary', False)
on_execute = kwargs.pop('on_execute', None)
on_completion = kwargs.pop('on_completion', None)
+ preexec_fn = kwargs.pop('preexec_fn', None)
if isinstance(check_exit_code, bool):
ignore_exit_code = not check_exit_code
@@ -220,10 +231,11 @@ def execute(*cmd, **kwargs):
_PIPE = subprocess.PIPE # pylint: disable=E1101
if os.name == 'nt':
- preexec_fn = None
+ on_preexec_fn = None
close_fds = False
else:
- preexec_fn = _subprocess_setup
+ on_preexec_fn = functools.partial(_subprocess_setup,
+ preexec_fn)
close_fds = True
obj = subprocess.Popen(cmd,
@@ -231,7 +243,7 @@ def execute(*cmd, **kwargs):
stdout=_PIPE,
stderr=_PIPE,
close_fds=close_fds,
- preexec_fn=preexec_fn,
+ preexec_fn=on_preexec_fn,
shell=shell,
cwd=cwd,
env=env_variables)
diff --git a/oslo_concurrency/tests/unit/test_processutils.py b/oslo_concurrency/tests/unit/test_processutils.py
index 771d966..5ec5a9b 100644
--- a/oslo_concurrency/tests/unit/test_processutils.py
+++ b/oslo_concurrency/tests/unit/test_processutils.py
@@ -97,6 +97,23 @@ class UtilsTest(test_base.BaseTestCase):
self.assertEqual(1, on_execute_callback.call_count)
self.assertEqual(1, on_completion_callback.call_count)
+ def test_execute_with_preexec_fn(self):
+ # NOTE(dims): preexec_fn is set to a callable object, this object
+ # will be called in the child process just before the child is
+ # executed. So we cannot pass share variables etc, simplest is to
+ # check if a specific exception is thrown which can be caught here.
+ def preexec_fn():
+ raise processutils.InvalidArgumentError()
+
+ processutils.execute("/bin/true")
+
+ expected_exception = (processutils.InvalidArgumentError if six.PY2
+ else subprocess.SubprocessError)
+ self.assertRaises(expected_exception,
+ processutils.execute,
+ "/bin/true",
+ preexec_fn=preexec_fn)
+
class ProcessExecutionErrorTest(test_base.BaseTestCase):