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