blob: 0ea873d549bcb54ddfcfb7c35e72da9dceef1426 [file] [log] [blame]
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +01001# osmo_gsm_tester: specifics for remote nodes
2#
3# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Pau Espin Pedrol <pespin@sysmocom.de>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# 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
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import stat
21import os
22import re
23import pprint
24
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020025from . import log, util, config, template, process
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010026
27class RemoteHost(log.Origin):
28
29 WRAPPER_SCRIPT = 'ssh_sigkiller.sh'
30
Nils Fürste2af2b152020-11-23 11:57:41 +010031 def __init__(self, run_dir, remote_user = 'root', remote_host = 'localhost', remote_cwd=None, remote_port=None):
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010032 super().__init__(log.C_RUN, 'host-' + remote_user + '@' + remote_host)
33 self.run_dir = util.Dir(run_dir.new_dir(self.name()))
34 self.remote_user = remote_user
35 self.remote_host = remote_host
36 self.remote_cwd = remote_cwd
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +010037 self.remote_env = {}
Nils Fürste2af2b152020-11-23 11:57:41 +010038 self.remote_port = remote_port
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010039
40 def user(self):
41 return self.remote_user
42
43 def host(self):
44 return self.remote_host
45
46 def cwd(self):
47 return self.remote_cwd
48
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +010049 def set_remote_env(self, remote_env_dict):
50 self.remote_env = remote_env_dict
51
52 def get_remote_env(self):
53 return self.remote_env
54
Nils Fürste2af2b152020-11-23 11:57:41 +010055 def get_remote_port(self):
56 return self.remote_port
57
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +010058 def RemoteProcess(self, name, popen_args, remote_env={}, **popen_kwargs):
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010059 run_dir = self.run_dir.new_dir(name)
Nils Fürste2af2b152020-11-23 11:57:41 +010060 return process.RemoteProcess(name, run_dir, self.user(), self.host(), self.cwd(), popen_args,
61 remote_env=remote_env, remote_port=self.get_remote_port(), **popen_kwargs)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010062
Pau Espin Pedrol0d64f162020-05-25 15:44:09 +020063 def generate_wrapper_script(self, wait_time_sec):
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010064 wrapper_script = self.run_dir.new_file(RemoteHost.WRAPPER_SCRIPT)
65 with open(wrapper_script, 'w') as f:
66 r = """#!/bin/bash
Pau Espin Pedrola5f7bef2020-03-05 17:25:18 +010067 LOGFILE=/dev/null
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +010068 kill_pid(){
69 mypid=$1
70 kill $mypid
71 if ! kill -0 $mypid; then
72 return
73 fi
Pau Espin Pedrol0d64f162020-05-25 15:44:09 +020074 echo "sleeping %d seconds waiting for child to die..." >>$LOGFILE
75 sleep %d
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +010076 if ! kill -0 $mypid; then
77 return
78 fi
79 echo "kill -9 the process and wait!" >>$LOGFILE
80 kill -9 $mypid
81 wait $mypid
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010082 }
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +010083 prep_sighandler() {
84 unset term_child_pid
85 unset term_kill_needed
86 trap 'sign_handler SIGTERM' SIGTERM
87 trap 'sign_handler SIGINT' SIGINT
88 trap 'sign_handler SIGHUP' SIGHUP
89 echo "script started, traps set" >$LOGFILE
90 }
91 sign_handler() {
92 sig=$1
93 echo "$sig -> ${term_child_pid}" >>$LOGFILE
94 echo "received signal handler $sig, killing ${term_child_pid}" >>$LOGFILE
95 kill_pid ${term_child_pid}
96 }
97 wait_sighandler()
98 {
99 term_child_pid=$!
100 if [ "${term_kill_needed}" ]; then
101 kill_pid "${term_child_pid}"
102 fi
103 echo "waiting for ${term_child_pid}" >>$LOGFILE
104 wait ${term_child_pid}
105 echo "process ${term_child_pid} finished" >>$LOGFILE
106 }
107 prep_sighandler
Pau Espin Pedrol2e495742020-05-27 12:08:19 +0200108 $@ <&0 &
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +0100109 wait_sighandler
Pau Espin Pedrol0d64f162020-05-25 15:44:09 +0200110 """ % (wait_time_sec, wait_time_sec)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100111 f.write(r)
112 st = os.stat(wrapper_script)
113 os.chmod(wrapper_script, st.st_mode | stat.S_IEXEC)
114 return wrapper_script
115
Pau Espin Pedrol0d64f162020-05-25 15:44:09 +0200116 def RemoteProcessSafeExit(self, name, remote_dir, popen_args, remote_env={}, wait_time_sec=5, **popen_kwargs):
117 """Run binary under a wrapper which will make sure process is killed -9
118 a few seconds after SIGHUP from SSH is received."""
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100119 self.create_remote_dir(remote_dir)
120
Pau Espin Pedrol0d64f162020-05-25 15:44:09 +0200121 wrapper_script = self.generate_wrapper_script(wait_time_sec)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100122 remote_wrapper_script = remote_dir.child(RemoteHost.WRAPPER_SCRIPT)
123 self.scp('scp-wrapper-to-remote', wrapper_script, remote_wrapper_script)
124
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100125 args = (remote_wrapper_script,) + popen_args
126 return self.RemoteProcess(name, args, remote_env, **popen_kwargs)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100127
Pau Espin Pedrol14022d32020-02-11 14:20:00 +0100128 def RemoteNetNSProcess(self, name, netns, popen_args, **popen_kwargs):
129 run_dir = self.run_dir.new_dir(name)
Pau Espin Pedrol4983eb52020-02-11 19:16:06 +0100130 return process.RemoteNetNSProcess(name, run_dir, self.user(), self.host(), self.cwd(), netns, popen_args, **popen_kwargs)
Pau Espin Pedrol14022d32020-02-11 14:20:00 +0100131
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100132 def run_remote_sync(self, name, popen_args):
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100133 proc = self.RemoteProcess(name, popen_args, remote_env=self.remote_env)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100134 proc.launch_sync()
135 return proc
136
137 def rm_remote_dir(self, remote_dir):
138 remote_dir_str = str(remote_dir)
139 self.run_remote_sync('rm-remote-dir', ('test', '!', '-d', remote_dir_str, '||', 'rm', '-rf', remote_dir_str))
140
141 def create_remote_dir(self, remote_dir):
142 remote_dir_str = str(remote_dir)
143 self.run_remote_sync('mk-remote-dir', ('mkdir', '-p', remote_dir_str))
144
145 def recreate_remote_dir(self, remote_dir):
146 self.rm_remote_dir(remote_dir)
147 self.create_remote_dir(remote_dir)
148
149 def inst_compatible_for_remote(self):
150 proc = self.run_remote_sync('uname-m', ('uname', '-m'))
151 if "x86_64" in (proc.get_stdout() or ''):
152 return True
153 return False
154
155 def scp(self, name, local_path, remote_path):
156 process.run_local_sync(self.run_dir, name, ('scp', '-r', local_path, '%s@%s:%s' % (self.user(), self.host(), remote_path)))
Pau Espin Pedrol7381e822020-02-11 14:30:42 +0100157
158 def scpfrom(self, name, remote_path, local_path):
159 process.run_local_sync(self.run_dir, name, ('scp', '-r', '%s@%s:%s' % (self.user(), self.host(), remote_path), local_path))
160
161 def setcap_net_admin(self, binary_path):
162 '''
163 This functionality requires specific setup on the host running
164 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
165 '''
166 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
167 self.run_remote_sync('setcap-netadm', ('sudo', SETCAP_NET_ADMIN_BIN, binary_path))
168
169 def setcap_netsys_admin(self, binary_path):
170 '''
171 This functionality requires specific setup on the host running
172 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
173 '''
174 SETCAP_NETSYS_ADMIN_BIN = 'osmo-gsm-tester_setcap_netsys_admin.sh'
175 self.run_remote_sync('setcap-netsysadm', ('sudo', SETCAP_NETSYS_ADMIN_BIN, binary_path))
176
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100177 def create_netns(self, netns):
178 '''
179 It creates the netns if it doesn't already exist.
180 '''
181 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
182 self.run_remote_sync('create_netns', ('sudo', NETNS_SETUP_BIN, netns))
183
Pau Espin Pedrol7381e822020-02-11 14:30:42 +0100184 def change_elf_rpath(self, binary_path, paths):
185 '''
186 Change RPATH field in ELF executable binary.
Pau Espin Pedrol666d6c82020-02-24 10:41:58 +0100187 This feature can be used to tell the loader to load the trial libraries, as
Pau Espin Pedrol7381e822020-02-11 14:30:42 +0100188 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
189 '''
190 patchelf_bin = self.remote_env.get('PATCHELF_BIN', None)
191 if not patchelf_bin:
192 patchelf_bin = 'patchelf'
193 else:
194 self.dbg('Using specific patchelf from %s', patchelf_bin)
195
196 self.run_remote_sync('patchelf', (patchelf_bin, '--set-rpath', paths, binary_path))