¡@

Home 

OpenStack Study: processutils.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright 2011 OpenStack Foundation.

# All Rights Reserved.

#

# Licensed under the Apache License, Version 2.0 (the "License"); you may

# not use this file except in compliance with the License. You may obtain

# a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

# License for the specific language governing permissions and limitations

# under the License.

"""

System-level utilities and helper functions.

"""

import logging as stdlib_logging

import os

import random

import shlex

import signal

from eventlet.green import subprocess

from eventlet import greenthread

from keystone.openstack.common.gettextutils import _ # noqa

from keystone.openstack.common import log as logging

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

class InvalidArgumentError(Exception):

**** CubicPower OpenStack Study ****

    def __init__(self, message=None):

        super(InvalidArgumentError, self).__init__(message)

**** CubicPower OpenStack Study ****

class UnknownArgumentError(Exception):

**** CubicPower OpenStack Study ****

    def __init__(self, message=None):

        super(UnknownArgumentError, self).__init__(message)

**** CubicPower OpenStack Study ****

class ProcessExecutionError(Exception):

**** CubicPower OpenStack Study ****

    def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,

                 description=None):

        self.exit_code = exit_code

        self.stderr = stderr

        self.stdout = stdout

        self.cmd = cmd

        self.description = description

        if description is None:

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

        super(ProcessExecutionError, self).__init__(message)

**** CubicPower OpenStack Study ****

class NoRootWrapSpecified(Exception):

**** CubicPower OpenStack Study ****

    def __init__(self, message=None):

        super(NoRootWrapSpecified, self).__init__(message)

def _subprocess_setup():

    # Python installs a SIGPIPE handler by default. This is usually not what

    # non-Python subprocesses expect.

    signal.signal(signal.SIGPIPE, signal.SIG_DFL)

def execute(*cmd, **kwargs):

    """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

    :param check_exit_code: Single bool, int, or list of allowed exit

                            codes.  Defaults to [0].  Raise

                            :class:`ProcessExecutionError` unless

                            program exits with one of these code.

    :type check_exit_code:  boolean, int, or [int]

    :param delay_on_retry:  True | False. Defaults to True. If set to True,

                            wait a short amount of time before retrying.

    :type delay_on_retry:   boolean

    :param attempts:        How many times to retry cmd.

    :type attempts:         int

    :param run_as_root:     True | False. Defaults to False. If set to True,

                            the command is prefixed by the command specified

                            in the root_helper kwarg.

    :type run_as_root:      boolean

    :param root_helper:     command to prefix to commands called with

                            run_as_root=True

    :type root_helper:      string

    :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

    :raises:                :class:`ProcessExecutionError`

    """

    process_input = kwargs.pop('process_input', None)

    check_exit_code = kwargs.pop('check_exit_code', [0])

    ignore_exit_code = False

    delay_on_retry = kwargs.pop('delay_on_retry', True)

    attempts = kwargs.pop('attempts', 1)

    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

        check_exit_code = [0]

    elif isinstance(check_exit_code, int):

        check_exit_code = [check_exit_code]

    if kwargs:

        raise UnknownArgumentError(_('Got unknown keyword args '

                                     'to utils.execute: %r') % kwargs)

    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.'))

        cmd = shlex.split(root_helper) + list(cmd)

    cmd = map(str, cmd)

    while attempts > 0:

        attempts -= 1

        try:

            LOG.log(loglevel, _('Running cmd (subprocess): %s'), ' '.join(cmd))

            _PIPE = subprocess.PIPE  # pylint: disable=E1101

            if os.name == 'nt':

                preexec_fn = None

                close_fds = False

            else:

                preexec_fn = _subprocess_setup

                close_fds = True

            obj = subprocess.Popen(cmd,

                                   stdin=_PIPE,

                                   stdout=_PIPE,

                                   stderr=_PIPE,

                                   close_fds=close_fds,

                                   preexec_fn=preexec_fn,

                                   shell=shell)

            result = None

            if process_input is not None:

                result = obj.communicate(process_input)

            else:

                result = obj.communicate()

            obj.stdin.close()  # pylint: disable=E1101

            _returncode = obj.returncode  # pylint: disable=E1101

            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.log(loglevel, _('%r failed. Retrying.'), cmd)

                if delay_on_retry:

                    greenthread.sleep(random.randint(20, 200) / 100.0)

        finally:

            # NOTE(termie): this appears to be necessary to let the subprocess

            #               call clean something up in between calls, without

            #               it two execute calls in a row hangs the second one

            greenthread.sleep(0)

def trycmd(*args, **kwargs):

    """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

    command can be considered to have failed.

    :discard_warnings   True | False. Defaults to False. If set to True,

                        then for succeeding commands, stderr is cleared

    """

    discard_warnings = kwargs.pop('discard_warnings', False)

    try:

        out, err = execute(*args, **kwargs)

        failed = False

    except ProcessExecutionError as exn:

        out, err = '', str(exn)

        failed = True

    if not failed and discard_warnings and err:

        # Handle commands that output to stderr but otherwise succeed

        err = ''

    return out, err

