blob: d24bf3dc7af8be9924e202a2e5f4a36defeda4c9 [file] [log] [blame]
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02001# osmo_gsm_tester: process management
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02009# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Neels Hofmeyr3531a192017-03-28 14:30:28 +020020import os
21import time
22import subprocess
23import signal
24
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020025from . import log, test, event_loop
Neels Hofmeyr3531a192017-03-28 14:30:28 +020026from .util import Dir
27
28class Process(log.Origin):
29
30 process_obj = None
31 outputs = None
32 result = None
33 killed = None
34
35 def __init__(self, name, run_dir, popen_args, **popen_kwargs):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020036 super().__init__(log.C_RUN, name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +020037 self.name_str = name
Neels Hofmeyr3531a192017-03-28 14:30:28 +020038 self.run_dir = run_dir
39 self.popen_args = popen_args
40 self.popen_kwargs = popen_kwargs
41 self.outputs = {}
42 if not isinstance(self.run_dir, Dir):
43 self.run_dir = Dir(os.path.abspath(str(self.run_dir)))
44
45 def set_env(self, key, value):
46 env = self.popen_kwargs.get('env') or {}
47 env[key] = value
48 self.popen_kwargs['env'] = env
49
50 def make_output_log(self, name):
51 '''
52 create a non-existing log output file in run_dir to pipe stdout and
53 stderr from this process to.
54 '''
55 path = self.run_dir.new_child(name)
56 f = open(path, 'w')
57 self.dbg(path)
58 f.write('(launched: %s)\n' % time.strftime(log.LONG_DATEFMT))
59 f.flush()
60 self.outputs[name] = (path, f)
61 return f
62
63 def launch(self):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020064 log.dbg('cd %r; %s %s' % (
65 os.path.abspath(str(self.run_dir)),
66 ' '.join(['%s=%r'%(k,v) for k,v in self.popen_kwargs.get('env', {}).items()]),
67 ' '.join(self.popen_args)))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020068
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020069 self.process_obj = subprocess.Popen(
70 self.popen_args,
71 stdout=self.make_output_log('stdout'),
72 stderr=self.make_output_log('stderr'),
73 stdin=subprocess.PIPE,
74 shell=False,
75 cwd=self.run_dir.path,
76 **self.popen_kwargs)
77 self.set_name(self.name_str, pid=self.process_obj.pid)
78 self.log('Launched')
Neels Hofmeyr3531a192017-03-28 14:30:28 +020079
80 def _poll_termination(self, time_to_wait_for_term=5):
81 wait_step = 0.001
82 waited_time = 0
83 while True:
84 # poll returns None if proc is still running
85 self.result = self.process_obj.poll()
86 if self.result is not None:
87 return True
88 waited_time += wait_step
89 # make wait_step approach 1.0
90 wait_step = (1. + 5. * wait_step) / 6.
91 if waited_time >= time_to_wait_for_term:
92 break
93 time.sleep(wait_step)
94 return False
95
96 def terminate(self):
97 if self.process_obj is None:
98 return
99 if self.result is not None:
100 return
101
102 while True:
103 # first try SIGINT to allow stdout+stderr flushing
104 self.log('Terminating (SIGINT)')
105 os.kill(self.process_obj.pid, signal.SIGINT)
106 self.killed = signal.SIGINT
107 if self._poll_termination():
108 break
109
110 # SIGTERM maybe?
111 self.log('Terminating (SIGTERM)')
112 self.process_obj.terminate()
113 self.killed = signal.SIGTERM
114 if self._poll_termination():
115 break
116
117 # out of patience
118 self.log('Terminating (SIGKILL)')
119 self.process_obj.kill()
120 self.killed = signal.SIGKILL
121 break;
122
123 self.process_obj.wait()
124 self.cleanup()
125
126 def cleanup(self):
127 self.close_output_logs()
128 if self.result == 0:
129 self.log('Terminated: ok', rc=self.result)
130 elif self.killed:
131 self.log('Terminated', rc=self.result)
132 else:
133 self.err('Terminated: ERROR', rc=self.result)
Neels Hofmeyr85eb3242017-04-09 22:01:16 +0200134 #self.log_stdout_tail()
135 self.log_stderr_tail()
136
137 def log_stdout_tail(self):
138 m = self.get_stdout_tail(prefix='| ')
139 if not m:
140 return
141 self.log('stdout:\n', m, '\n')
142
143 def log_stderr_tail(self):
144 m = self.get_stderr_tail(prefix='| ')
145 if not m:
146 return
147 self.log('stderr:\n', m, '\n')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200148
149 def close_output_logs(self):
150 self.dbg('Cleanup')
151 for k, v in self.outputs.items():
152 path, f = v
153 if f:
154 f.flush()
155 f.close()
156 self.outputs[k] = (path, None)
157
158 def poll(self):
159 if self.process_obj is None:
160 return
161 if self.result is not None:
162 return
163 self.result = self.process_obj.poll()
164 if self.result is not None:
165 self.cleanup()
166
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200167 def is_running(self, poll_first=True):
168 if poll_first:
169 self.poll()
Neels Hofmeyr85eb3242017-04-09 22:01:16 +0200170 return self.process_obj is not None and self.result is None
171
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200172 def get_output(self, which):
173 v = self.outputs.get(which)
174 if not v:
175 return None
176 path, f = v
177 with open(path, 'r') as f2:
178 return f2.read()
179
180 def get_output_tail(self, which, tail=10, prefix=''):
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200181 out = self.get_output(which)
182 if not out:
183 return None
184 out = out.splitlines()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200185 tail = min(len(out), tail)
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200186 return prefix + ('\n' + prefix).join(out[-tail:])
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200187
188 def get_stdout(self):
189 return self.get_output('stdout')
190
191 def get_stderr(self):
192 return self.get_output('stderr')
193
194 def get_stdout_tail(self, tail=10, prefix=''):
195 return self.get_output_tail('stdout', tail, prefix)
196
197 def get_stderr_tail(self, tail=10, prefix=''):
198 return self.get_output_tail('stderr', tail, prefix)
199
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200200 def terminated(self, poll_first=True):
201 if poll_first:
202 self.poll()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200203 return self.result is not None
204
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200205 def wait(self, timeout=300):
Pau Espin Pedrol927344b2017-05-22 16:38:49 +0200206 event_loop.wait(self, self.terminated, timeout=timeout)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200207
208
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200209class RemoteProcess(Process):
210
Pau Espin Pedrol3895fec2017-04-28 16:13:03 +0200211 def __init__(self, name, run_dir, remote_user, remote_host, remote_cwd, popen_args, **popen_kwargs):
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200212 super().__init__(name, run_dir, popen_args, **popen_kwargs)
Pau Espin Pedrol3895fec2017-04-28 16:13:03 +0200213 self.remote_user = remote_user
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200214 self.remote_host = remote_host
215 self.remote_cwd = remote_cwd
216
217 # hacky: instead of just prepending ssh, i.e. piping stdout and stderr
218 # over the ssh link, we should probably run on the remote side,
219 # monitoring the process remotely.
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200220 if self.remote_cwd:
221 cd = 'cd "%s"; ' % self.remote_cwd
222 else:
223 cd = ''
Pau Espin Pedrol3895fec2017-04-28 16:13:03 +0200224 self.popen_args = ['ssh', self.remote_user+'@'+self.remote_host,
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200225 '%s%s' % (cd,
226 ' '.join(self.popen_args))]
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200227 self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200228
229# vim: expandtab tabstop=4 shiftwidth=4