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
This commit is contained in:
parent
45ff557138
commit
36fb964f0c
@ -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)
|
||||
|
@ -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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user