def ssh_execute(ssh, cmd, process_input=None,

                addl_env=None, check_exit_code=True):

    LOG.debug(_('Running cmd (SSH): %s'), cmd)

    if addl_env:

        raise InvalidArgumentError(_('Environment not supported over SSH'))

    if process_input:

        # This is (probably) fixable if we need it...

        raise InvalidArgumentError(_('process_input not supported over SSH'))

    stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)

    channel = stdout_stream.channel

    # NOTE(justinsb): This seems suspicious...

    # ...other SSH clients have buffering issues with this approach

    stdout = stdout_stream.read()

    stderr = stderr_stream.read()

    stdin_stream.close()

    exit_status = channel.recv_exit_status()

    # exit_status == -1 if no exit code was returned

    if exit_status != -1:

        LOG.debug(_('Result was %s') % exit_status)

        if check_exit_code and exit_status != 0:

            raise ProcessExecutionError(exit_code=exit_status,

                                        stdout=stdout,

                                        stderr=stderr,

                                        cmd=cmd)

    return (stdout, stderr)

**** CubicPower OpenStack Study ****

def _subprocess_setup():

    # Python installs a SIGPIPE handler by default. This is usually not what

    # non-Python subprocesses expect.

    signal.signal(signal.SIGPIPE, signal.SIG_DFL)

**** CubicPower OpenStack Study ****

def execute(*cmd, **kwargs):

    """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

    :param check_exit_code: Single bool, int, or list of allowed exit

                            codes.  Defaults to [0].  Raise

                            :class:`ProcessExecutionError` unless

                            program exits with one of these code.

    :type check_exit_code:  boolean, int, or [int]

    :param delay_on_retry:  True | False. Defaults to True. If set to True,

                            wait a short amount of time before retrying.

    :type delay_on_retry:   boolean

    :param attempts:        How many times to retry cmd.

    :type attempts:         int

    :param run_as_root:     True | False. Defaults to False. If set to True,

                            the command is prefixed by the command specified

                            in the root_helper kwarg.

    :type run_as_root:      boolean

    :param root_helper:     command to prefix to commands called with

                            run_as_root=True

    :type root_helper:      string

    :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

    :raises:                :class:`ProcessExecutionError`

    """

    process_input = kwargs.pop('process_input', None)

    check_exit_code = kwargs.pop('check_exit_code', [0])

    ignore_exit_code = False

    delay_on_retry = kwargs.pop('delay_on_retry', True)

    attempts = kwargs.pop('attempts', 1)

    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

        check_exit_code = [0]

    elif isinstance(check_exit_code, int):

        check_exit_code = [check_exit_code]

    if kwargs:

        raise UnknownArgumentError(_('Got unknown keyword args '

                                     'to utils.execute: %r') % kwargs)

    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.'))

        cmd = shlex.split(root_helper) + list(cmd)

    cmd = map(str, cmd)

    while attempts > 0:

        attempts -= 1

        try:

            LOG.log(loglevel, _('Running cmd (subprocess): %s'), ' '.join(cmd))

            _PIPE = subprocess.PIPE  # pylint: disable=E1101

            if os.name == 'nt':

                preexec_fn = None

                close_fds = False

            else:

                preexec_fn = _subprocess_setup

                close_fds = True

            obj = subprocess.Popen(cmd,

                                   stdin=_PIPE,

                                   stdout=_PIPE,

                                   stderr=_PIPE,

                                   close_fds=close_fds,

                                   preexec_fn=preexec_fn,

                                   shell=shell)

            result = None

            if process_input is not None:

                result = obj.communicate(process_input)

            else:

                result = obj.communicate()

            obj.stdin.close()  # pylint: disable=E1101

            _returncode = obj.returncode  # pylint: disable=E1101

            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.log(loglevel, _('%r failed. Retrying.'), cmd)

                if delay_on_retry:

                    greenthread.sleep(random.randint(20, 200) / 100.0)

        finally:

            # NOTE(termie): this appears to be necessary to let the subprocess

            #               call clean something up in between calls, without

            #               it two execute calls in a row hangs the second one

            greenthread.sleep(0)

**** CubicPower OpenStack Study ****

def trycmd(*args, **kwargs):

    """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

    command can be considered to have failed.

    :discard_warnings   True | False. Defaults to False. If set to True,

                        then for succeeding commands, stderr is cleared

    """

    discard_warnings = kwargs.pop('discard_warnings', False)

    try:

        out, err = execute(*args, **kwargs)

        failed = False

    except ProcessExecutionError as exn:

        out, err = '', str(exn)

        failed = True

    if not failed and discard_warnings and err:

        # Handle commands that output to stderr but otherwise succeed

        err = ''

    return out, err

**** CubicPower OpenStack Study ****

def ssh_execute(ssh, cmd, process_input=None,

                addl_env=None, check_exit_code=True):

    LOG.debug(_('Running cmd (SSH): %s'), cmd)

    if addl_env:

        raise InvalidArgumentError(_('Environment not supported over SSH'))

    if process_input:

        # This is (probably) fixable if we need it...

        raise InvalidArgumentError(_('process_input not supported over SSH'))

    stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)

    channel = stdout_stream.channel

    # NOTE(justinsb): This seems suspicious...

    # ...other SSH clients have buffering issues with this approach

    stdout = stdout_stream.read()

    stderr = stderr_stream.read()

    stdin_stream.close()

    exit_status = channel.recv_exit_status()

    # exit_status == -1 if no exit code was returned

    if exit_status != -1:

        LOG.debug(_('Result was %s') % exit_status)

        if check_exit_code and exit_status != 0:

            raise ProcessExecutionError(exit_code=exit_status,

                                        stdout=stdout,

                                        stderr=stderr,

                                        cmd=cmd)

    return (stdout, stderr)