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.
|
System-level utilities and helper functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
@ -87,10 +88,12 @@ class NoRootWrapSpecified(Exception):
|
|||||||
super(NoRootWrapSpecified, self).__init__(message)
|
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
|
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||||
# non-Python subprocesses expect.
|
# non-Python subprocesses expect.
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
if on_preexec_fn:
|
||||||
|
on_preexec_fn()
|
||||||
|
|
||||||
|
|
||||||
LOG_ALL_ERRORS = 1
|
LOG_ALL_ERRORS = 1
|
||||||
@ -159,6 +162,13 @@ def execute(*cmd, **kwargs):
|
|||||||
`processutils.execute` to track process completion
|
`processutils.execute` to track process completion
|
||||||
asynchronously.
|
asynchronously.
|
||||||
:type on_completion: function(:class:`subprocess.Popen`)
|
: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
|
:returns: (stdout, stderr) from process execution
|
||||||
:raises: :class:`UnknownArgumentError` on
|
:raises: :class:`UnknownArgumentError` on
|
||||||
receiving unknown arguments
|
receiving unknown arguments
|
||||||
@ -181,6 +191,7 @@ def execute(*cmd, **kwargs):
|
|||||||
binary = kwargs.pop('binary', False)
|
binary = kwargs.pop('binary', False)
|
||||||
on_execute = kwargs.pop('on_execute', None)
|
on_execute = kwargs.pop('on_execute', None)
|
||||||
on_completion = kwargs.pop('on_completion', None)
|
on_completion = kwargs.pop('on_completion', None)
|
||||||
|
preexec_fn = kwargs.pop('preexec_fn', None)
|
||||||
|
|
||||||
if isinstance(check_exit_code, bool):
|
if isinstance(check_exit_code, bool):
|
||||||
ignore_exit_code = not check_exit_code
|
ignore_exit_code = not check_exit_code
|
||||||
@ -220,10 +231,11 @@ def execute(*cmd, **kwargs):
|
|||||||
_PIPE = subprocess.PIPE # pylint: disable=E1101
|
_PIPE = subprocess.PIPE # pylint: disable=E1101
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
preexec_fn = None
|
on_preexec_fn = None
|
||||||
close_fds = False
|
close_fds = False
|
||||||
else:
|
else:
|
||||||
preexec_fn = _subprocess_setup
|
on_preexec_fn = functools.partial(_subprocess_setup,
|
||||||
|
preexec_fn)
|
||||||
close_fds = True
|
close_fds = True
|
||||||
|
|
||||||
obj = subprocess.Popen(cmd,
|
obj = subprocess.Popen(cmd,
|
||||||
@ -231,7 +243,7 @@ def execute(*cmd, **kwargs):
|
|||||||
stdout=_PIPE,
|
stdout=_PIPE,
|
||||||
stderr=_PIPE,
|
stderr=_PIPE,
|
||||||
close_fds=close_fds,
|
close_fds=close_fds,
|
||||||
preexec_fn=preexec_fn,
|
preexec_fn=on_preexec_fn,
|
||||||
shell=shell,
|
shell=shell,
|
||||||
cwd=cwd,
|
cwd=cwd,
|
||||||
env=env_variables)
|
env=env_variables)
|
||||||
|
@ -97,6 +97,23 @@ class UtilsTest(test_base.BaseTestCase):
|
|||||||
self.assertEqual(1, on_execute_callback.call_count)
|
self.assertEqual(1, on_execute_callback.call_count)
|
||||||
self.assertEqual(1, on_completion_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):
|
class ProcessExecutionErrorTest(test_base.BaseTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user