2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 INRIA
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation;
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19 from nepi.util.sshfuncs import ProcStatus, STDOUT, log, shell_escape
32 Executes a local command, returns ((stdout,stderr),process)
36 for envkey, envval in env.items():
37 export += '%s=%s ' % (envkey, envval)
38 command = "%s %s" % (export, command)
41 command = "sudo %s" % command
43 # XXX: Doing 'su user' blocks waiting for a password on stdin
45 # command = "su %s ; %s " % (user, command)
47 extras = {} if PY2 else {'universal_newlines' : True}
48 proc = subprocess.Popen(command,
50 stdout = subprocess.PIPE,
51 stderr = subprocess.PIPE,
55 log_msg = "lexec - command %s " % command
58 out, err = proc.communicate()
59 log(log_msg, logging.DEBUG, out, err)
61 log(log_msg, logging.ERROR, out, err)
64 return ((out, err), proc)
66 def lcopy(source, dest, recursive = False):
68 Copies from/to localy.
75 if isinstance(source, list):
80 if isinstance(dest, list):
85 extras = {} if PY2 else {'universal_newlines' : True}
86 proc = subprocess.Popen(args,
87 stdout=subprocess.PIPE,
88 stderr=subprocess.PIPE,
92 command = " ".join(args)
93 log_msg = " lcopy - command %s " % command
96 out, err = proc.communicate()
97 log(log_msg, logging.DEBUG, out, err)
99 log(log_msg, logging.ERROR, out, err)
102 return ((out, err), proc)
104 def lspawn(command, pidfile,
105 stdout = '/dev/null',
113 Spawn a local command such that it will continue working asynchronously.
116 command: the command to run - it should be a single line.
118 pidfile: path of a (ideally unique to this task) pidfile for tracking the process.
120 stdout: path of a file to redirect standard output to - must be a string.
121 Defaults to /dev/null
122 stderr: path of a file to redirect standard error to - string or the special STDOUT value
123 to redirect to the same file stdout was redirected to. Defaults to STDOUT.
124 stdin: path of a file with input to be piped into the command's standard input
126 home: path of a folder to use as working directory - should exist, unless you specify create_home
128 create_home: if True, the home folder will be created first with mkdir -p
130 sudo: whether the command needs to be executed as root
133 (stdout, stderr), process
135 Of the spawning process, which only captures errors at spawning time.
136 Usually only useful for diagnostics.
138 # Start process in a "daemonized" way, using nohup and heavy
139 # stdin/out redirection to avoid connection issues
143 stderr = ' ' + stderr
145 daemon_command = '{ { %(command)s > %(stdout)s 2>%(stderr)s < %(stdin)s & } ; echo $! 1 > %(pidfile)s ; }' % {
147 'pidfile' : shell_escape(pidfile),
153 cmd = "%(create)s%(gohome)s rm -f %(pidfile)s ; %(sudo)s bash -c %(command)s " % {
154 'command' : shell_escape(daemon_command),
155 'sudo' : 'sudo -S' if sudo else '',
156 'pidfile' : shell_escape(pidfile),
157 'gohome' : 'cd %s ; ' % (shell_escape(home),) if home else '',
158 'create' : 'mkdir -p %s ; ' % (shell_escape(home),) if create_home else '',
161 (out,err), proc = lexec(cmd)
164 raise RuntimeError("Failed to set up application on host %s: %s %s" % (host, out,err,))
166 return ((out,err), proc)
168 def lgetpid(pidfile):
170 Check the pidfile of a process spawned with remote_spawn.
173 pidfile: the pidfile passed to remote_span
177 A (pid, ppid) tuple useful for calling remote_status and remote_kill,
178 or None if the pidfile isn't valid yet (maybe the process is still starting).
181 (out,err), proc = lexec("cat %s" % pidfile )
188 return [ int(x) for x in out.strip().split(' ', 1) ]
190 # Ignore, many ways to fail that don't matter that much
193 def lstatus(pid, ppid):
195 Check the status of a process spawned with remote_spawn.
198 pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid
202 One of NOT_STARTED, RUNNING, FINISHED
205 (out,err), proc = lexec(
206 # Check only by pid. pid+ppid does not always work (especially with sudo)
207 " (( ps --pid %(pid)d -o pid | grep -c %(pid)d && echo 'wait') || echo 'done' ) | tail -n 1" % {
213 return ProcStatus.NOT_STARTED
217 status = (out.strip() == 'wait')
219 return ProcStatus.NOT_STARTED
221 return ProcStatus.RUNNING if status else ProcStatus.FINISHED
223 def lkill(pid, ppid, sudo = False, nowait = False):
225 Kill a process spawned with lspawn.
227 First tries a SIGTERM, and if the process does not end in 10 seconds,
231 pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid
233 sudo: whether the command was run with sudo - careful killing like this.
237 Nothing, should have killed the process
240 subkill = "$(ps --ppid %(pid)d -o pid h)" % { 'pid' : pid }
242 SUBKILL="%(subkill)s" ;
243 %(sudo)s kill -- -%(pid)d $SUBKILL || /bin/true
244 %(sudo)s kill %(pid)d $SUBKILL || /bin/true
245 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
247 if [ `ps --pid %(pid)d -o pid | grep -c %(pid)d` == '0' ]; then
250 %(sudo)s kill -- -%(pid)d $SUBKILL || /bin/true
251 %(sudo)s kill %(pid)d $SUBKILL || /bin/true
255 if [ `ps --pid %(pid)d -o pid | grep -c %(pid)d` != '0' ]; then
256 %(sudo)s kill -9 -- -%(pid)d $SUBKILL || /bin/true
257 %(sudo)s kill -9 %(pid)d $SUBKILL || /bin/true
261 cmd = "( %s ) >/dev/null 2>/dev/null </dev/null &" % (cmd,)
263 (out,err),proc = lexec(
267 'sudo' : 'sudo -S' if sudo else '',