Source code for utah.process

# Ubuntu Testing Automation Harness
# Copyright 2012 Canonical Ltd.

# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Process related utilities."""

import errno
import fcntl
import logging
import os
import select
import signal
import StringIO
import subprocess
import sys
import time

import psutil

from utah import logger


[docs]def pid_in_use(pid, arg_patterns=None): """Test if the given pid is running. If arg_patterns is provided then the pid's command line will be compared to make sure it matches an expected value. :returns: proc object if the pid is active and optionally matches one of the arg_patterns, otherwise None :rtype: psutil.Process """ try: proc = psutil.Process(pid) except psutil.error.NoSuchProcess: return None cmdline = ' '.join(proc.cmdline) if arg_patterns: for p in arg_patterns: if p in cmdline: return proc return None return proc
[docs]class ProcessChecker(object): """Check all running process looking for a given pattern.""" MESSAGE_TEMPLATE = ( "{app} application is running.\n" "Please stop it before launching utah since " "it won't interact properly with KVM " "(Kernel-based Virtual Machine)\n")
[docs] def check_cmdline(self, pattern): """Check if the command line of any process matches the given pattern. :param pattern: A fragment of the string that should be included in the command line. :type pattern: str :returns: Whether there's a process command line that matches the given pattern. :rtype: bool """ pids = psutil.get_pid_list() cmdlines = [] for pid in pids: try: cmdlines.append(' '.join(psutil.Process(pid).cmdline)) except psutil.error.NoSuchProcess: pass return any(pattern in cmdline for cmdline in cmdlines)
[docs] def get_error_message(self, app): """Return error message for an incompatible application running.""" return self.MESSAGE_TEMPLATE.format(app=app)
[docs]class ProcessRunner(object): """Run a command in a subprocess and log output properly. :param arglist: Argument list as would be passsed to `subprocess.Popen` :type arglist: list :attribute output: Command output (both stdout and stderr) :attribute returncode: Command return code .. note:: Process is launched when object is instantiated, there isn't any separate `run` method. """ def __init__(self, arglist): logging.debug('Running command: ' + ' '.join(arglist)) logging.debug('Output follows:') p = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = [] while p.poll() is None: line = p.stdout.readline().strip() logging.debug(line) output.append(line) if p.returncode == 0: logging.debug('Return code: {}'.format(p.returncode)) else: logging.warning('Command ({}) failed with return code: {}' .format(' '.join(arglist), p.returncode)) self.output = '\n'.join(output) self.returncode = p.returncode
def _get_process_children(pid): """Get the children processes of a given one. :param pid: Process ID of the parent process :type pid: int :returns: Process ID for the childern processes :rtype: list(int) """ try: pids = subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '--ppid', str(pid)]).split() return [int(process_id) for process_id in pids] except subprocess.CalledProcessError: return [] def _kill(pid, sig): pids = [pid] pids.extend(_get_process_children(pid)) for pid in pids: # process might have died before getting to this line # so wrap to avoid OSError: no such process try: os.kill(pid, sig) except OSError: pass def _handle_fd(buffs, fds, fd, stream): data = os.read(fd, 256) if data: buffs[fd].write(data) if stream: logger.log(data, False) else: os.close(fd) fds.remove(fd) def _rc(pid, timed_out): """Determine the exit code of the process and kill it if timed out. :return: pid's return code or exist status """ if timed_out: _kill(pid, signal.SIGTERM) _kill(pid, signal.SIGKILL) status = os.waitpid(pid, 0) if not os.WIFEXITED(status[1]): rc = status[1] # todo syslog this? else: rc = os.WEXITSTATUS(status[1]) return rc def _drain(pid, stdout, stderr, deadline, stream): buff = { stdout: StringIO.StringIO(), stderr: StringIO.StringIO(), } timed_out = False fds = [stdout, stderr] while len(fds): timeout = 5 # don't just pound the select call if deadline: now = time.time() if now > deadline: timed_out = True break else: remaining = deadline - now if remaining < timeout: timeout = remaining try: readable, _, _ = select.select(fds, [], [], timeout) except select.error as e: if e.args[0] == errno.EINTR: continue raise for fd in readable: _handle_fd(buff, fds, fd, stream) rc = _rc(pid, timed_out) return (rc, buff[stdout].getvalue(), buff[stderr].getvalue(), timed_out) def _set_cloexec_flag(fd, cloexec=True): # this helps ensure the subprocesses will close FD's upon exit # the floodlight runlist is a great way to validate old = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC) def _pipes(): stdout_r, stdout_w = os.pipe() stderr_r, stderr_w = os.pipe() _set_cloexec_flag(stdout_r) _set_cloexec_flag(stdout_w) _set_cloexec_flag(stderr_r) _set_cloexec_flag(stderr_w) return stdout_r, stdout_w, stderr_r, stderr_w
[docs]def run(cmd, cwd, timeout, stream_syslog): """Run a command using fork/exec. subprocess.* methods, don't provide a good way to do the UNIX classic fork/exec/select to get stdout/stderr while a process is executing. This provides a version for UTAH. :returns: rc, timed_out, stdout, stderr :rtype: tuple(int, bool, string, string) """ stdout_r, stdout_w, stderr_r, stderr_w = _pipes() pid = os.fork() if pid == 0: # setup stdout/stderr os.close(stdout_r) os.dup2(stdout_w, 1) os.close(stderr_r) os.dup2(stderr_w, 2) if cwd: os.chdir(cwd) os.execv('/bin/sh', ['/bin/sh', '-c', cmd]) print "ERROR os.execv command" sys.exit(1) os.close(stdout_w) os.close(stderr_w) deadline = 0 if timeout: deadline = time.time() + timeout rc, out, err, timed_out = \ _drain(pid, stdout_r, stderr_r, deadline, stream_syslog) return rc, timed_out, out, err
Read the Docs v: latest
Versions
latest
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.