From 8b61c44fe2698d0ef2787d5e411b6332dcb6aae7 Mon Sep 17 00:00:00 2001 From: Mikhail Durnosvistov Date: Thu, 5 Jun 2014 17:30:03 +0300 Subject: [PATCH] Sync processutils from oslo code Sync openstack/common/processutils.py: cdcc19c1d78a4a88daabfb64b27abd4924aa442d 2a4d15dda6f2c32cce322d82f1a81de6b50853f0 8a0f5678485076fbe0ea1f318c7fd49bac3f1df8 51778f9eef2dd88a949679f0280f69ee99fd5567 fcf517d72cb81f972fad20caa9ff0341e9b4aa9c af4159266f6e366a3cd7366a672f8f8b38a2f0f2 f773ea298cd9f322023b91247e688726cf674cd5 8b2b0b743e84ceed7841cf470afed6a5da8e1d07 3b71f46628822c74c66d37cae8eca9e7d8679d0d 12bcdb71ffbe9ee1688beed1f0ddb0c198822682 a4dab739a780a38c5263b02a670a1d0d009f829e d6a963e911b8456c06dceb5ee3cc88a70c08bf82 aa5b6588292e804557c7ff7f24a88f0ea97acca8 1a2df89c05d33495d7e057dcdf0714d05beb1fc8 7119e29cb535426c587eaf2cfc2cfcd11a422df0 2f013887c20310c522f9494ed854554e6f8c7aa4 a51469326e84ed977ecc4e57fd3d46cdc21aa08f Change-Id: I50439ced3adec1e6befcb7cb65fe558852f2611a --- ironic/openstack/common/processutils.py | 91 ++++++++++++++++--------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/ironic/openstack/common/processutils.py b/ironic/openstack/common/processutils.py index 2bc34d07e3..39fc2e10a9 100644 --- a/ironic/openstack/common/processutils.py +++ b/ironic/openstack/common/processutils.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2011 OpenStack Foundation. # All Rights Reserved. # @@ -19,6 +17,8 @@ System-level utilities and helper functions. """ +import errno +import logging as stdlib_logging import os import random import shlex @@ -26,6 +26,7 @@ import signal from eventlet.green import subprocess from eventlet import greenthread +import six from ironic.openstack.common.gettextutils import _ from ironic.openstack.common import log as logging @@ -54,11 +55,18 @@ class ProcessExecutionError(Exception): self.description = description if description is None: - description = "Unexpected error while running command." + description = _("Unexpected error while running command.") if exit_code is None: exit_code = '-' - message = ("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" - % (description, cmd, exit_code, stdout, stderr)) + message = _('%(description)s\n' + 'Command: %(cmd)s\n' + 'Exit code: %(exit_code)s\n' + 'Stdout: %(stdout)r\n' + 'Stderr: %(stderr)r') % {'description': description, + 'cmd': cmd, + 'exit_code': exit_code, + 'stdout': stdout, + 'stderr': stderr} super(ProcessExecutionError, self).__init__(message) @@ -74,14 +82,17 @@ def _subprocess_setup(): def execute(*cmd, **kwargs): - """ - Helper method to shell out and execute a command through subprocess with - optional retry. + """Helper method to shell out and execute a command through subprocess. + + Allows optional retry. :param cmd: Passed to subprocess.Popen. :type cmd: string :param process_input: Send to opened process. - :type proces_input: string + :type process_input: string + :param env_variables: Environment variables and their values that + will be set for the process. + :type env_variables: dict :param check_exit_code: Single bool, int, or list of allowed exit codes. Defaults to [0]. Raise :class:`ProcessExecutionError` unless @@ -102,6 +113,9 @@ def execute(*cmd, **kwargs): :param shell: whether or not there should be a shell used to execute this command. Defaults to false. :type shell: boolean + :param loglevel: log level for execute commands. + :type loglevel: int. (Should be stdlib_logging.DEBUG or + stdlib_logging.INFO) :returns: (stdout, stderr) from process execution :raises: :class:`UnknownArgumentError` on receiving unknown arguments @@ -109,6 +123,7 @@ def execute(*cmd, **kwargs): """ process_input = kwargs.pop('process_input', None) + env_variables = kwargs.pop('env_variables', None) check_exit_code = kwargs.pop('check_exit_code', [0]) ignore_exit_code = False delay_on_retry = kwargs.pop('delay_on_retry', True) @@ -116,6 +131,7 @@ def execute(*cmd, **kwargs): run_as_root = kwargs.pop('run_as_root', False) root_helper = kwargs.pop('root_helper', '') shell = kwargs.pop('shell', False) + loglevel = kwargs.pop('loglevel', stdlib_logging.DEBUG) if isinstance(check_exit_code, bool): ignore_exit_code = not check_exit_code @@ -127,11 +143,11 @@ def execute(*cmd, **kwargs): raise UnknownArgumentError(_('Got unknown keyword args ' 'to utils.execute: %r') % kwargs) - if run_as_root and os.geteuid() != 0: + if run_as_root and hasattr(os, 'geteuid') and os.geteuid() != 0: if not root_helper: raise NoRootWrapSpecified( - message=('Command requested root, but did not specify a root ' - 'helper.')) + message=_('Command requested root, but did not ' + 'specify a root helper.')) cmd = shlex.split(root_helper) + list(cmd) cmd = map(str, cmd) @@ -139,7 +155,8 @@ def execute(*cmd, **kwargs): while attempts > 0: attempts -= 1 try: - LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd)) + LOG.log(loglevel, 'Running cmd (subprocess): %s', + ' '.join(logging.mask_password(cmd))) _PIPE = subprocess.PIPE # pylint: disable=E1101 if os.name == 'nt': @@ -155,28 +172,37 @@ def execute(*cmd, **kwargs): stderr=_PIPE, close_fds=close_fds, preexec_fn=preexec_fn, - shell=shell) + shell=shell, + env=env_variables) result = None - if process_input is not None: - result = obj.communicate(process_input) - else: - result = obj.communicate() + for _i in six.moves.range(20): + # NOTE(russellb) 20 is an arbitrary number of retries to + # prevent any chance of looping forever here. + try: + if process_input is not None: + result = obj.communicate(process_input) + else: + result = obj.communicate() + except OSError as e: + if e.errno in (errno.EAGAIN, errno.EINTR): + continue + raise + break obj.stdin.close() # pylint: disable=E1101 _returncode = obj.returncode # pylint: disable=E1101 - if _returncode: - LOG.debug(_('Result was %s') % _returncode) - if not ignore_exit_code and _returncode not in check_exit_code: - (stdout, stderr) = result - raise ProcessExecutionError(exit_code=_returncode, - stdout=stdout, - stderr=stderr, - cmd=' '.join(cmd)) + LOG.log(loglevel, 'Result was %s' % _returncode) + if not ignore_exit_code and _returncode not in check_exit_code: + (stdout, stderr) = result + raise ProcessExecutionError(exit_code=_returncode, + stdout=stdout, + stderr=stderr, + cmd=' '.join(cmd)) return result except ProcessExecutionError: if not attempts: raise else: - LOG.debug(_('%r failed. Retrying.'), cmd) + LOG.log(loglevel, '%r failed. Retrying.', cmd) if delay_on_retry: greenthread.sleep(random.randint(20, 200) / 100.0) finally: @@ -187,8 +213,7 @@ def execute(*cmd, **kwargs): def trycmd(*args, **kwargs): - """ - A wrapper around execute() to more easily handle warnings and errors. + """A wrapper around execute() to more easily handle warnings and errors. Returns an (out, err) tuple of strings containing the output of the command's stdout and stderr. If 'err' is not empty then the @@ -203,8 +228,8 @@ def trycmd(*args, **kwargs): try: out, err = execute(*args, **kwargs) failed = False - except ProcessExecutionError, exn: - out, err = '', str(exn) + except ProcessExecutionError as exn: + out, err = '', six.text_type(exn) failed = True if not failed and discard_warnings and err: @@ -216,7 +241,7 @@ def trycmd(*args, **kwargs): def ssh_execute(ssh, cmd, process_input=None, addl_env=None, check_exit_code=True): - LOG.debug(_('Running cmd (SSH): %s'), cmd) + LOG.debug('Running cmd (SSH): %s', cmd) if addl_env: raise InvalidArgumentError(_('Environment not supported over SSH')) @@ -237,7 +262,7 @@ def ssh_execute(ssh, cmd, process_input=None, # exit_status == -1 if no exit code was returned if exit_status != -1: - LOG.debug(_('Result was %s') % exit_status) + LOG.debug('Result was %s' % exit_status) if check_exit_code and exit_status != 0: raise ProcessExecutionError(exit_code=exit_status, stdout=stdout,