blob: e305c64b274ae8244f542e4782f75cbd718b853b [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
Holger Hans Peter Freyther20b52c12019-02-27 02:31:50 +000024from abc import ABCMeta, abstractmethod
Pau Espin Pedrol0d8deec2017-06-23 11:43:38 +020025from datetime import datetime
Neels Hofmeyrfc383932020-11-30 22:04:11 +010026import re
Neels Hofmeyr3531a192017-03-28 14:30:28 +020027
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020028from . import log
29from .event_loop import MainLoop
Neels Hofmeyr3531a192017-03-28 14:30:28 +020030from .util import Dir
31
Holger Hans Peter Freyther20b52c12019-02-27 02:31:50 +000032class TerminationStrategy(log.Origin, metaclass=ABCMeta):
33 """A baseclass for terminating a collection of processes."""
34
35 def __init__(self):
36 self._processes = []
37
38 def add_process(self, process):
39 """Remembers a process that needs to be terminated."""
40 self._processes.append(process)
41
42 @abstractmethod
43 def terminate_all(self):
44 "Terminates all scheduled processes and waits for the termination."""
45 pass
46
47
48class ParallelTerminationStrategy(TerminationStrategy):
49 """Processes will be terminated in parallel."""
50
Holger Hans Peter Freyther54b4fa92019-02-27 13:00:33 +000051 def _prune_dead_processes(self, poll_first):
52 """Removes all dead processes from the list."""
53 # Remove all processes that terminated!
54 self._processes = list(filter(lambda proc: proc.is_running(poll_first), self._processes))
55
56 def _build_process_map(self):
57 """Builds a mapping from pid to process."""
58 self._process_map = {}
Holger Hans Peter Freyther20b52c12019-02-27 02:31:50 +000059 for process in self._processes:
Holger Hans Peter Freyther54b4fa92019-02-27 13:00:33 +000060 pid = process.pid()
61 if pid is None:
62 continue
63 self._process_map[pid] = process
64
65 def _poll_once(self):
66 """Polls for to be collected children once."""
67 pid, result = os.waitpid(0, os.WNOHANG)
68 # Did some other process die?
69 if pid == 0:
70 return False
71 proc = self._process_map.get(pid)
72 if proc is None:
73 self.dbg("Unknown process with pid(%d) died." % pid)
74 return False
75 # Update the process state and forget about it
76 self.log("PID %d died..." % pid)
77 proc.result = result
78 proc.cleanup()
79 self._processes.remove(proc)
80 del self._process_map[pid]
81 return True
82
83 def _poll_for_termination(self, time_to_wait_for_term=5):
84 """Waits for the termination of processes until timeout|all ended."""
85
86 wait_step = 0.001
87 waited_time = 0
88 while len(self._processes) > 0:
89 # Collect processes until there are none to be collected.
90 while True:
91 try:
92 if not self._poll_once():
93 break
94 except ChildProcessError:
95 break
96
97 # All processes died and we can return before sleeping
98 if len(self._processes) == 0:
99 break
100 waited_time += wait_step
101 # make wait_step approach 1.0
102 wait_step = (1. + 5. * wait_step) / 6.
103 if waited_time >= time_to_wait_for_term:
104 break
105 time.sleep(wait_step)
106
107 def terminate_all(self):
Pau Espin Pedrol04096552019-04-04 16:43:12 +0200108 num_processes = len(self._processes)
109 self.dbg("Scheduled to terminate %d processes." % num_processes)
110 if num_processes == 0:
111 return
Holger Hans Peter Freyther54b4fa92019-02-27 13:00:33 +0000112 self._prune_dead_processes(True)
113 self._build_process_map()
114
115 # Iterate through all signals.
116 for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGKILL]:
117 self.dbg("Starting to kill with %s" % sig.name)
118 for process in self._processes:
119 process.kill(sig)
120 if sig == signal.SIGKILL:
121 continue
122 self._poll_for_termination()
Pau Espin Pedrol04096552019-04-04 16:43:12 +0200123 if len(self._processes) == 0:
124 return
Holger Hans Peter Freyther20b52c12019-02-27 02:31:50 +0000125
126
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200127class Process(log.Origin):
128
Pau Espin Pedrola9bc93d2020-06-12 15:34:28 +0200129 DEFAULT_WAIT_TIMEOUT = 300 # seconds
130
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200131 def __init__(self, name, run_dir, popen_args, **popen_kwargs):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200132 super().__init__(log.C_RUN, name)
Pau Espin Pedrol58603672018-08-09 13:45:55 +0200133 self.process_obj = None
134 self.result = None
135 self.killed = None
Pau Espin Pedrola9bc93d2020-06-12 15:34:28 +0200136 self.default_wait_timeout = Process.DEFAULT_WAIT_TIMEOUT
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200137 self.name_str = name
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200138 self.run_dir = run_dir
139 self.popen_args = popen_args
140 self.popen_kwargs = popen_kwargs
141 self.outputs = {}
142 if not isinstance(self.run_dir, Dir):
143 self.run_dir = Dir(os.path.abspath(str(self.run_dir)))
144
145 def set_env(self, key, value):
146 env = self.popen_kwargs.get('env') or {}
147 env[key] = value
148 self.popen_kwargs['env'] = env
149
Pau Espin Pedrola9bc93d2020-06-12 15:34:28 +0200150 def set_default_wait_timeout(self, timeout):
151 assert timeout
152 self.default_wait_timeout = timeout
153
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200154 def make_output_log(self, name):
155 '''
156 create a non-existing log output file in run_dir to pipe stdout and
157 stderr from this process to.
158 '''
159 path = self.run_dir.new_child(name)
160 f = open(path, 'w')
161 self.dbg(path)
Pau Espin Pedrol0d8deec2017-06-23 11:43:38 +0200162 f.write('(launched: %s)\n' % datetime.now().strftime(log.LONG_DATEFMT))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200163 f.flush()
164 self.outputs[name] = (path, f)
165 return f
166
Andre Puschmannf249a022021-01-05 14:14:48 +0100167 def get_counter_stdout(self, keyword):
168 # Match stdout against keyword
169 n = 0
170 stdout_lines = (self.get_stdout() or '').splitlines()
171 for l in stdout_lines:
172 if keyword in l:
173 n += 1
174 return n
175
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200176 def launch(self):
Pau Espin Pedrol3a479c22019-04-05 19:47:40 +0200177 preexec_fn = None
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200178 log.dbg('cd %r; %s %s' % (
179 os.path.abspath(str(self.run_dir)),
180 ' '.join(['%s=%r'%(k,v) for k,v in self.popen_kwargs.get('env', {}).items()]),
181 ' '.join(self.popen_args)))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200182
Pau Espin Pedrol3a479c22019-04-05 19:47:40 +0200183 if self.popen_args[0] == "sudo":
184 # sudo drops forwarding of signals sent by processes of the same
185 # process group, which means by default will drop signals from
186 # parent and children processes. By moving it to another group, we
187 # will later be able to kill it.
188 # Note: sudo documentation is wrong, since it states it only drops
189 # signals from children.
190 preexec_fn = os.setpgrp
191
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200192 self.process_obj = subprocess.Popen(
193 self.popen_args,
194 stdout=self.make_output_log('stdout'),
195 stderr=self.make_output_log('stderr'),
196 stdin=subprocess.PIPE,
Pau Espin Pedrol3a479c22019-04-05 19:47:40 +0200197 preexec_fn=preexec_fn,
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200198 shell=False,
199 cwd=self.run_dir.path,
200 **self.popen_kwargs)
201 self.set_name(self.name_str, pid=self.process_obj.pid)
202 self.log('Launched')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200203
Pau Espin Pedrol78087be2018-11-12 18:20:52 +0100204 def launch_sync(self, raise_nonsuccess=True):
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100205 '''
206 calls launch() method and block waiting for it to finish, serving the
207 mainloop meanwhile.
208 '''
209 try:
210 self.launch()
211 self.wait()
212 except Exception as e:
213 self.terminate()
214 raise e
Pau Espin Pedrol78087be2018-11-12 18:20:52 +0100215 if raise_nonsuccess and self.result != 0:
Pau Espin Pedrol7e30d842020-06-04 16:23:46 +0200216 raise self.RunError('launch_sync()')
Pau Espin Pedrol78087be2018-11-12 18:20:52 +0100217 return self.result
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100218
Pau Espin Pedrolb1526b92018-05-22 20:32:30 +0200219 def respawn(self):
220 self.dbg('respawn')
221 assert not self.is_running()
222 self.result = None
223 self.killed = None
Pau Espin Pedrol922ce5a2019-09-10 13:44:20 +0200224 return self.launch()
Pau Espin Pedrolb1526b92018-05-22 20:32:30 +0200225
Pau Espin Pedroldd7bb2c2019-09-10 13:44:39 +0200226 def respawn_sync(self, raise_nonsuccess=True):
227 self.dbg('respawn_sync')
228 assert not self.is_running()
229 self.result = None
230 self.killed = None
231 return self.launch_sync(raise_nonsuccess)
232
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200233 def _poll_termination(self, time_to_wait_for_term=5):
234 wait_step = 0.001
235 waited_time = 0
236 while True:
237 # poll returns None if proc is still running
238 self.result = self.process_obj.poll()
239 if self.result is not None:
240 return True
241 waited_time += wait_step
242 # make wait_step approach 1.0
243 wait_step = (1. + 5. * wait_step) / 6.
244 if waited_time >= time_to_wait_for_term:
245 break
246 time.sleep(wait_step)
247 return False
248
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200249 def send_signal(self, sig):
250 os.kill(self.process_obj.pid, sig)
251
Holger Hans Peter Freyther54b4fa92019-02-27 13:00:33 +0000252 def pid(self):
253 if self.process_obj is None:
254 return None
255 return self.process_obj.pid
256
Holger Hans Peter Freyther0d714c92019-02-27 09:50:52 +0000257 def kill(self, sig):
258 """Kills the process with the given signal and remembers it."""
259 self.log('Terminating (%s)' % sig.name)
260 self.send_signal(sig)
261 self.killed = sig
262
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200263 def terminate(self):
264 if self.process_obj is None:
265 return
266 if self.result is not None:
267 return
268
269 while True:
270 # first try SIGINT to allow stdout+stderr flushing
Holger Hans Peter Freyther0d714c92019-02-27 09:50:52 +0000271 self.kill(signal.SIGINT)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200272 if self._poll_termination():
273 break
274
275 # SIGTERM maybe?
Holger Hans Peter Freyther0d714c92019-02-27 09:50:52 +0000276 self.kill(signal.SIGTERM)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200277 if self._poll_termination():
278 break
279
280 # out of patience
Holger Hans Peter Freyther0d714c92019-02-27 09:50:52 +0000281 self.kill(signal.SIGKILL)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200282 break;
283
284 self.process_obj.wait()
285 self.cleanup()
286
287 def cleanup(self):
Pau Espin Pedrol06ada452018-05-22 19:20:41 +0200288 self.dbg('Cleanup')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200289 self.close_output_logs()
290 if self.result == 0:
291 self.log('Terminated: ok', rc=self.result)
292 elif self.killed:
293 self.log('Terminated', rc=self.result)
294 else:
295 self.err('Terminated: ERROR', rc=self.result)
Pau Espin Pedrol9dbdb622020-05-25 16:45:34 +0200296 self.log_stdout_tail()
Neels Hofmeyr85eb3242017-04-09 22:01:16 +0200297 self.log_stderr_tail()
298
299 def log_stdout_tail(self):
300 m = self.get_stdout_tail(prefix='| ')
301 if not m:
302 return
Neels Hofmeyre5e5df82020-12-02 05:12:15 +0100303 self.log('stdout:', '\n' + m, '\n')
Neels Hofmeyr85eb3242017-04-09 22:01:16 +0200304
305 def log_stderr_tail(self):
306 m = self.get_stderr_tail(prefix='| ')
307 if not m:
308 return
Neels Hofmeyre5e5df82020-12-02 05:12:15 +0100309 self.log('stderr:', '\n' + m, '\n')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200310
311 def close_output_logs(self):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200312 for k, v in self.outputs.items():
313 path, f = v
314 if f:
315 f.flush()
316 f.close()
317 self.outputs[k] = (path, None)
318
319 def poll(self):
320 if self.process_obj is None:
321 return
322 if self.result is not None:
323 return
324 self.result = self.process_obj.poll()
325 if self.result is not None:
326 self.cleanup()
327
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200328 def is_running(self, poll_first=True):
329 if poll_first:
330 self.poll()
Neels Hofmeyr85eb3242017-04-09 22:01:16 +0200331 return self.process_obj is not None and self.result is None
332
Neels Hofmeyrfc383932020-11-30 22:04:11 +0100333 @staticmethod
334 def end_ansi_colors(txt):
335 '''Make sure no ANSI colors leak out of logging output'''
336 color_off = '\033[0;m'
337 color_any = '\033['
338 if txt.rfind(color_any) > txt.rfind(color_off):
339 return txt + color_off
340 return txt
341
342 def get_output(self, which, since_mark=0):
343 ''' Read process output. For since_mark, see get_output_mark(). '''
Andre Puschmann20087ad2020-06-19 15:44:34 +0200344 path = self.get_output_file(which)
345 if path is None:
346 return None
Neels Hofmeyrfc383932020-11-30 22:04:11 +0100347 with open(path, 'r') as f:
348 if since_mark > 0:
349 f.seek(since_mark)
Neels Hofmeyrfb8c02f2020-12-06 16:25:51 +0100350 return self.end_ansi_colors(f.read())
Andre Puschmann20087ad2020-06-19 15:44:34 +0200351
352 def get_output_file(self, which):
353 ''' Return filename for given output '''
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200354 v = self.outputs.get(which)
355 if not v:
356 return None
357 path, f = v
Andre Puschmann20087ad2020-06-19 15:44:34 +0200358 return path
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200359
360 def get_output_tail(self, which, tail=10, prefix=''):
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200361 out = self.get_output(which)
362 if not out:
363 return None
364 out = out.splitlines()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200365 tail = min(len(out), tail)
Neels Hofmeyrfb8c02f2020-12-06 16:25:51 +0100366 return prefix + self.end_ansi_colors(('\n' + prefix).join(out[-tail:]))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200367
Neels Hofmeyrfc383932020-11-30 22:04:11 +0100368 def get_output_mark(self, which):
369 '''Usage:
370 # remember a start marker
371 my_mark = my_process.get_output_mark('stderr')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200372
Neels Hofmeyrfc383932020-11-30 22:04:11 +0100373 do_actions_that_produce_log_output()
374
375 my_log = my_process.get_output('stderr', since_mark=my_mark)
376 # my_log contains the stderr of that process since the start marker.
377 '''
378 path = self.get_output_file(which)
379 if path is None:
380 return None
381 with open(path, 'r') as f:
382 return f.seek(0, 2)
383
384 def grep_output(self, which, regex, since_mark=0, line_nrs=False):
385 lines = self.get_output(which, since_mark=since_mark).splitlines()
386 if not lines:
387 return None
388 matches = []
389 r = re.compile(regex)
390 line_nr = since_mark
391 for line in lines:
392 line_nr += 1
393 if r.search(line):
394 line = self.end_ansi_colors(line)
395 if line_nrs:
396 matches.append((line_nr, line))
397 else:
398 matches.append(line)
399 return matches
400
401 def get_stdout(self, since_mark=0):
402 return self.get_output('stdout', since_mark=since_mark)
403
404 def get_stderr(self, since_mark=0):
405 return self.get_output('stderr', since_mark=since_mark)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200406
407 def get_stdout_tail(self, tail=10, prefix=''):
408 return self.get_output_tail('stdout', tail, prefix)
409
410 def get_stderr_tail(self, tail=10, prefix=''):
411 return self.get_output_tail('stderr', tail, prefix)
412
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200413 def terminated(self, poll_first=True):
414 if poll_first:
415 self.poll()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200416 return self.result is not None
417
Pau Espin Pedrola9bc93d2020-06-12 15:34:28 +0200418 def wait(self, timeout=None):
419 if timeout is None:
420 timeout = self.default_wait_timeout
Pau Espin Pedrol664e3832020-06-10 19:30:33 +0200421 MainLoop.wait(self.terminated, timeout=timeout)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200422
Andre Puschmann66272f82020-03-21 21:01:31 +0100423 def stdin_write(self, cmd):
424 '''
425 Send a cmd to the stdin of a process (convert to byte before)
426 '''
427 if self.process_obj.stdin is not None:
428 self.process_obj.stdin.write(cmd.encode("utf-8"))
429 self.process_obj.stdin.flush()
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200430
Pau Espin Pedrol7e30d842020-06-04 16:23:46 +0200431 def RunError(self, msg_prefix):
432 'Get a log.Error filled in with Result information. Use when program is terminated and result !=0'
433 msg = '%s: local process exited with status %d' % (msg_prefix, self.result)
434 return log.Error(msg)
435
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200436class RemoteProcess(Process):
437
Nils Fürste2af2b152020-11-23 11:57:41 +0100438 def __init__(self, name, run_dir, remote_user, remote_host, remote_cwd, popen_args,
439 remote_env={}, remote_port=None, **popen_kwargs):
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200440 super().__init__(name, run_dir, popen_args, **popen_kwargs)
Pau Espin Pedrol3895fec2017-04-28 16:13:03 +0200441 self.remote_user = remote_user
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200442 self.remote_host = remote_host
443 self.remote_cwd = remote_cwd
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100444 self.remote_env = remote_env
Nils Fürste2af2b152020-11-23 11:57:41 +0100445 self.remote_port = remote_port
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200446
447 # hacky: instead of just prepending ssh, i.e. piping stdout and stderr
448 # over the ssh link, we should probably run on the remote side,
449 # monitoring the process remotely.
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200450 if self.remote_cwd:
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100451 cd = 'cd "%s";' % self.remote_cwd
Neels Hofmeyr5356d0a2017-04-10 03:45:30 +0200452 else:
453 cd = ''
Pau Espin Pedrol302c7562018-10-02 13:08:02 +0200454 # We need double -t to force tty and be able to forward signals to
455 # processes (SIGHUP) when we close ssh on the local side. As a result,
456 # stderr seems to be merged into stdout in ssh client.
457 self.popen_args = ['ssh', '-t', '-t', self.remote_user+'@'+self.remote_host,
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100458 '%s %s %s' % (cd,
459 ' '.join(['%s=%r'%(k,v) for k,v in self.remote_env.items()]),
460 ' '.join(self.popen_args))]
Nils Fürste2af2b152020-11-23 11:57:41 +0100461 if self.remote_port:
462 self.popen_args.insert(1, '-p')
463 self.popen_args.insert(2, self.remote_port)
464
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100465 self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs, remote_env=self.remote_env)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200466
Pau Espin Pedrol7e30d842020-06-04 16:23:46 +0200467 def RunError(self, msg_prefix):
468 'Overwrite Process method with ssh extra information'
469 # man ssh states it returns 255 if an ssh error occurred:
470 msg = msg_prefix + ': '
471 if self.result == 255:
472 tail = ' (' + (self.get_stderr_tail(tail=1, prefix='') or '').rstrip() + ')'
473 msg += 'local ssh process exited with status %d%s' % (self.result, tail if 'ssh' in tail else '')
474 else:
475 msg += 'remote process exited with status %d' % (self.result)
476 return log.Error(msg)
477
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200478class NetNSProcess(Process):
479 NETNS_EXEC_BIN = 'osmo-gsm-tester_netns_exec.sh'
480 def __init__(self, name, run_dir, netns, popen_args, **popen_kwargs):
481 super().__init__(name, run_dir, popen_args, **popen_kwargs)
482 self.netns = netns
483
484 self.popen_args = ['sudo', self.NETNS_EXEC_BIN, self.netns] + list(popen_args)
485 self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs)
486
487 # HACK: Since we run under sudo, only way to kill root-owned process is to kill as root...
488 # This function is overwritten from Process.
489 def send_signal(self, sig):
Pau Espin Pedrole159cd22019-04-03 17:53:54 +0200490 if sig == signal.SIGKILL:
491 # if we kill sudo, its children (bash running NETNS_EXEC_BIN +
492 # tcpdump under it) are kept alive. Let's instead tell the script to
493 # kill tcpdump:
494 sig = signal.SIGUSR1
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200495 kill_cmd = ('kill', '-%d' % int(sig), str(self.process_obj.pid))
Pau Espin Pedrol17a4ed92019-04-03 17:10:31 +0200496 run_local_netns_sync(self.run_dir, self.name()+"-kill"+str(sig), self.netns, kill_cmd)
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200497
Pau Espin Pedrol14022d32020-02-11 14:20:00 +0100498class RemoteNetNSProcess(RemoteProcess):
499 NETNS_EXEC_BIN = 'osmo-gsm-tester_netns_exec.sh'
500 def __init__(self, name, run_dir, remote_user, remote_host, remote_cwd, netns, popen_args, **popen_kwargs):
Pau Espin Pedrol4983eb52020-02-11 19:16:06 +0100501 self.netns = netns
Pau Espin Pedrol14022d32020-02-11 14:20:00 +0100502 args = ['sudo', self.NETNS_EXEC_BIN, self.netns] + list(popen_args)
503 super().__init__(name, run_dir, remote_user, remote_host, remote_cwd, args, **popen_kwargs)
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200504
Nils Fürstea8263f42020-11-23 14:45:15 +0100505class AdbProcess(Process):
506 def __init__(self, name, run_dir, adb_serial, popen_args, **popen_kwargs):
507 super().__init__(name, run_dir, popen_args, **popen_kwargs)
508 self.adb_serial = adb_serial
509
510 self.popen_args = ['adb', '-s', self.adb_serial, 'exec-out', 'su', '-c'] + list(popen_args)
511 self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs)
512
Pau Espin Pedrole4358a92018-10-01 11:27:55 +0200513def run_local_sync(run_dir, name, popen_args):
514 run_dir =run_dir.new_dir(name)
515 proc = Process(name, run_dir, popen_args)
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100516 proc.launch_sync()
Pau Espin Pedrol12c5ea42019-11-26 14:25:33 +0100517 return proc
Pau Espin Pedrole4358a92018-10-01 11:27:55 +0200518
Pau Espin Pedrolfd4c1442018-10-25 17:37:23 +0200519def run_local_netns_sync(run_dir, name, netns, popen_args):
520 run_dir =run_dir.new_dir(name)
521 proc = NetNSProcess(name, run_dir, netns, popen_args)
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100522 proc.launch_sync()
Pau Espin Pedrol12c5ea42019-11-26 14:25:33 +0100523 return proc
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200524# vim: expandtab tabstop=4 shiftwidth=4