Merging ns-3 into nepi-3-dev
[nepi.git] / src / nepi / util / execfuncs.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License as published by
7 #    the Free Software Foundation, either version 3 of the License, or
8 #    (at your option) any later version.
9 #
10 #    This program is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #    GNU General Public License for more details.
14 #
15 #    You should have received a copy of the GNU General Public License
16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.util.sshfuncs import ProcStatus, STDOUT
21
22 import subprocess
23
24 def lexec(command, 
25         user = None, 
26         sudo = False,
27         env = None):
28     """
29     Executes a local command, returns ((stdout,stderr),process)
30     """
31     if env:
32         export = ''
33         for envkey, envval in env.iteritems():
34             export += '%s=%s ' % (envkey, envval)
35         command = "%s %s" % (export, command)
36
37     if sudo:
38         command = "sudo %s" % command
39     elif user:
40         command = "su %s ; %s " % (user, command)
41
42
43     proc = subprocess.Popen(command, shell=True, 
44             stdout = subprocess.PIPE, 
45             stderr = subprocess.PIPE)
46
47     out, err = proc.communicate()
48     return ((out, err), proc)
49
50 def lcopy(source, dest, recursive = False):
51     """
52     Copies from/to localy.
53     """
54     
55     if TRACE:
56         print "scp", source, dest
57     
58     command = ["cp"]
59     if recursive:
60         command.append("-R")
61   
62     if isinstance(dest, str):
63         dest = dest.split(";")
64
65     if isinstance(src, str):
66         src = src.split(";")
67            
68     args.extend(src)
69
70     args.extend(dest)
71
72     proc = subprocess.Popen(command, 
73         stdout=subprocess.PIPE, 
74         stderr=subprocess.PIPE)
75
76     out, err = proc.communicate()
77     return ((out, err), proc)
78    
79 def lspawn(command, pidfile, 
80         stdout = '/dev/null', 
81         stderr = STDOUT, 
82         stdin = '/dev/null', 
83         home = None, 
84         create_home = False, 
85         sudo = False,
86         user = None): 
87     """
88     Spawn a local command such that it will continue working asynchronously.
89     
90     Parameters:
91         command: the command to run - it should be a single line.
92         
93         pidfile: path of a (ideally unique to this task) pidfile for tracking the process.
94         
95         stdout: path of a file to redirect standard output to - must be a string.
96             Defaults to /dev/null
97         stderr: path of a file to redirect standard error to - string or the special STDOUT value
98             to redirect to the same file stdout was redirected to. Defaults to STDOUT.
99         stdin: path of a file with input to be piped into the command's standard input
100         
101         home: path of a folder to use as working directory - should exist, unless you specify create_home
102         
103         create_home: if True, the home folder will be created first with mkdir -p
104         
105         sudo: whether the command needs to be executed as root
106         
107     Returns:
108         (stdout, stderr), process
109         
110         Of the spawning process, which only captures errors at spawning time.
111         Usually only useful for diagnostics.
112     """
113     # Start process in a "daemonized" way, using nohup and heavy
114     # stdin/out redirection to avoid connection issues
115     if stderr is STDOUT:
116         stderr = '&1'
117     else:
118         stderr = ' ' + stderr
119     
120     daemon_command = '{ { %(command)s  > %(stdout)s 2>%(stderr)s < %(stdin)s & } ; echo $! 1 > %(pidfile)s ; }' % {
121         'command' : command,
122         'pidfile' : shell_escape(pidfile),
123         'stdout' : stdout,
124         'stderr' : stderr,
125         'stdin' : stdin,
126     }
127     
128     cmd = "%(create)s%(gohome)s rm -f %(pidfile)s ; %(sudo)s nohup bash -c %(command)s " % {
129             'command' : shell_escape(daemon_command),
130             'sudo' : 'sudo -S' if sudo else '',
131             'pidfile' : shell_escape(pidfile),
132             'gohome' : 'cd %s ; ' % (shell_escape(home),) if home else '',
133             'create' : 'mkdir -p %s ; ' % (shell_escape(home),) if create_home else '',
134         }
135
136     (out,err), proc = lexec(cmd)
137     
138     if proc.wait():
139         raise RuntimeError, "Failed to set up application on host %s: %s %s" % (host, out,err,)
140
141     return ((out,err), proc)
142
143 def lgetpid(pidfile):
144     """
145     Check the pidfile of a process spawned with remote_spawn.
146     
147     Parameters:
148         pidfile: the pidfile passed to remote_span
149         
150     Returns:
151         
152         A (pid, ppid) tuple useful for calling remote_status and remote_kill,
153         or None if the pidfile isn't valid yet (maybe the process is still starting).
154     """
155
156     (out,err), proc = lexec("cat %s" % pidfile )
157         
158     if proc.wait():
159         return None
160     
161     if out:
162         try:
163             return map(int,out.strip().split(' ',1))
164         except:
165             # Ignore, many ways to fail that don't matter that much
166             return None
167
168 def lstatus(pid, ppid): 
169     """
170     Check the status of a process spawned with remote_spawn.
171     
172     Parameters:
173         pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid
174         
175     Returns:
176         
177         One of NOT_STARTED, RUNNING, FINISHED
178     """
179
180     (out,err), proc = lexec(
181         # Check only by pid. pid+ppid does not always work (especially with sudo) 
182         " (( ps --pid %(pid)d -o pid | grep -c %(pid)d && echo 'wait')  || echo 'done' ) | tail -n 1" % {
183             'ppid' : ppid,
184             'pid' : pid,
185         })
186     
187     if proc.wait():
188         return ProcStatus.NOT_STARTED
189     
190     status = False
191     if out:
192         status = (out.strip() == 'wait')
193     else:
194         return ProcStatus.NOT_STARTED
195
196     return ProcStatus.RUNNING if status else ProcStatus.FINISHED
197
198 def lkill(pid, ppid, sudo = False):
199     """
200     Kill a process spawned with lspawn.
201     
202     First tries a SIGTERM, and if the process does not end in 10 seconds,
203     it sends a SIGKILL.
204     
205     Parameters:
206         pid/ppid: pid and parent-pid of the spawned process. See remote_check_pid
207         
208         sudo: whether the command was run with sudo - careful killing like this.
209     
210     Returns:
211         
212         Nothing, should have killed the process
213     """
214     
215     subkill = "$(ps --ppid %(pid)d -o pid h)" % { 'pid' : pid }
216     cmd = """
217 SUBKILL="%(subkill)s" ;
218 %(sudo)s kill -- -%(pid)d $SUBKILL || /bin/true
219 %(sudo)s kill %(pid)d $SUBKILL || /bin/true
220 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 
221     sleep 0.2 
222     if [ `ps --pid %(pid)d -o pid | grep -c %(pid)d` == '0' ]; then
223         break
224     else
225         %(sudo)s kill -- -%(pid)d $SUBKILL || /bin/true
226         %(sudo)s kill %(pid)d $SUBKILL || /bin/true
227     fi
228     sleep 1.8
229 done
230 if [ `ps --pid %(pid)d -o pid | grep -c %(pid)d` != '0' ]; then
231     %(sudo)s kill -9 -- -%(pid)d $SUBKILL || /bin/true
232     %(sudo)s kill -9 %(pid)d $SUBKILL || /bin/true
233 fi
234 """
235     if nowait:
236         cmd = "( %s ) >/dev/null 2>/dev/null </dev/null &" % (cmd,)
237
238     (out,err),proc = lexec(
239         cmd % {
240             'ppid' : ppid,
241             'pid' : pid,
242             'sudo' : 'sudo -S' if sudo else '',
243             'subkill' : subkill,
244         })
245     
246