# # NEPI, a framework to manage network experiments # Copyright (C) 2013 INRIA # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 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 warranty of # MERCHANTABILITY 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 . # # Author: Alina Quereilhac from nepi.util.sshfuncs import ProcStatus, STDOUT, log, shell_escape import logging import shlex import subprocess from six import PY2 def lexec(command, user = None, sudo = False, env = None): """ Executes a local command, returns ((stdout, stderr), process) """ if env: export = '' for envkey, envval in env.items(): export += "{}={} ".format(envkey, envval) command = "{} {}".format(export, command) if sudo: command = "sudo {}".format(command) # XXX: Doing 'su user' blocks waiting for a password on stdin #elif user: # command = "su {} ; {} ".format(user, command) extras = {} if PY2 else {'universal_newlines' : True} proc = subprocess.Popen(command, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **extras) out = err = "" log_msg = "lexec - command {} ".format(command) try: out, err = proc.communicate() log(log_msg, logging.DEBUG, out, err) except: log(log_msg, logging.ERROR, out, err) raise return ((out, err), proc) def lcopy(source, dest, recursive = False): """ Copies from/to locally. """ args = ["cp"] if recursive: args.append("-r") if isinstance(source, list): args.extend(source) else: args.append(source) if isinstance(dest, list): args.extend(dest) else: args.append(dest) extras = {} if PY2 else {'universal_newlines' : True} proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **extras) out = err = "" command = " ".join(args) log_msg = " lcopy - command {} ".format(command) try: out, err = proc.communicate() log(log_msg, logging.DEBUG, out, err) except: log(log_msg, logging.ERROR, out, err) raise return ((out, err), proc) def lspawn(command, pidfile, stdout = '/dev/null', stderr = STDOUT, stdin = '/dev/null', home = None, create_home = False, sudo = False, user = None): """ Spawn a local command such that it will continue working asynchronously. Parameters: command: the command to run - it should be a single line. pidfile: path of a (ideally unique to this task) pidfile for tracking the process. stdout: path of a file to redirect standard output to - must be a string. Defaults to /dev/null stderr: path of a file to redirect standard error to - string or the special STDOUT value to redirect to the same file stdout was redirected to. Defaults to STDOUT. stdin: path of a file with input to be piped into the command's standard input home: path of a folder to use as working directory - should exist, unless you specify create_home create_home: if True, the home folder will be created first with mkdir -p sudo: whether the command needs to be executed as root Returns: (stdout, stderr), process Of the spawning process, which only captures errors at spawning time. Usually only useful for diagnostics. """ # Start process in a "daemonized" way, using nohup and heavy # stdin/out redirection to avoid connection issues if stderr is STDOUT: stderr = '&1' else: stderr = ' ' + stderr escaped_pidfile = shell_escape(pidfile) daemon_command = '{{ {{ {command} > {stdout} 2>{stderr} < {stdin} & }} ; echo $! 1 > {escaped_pidfile} ; }}'\ .format(**locals()) cmd = "{create}{gohome} rm -f {pidfile} ; {sudo} bash -c {command} "\ .format(command = shell_escape(daemon_command), sudo = 'sudo -S' if sudo else '', pidfile = shell_escape(pidfile), gohome = 'cd {} ; '.format(shell_escape(home)) if home else '', create = 'mkdir -p {} ; '.format(shell_escape(home)) if create_home else '') (out, err), proc = lexec(cmd) if proc.wait(): raise RuntimeError("Failed to set up local application: {} {}" .format(out, err)) return ((out, err), proc) def lgetpid(pidfile): """ Check the pidfile of a process spawned with remote_spawn. Parameters: pidfile: the pidfile passed to remote_span Returns: A (pid, ppid) tuple useful for calling remote_status and remote_kill, or None if the pidfile isn't valid yet (maybe the process is still starting). """ (out, err), proc = lexec("cat {}".format(pidfile)) if proc.wait(): return None if out: try: return [ int(x) for x in out.strip().split(' ', 1) ] except: # Ignore, many ways to fail that don't matter that much return None def lstatus(pid, ppid): """ Check the status of a process spawned with remote_spawn. Parameters: pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid Returns: One of NOT_STARTED, RUNNING, FINISHED """ (out, err), proc = lexec( # Check only by pid. pid+ppid does not always work (especially with sudo) " (( ps --pid {pid} -o pid | grep -c {pid} && echo 'wait') || echo 'done' ) | tail -n 1" .format(ppid = ppid, pid = pid)) if proc.wait(): return ProcStatus.NOT_STARTED status = False if out: status = (out.strip() == 'wait') else: return ProcStatus.NOT_STARTED return ProcStatus.RUNNING if status else ProcStatus.FINISHED def lkill(pid, ppid, sudo = False, nowait = False): """ Kill a process spawned with lspawn. First tries a SIGTERM, and if the process does not end in 10 seconds, it sends a SIGKILL. Parameters: pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid sudo: whether the command was run with sudo - careful killing like this. Returns: Nothing, should have killed the process """ subkill = "$(ps --ppid {pid} -o pid h)".format(pid=pid) cmd_format = """ SUBKILL="{subkill}" ; {sudo} kill -- -{pid} $SUBKILL || /bin/true {sudo} kill {pid} $SUBKILL || /bin/true for x in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 ; do sleep 0.2 if [ `ps --pid {pid} -o pid | grep -c {pid}` == '0' ]; then break else {sudo} kill -- -{pid} $SUBKILL || /bin/true {sudo} kill {pid} $SUBKILL || /bin/true fi sleep 1.8 done if [ `ps --pid {pid} -o pid | grep -c {pid}` != '0' ]; then {sudo} kill -9 -- -{pid} $SUBKILL || /bin/true {sudo} kill -9 {pid} $SUBKILL || /bin/true fi """ if nowait: cmd = "( {} ) >/dev/null 2>/dev/null