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:
Davanum Srinivas 2015-06-24 14:24:50 -04:00
parent 45ff557138
commit 36fb964f0c
2 changed files with 33 additions and 4 deletions

View File

@ -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)

View File

@ -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):