blob: 10ba069e4a4b42062ef2ac9dbff3d12564da923f [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
25from . import log, util, config, template, process, osmo_ctrl, pcap_recorder
26
27class RemoteHost(log.Origin):
28
29 WRAPPER_SCRIPT = 'ssh_sigkiller.sh'
30
31 def __init__(self, run_dir, remote_user = 'root', remote_host = 'localhost', remote_cwd=None):
32 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 = {}
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010038
39 def user(self):
40 return self.remote_user
41
42 def host(self):
43 return self.remote_host
44
45 def cwd(self):
46 return self.remote_cwd
47
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +010048 def set_remote_env(self, remote_env_dict):
49 self.remote_env = remote_env_dict
50
51 def get_remote_env(self):
52 return self.remote_env
53
54 def RemoteProcess(self, name, popen_args, remote_env={}, **popen_kwargs):
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010055 run_dir = self.run_dir.new_dir(name)
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +010056 return process.RemoteProcess(name, run_dir, self.user(), self.host(), self.cwd(), popen_args, remote_env=remote_env, **popen_kwargs)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010057
58 def generate_wrapper_script(self):
59 wrapper_script = self.run_dir.new_file(RemoteHost.WRAPPER_SCRIPT)
60 with open(wrapper_script, 'w') as f:
61 r = """#!/bin/bash
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +010062 LOGFILE=/tmp/yes
63 kill_pid(){
64 mypid=$1
65 kill $mypid
66 if ! kill -0 $mypid; then
67 return
68 fi
69 echo "sleeping some time waiting for child to die..." >>$LOGFILE
70 sleep 5
71 if ! kill -0 $mypid; then
72 return
73 fi
74 echo "kill -9 the process and wait!" >>$LOGFILE
75 kill -9 $mypid
76 wait $mypid
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +010077 }
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +010078 prep_sighandler() {
79 unset term_child_pid
80 unset term_kill_needed
81 trap 'sign_handler SIGTERM' SIGTERM
82 trap 'sign_handler SIGINT' SIGINT
83 trap 'sign_handler SIGHUP' SIGHUP
84 echo "script started, traps set" >$LOGFILE
85 }
86 sign_handler() {
87 sig=$1
88 echo "$sig -> ${term_child_pid}" >>$LOGFILE
89 echo "received signal handler $sig, killing ${term_child_pid}" >>$LOGFILE
90 kill_pid ${term_child_pid}
91 }
92 wait_sighandler()
93 {
94 term_child_pid=$!
95 if [ "${term_kill_needed}" ]; then
96 kill_pid "${term_child_pid}"
97 fi
98 echo "waiting for ${term_child_pid}" >>$LOGFILE
99 wait ${term_child_pid}
100 echo "process ${term_child_pid} finished" >>$LOGFILE
101 }
102 prep_sighandler
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100103 $@ &
Pau Espin Pedrol180adfc2020-02-11 14:22:23 +0100104 wait_sighandler
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100105 """
106 f.write(r)
107 st = os.stat(wrapper_script)
108 os.chmod(wrapper_script, st.st_mode | stat.S_IEXEC)
109 return wrapper_script
110
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100111 def RemoteProcessFixIgnoreSIGHUP(self, name, remote_dir, popen_args, remote_env={}, **popen_kwargs):
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100112 # Run remotely through ssh. We need to run binary under a wrapper
113 # script since osmo-trx ignores SIGHUP and will keep running after
114 # we close local ssh session. The wrapper script catches SIGHUP and
115 # sends SIGINT to it.
116 self.create_remote_dir(remote_dir)
117
118 wrapper_script = self.generate_wrapper_script()
119 remote_wrapper_script = remote_dir.child(RemoteHost.WRAPPER_SCRIPT)
120 self.scp('scp-wrapper-to-remote', wrapper_script, remote_wrapper_script)
121
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100122 args = (remote_wrapper_script,) + popen_args
123 return self.RemoteProcess(name, args, remote_env, **popen_kwargs)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100124
Pau Espin Pedrol14022d32020-02-11 14:20:00 +0100125 def RemoteNetNSProcess(self, name, netns, popen_args, **popen_kwargs):
126 run_dir = self.run_dir.new_dir(name)
Pau Espin Pedrol4983eb52020-02-11 19:16:06 +0100127 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 +0100128
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100129 def run_remote_sync(self, name, popen_args):
Pau Espin Pedrolf6d45ad2020-02-11 14:39:15 +0100130 proc = self.RemoteProcess(name, popen_args, remote_env=self.remote_env)
Pau Espin Pedrol2eafb032020-01-29 17:56:14 +0100131 proc.launch_sync()
132 return proc
133
134 def rm_remote_dir(self, remote_dir):
135 remote_dir_str = str(remote_dir)
136 self.run_remote_sync('rm-remote-dir', ('test', '!', '-d', remote_dir_str, '||', 'rm', '-rf', remote_dir_str))
137
138 def create_remote_dir(self, remote_dir):
139 remote_dir_str = str(remote_dir)
140 self.run_remote_sync('mk-remote-dir', ('mkdir', '-p', remote_dir_str))
141
142 def recreate_remote_dir(self, remote_dir):
143 self.rm_remote_dir(remote_dir)
144 self.create_remote_dir(remote_dir)
145
146 def inst_compatible_for_remote(self):
147 proc = self.run_remote_sync('uname-m', ('uname', '-m'))
148 if "x86_64" in (proc.get_stdout() or ''):
149 return True
150 return False
151
152 def scp(self, name, local_path, remote_path):
153 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 +0100154
155 def scpfrom(self, name, remote_path, local_path):
156 process.run_local_sync(self.run_dir, name, ('scp', '-r', '%s@%s:%s' % (self.user(), self.host(), remote_path), local_path))
157
158 def setcap_net_admin(self, binary_path):
159 '''
160 This functionality requires specific setup on the host running
161 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
162 '''
163 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
164 self.run_remote_sync('setcap-netadm', ('sudo', SETCAP_NET_ADMIN_BIN, binary_path))
165
166 def setcap_netsys_admin(self, binary_path):
167 '''
168 This functionality requires specific setup on the host running
169 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
170 '''
171 SETCAP_NETSYS_ADMIN_BIN = 'osmo-gsm-tester_setcap_netsys_admin.sh'
172 self.run_remote_sync('setcap-netsysadm', ('sudo', SETCAP_NETSYS_ADMIN_BIN, binary_path))
173
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100174 def create_netns(self, netns):
175 '''
176 It creates the netns if it doesn't already exist.
177 '''
178 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
179 self.run_remote_sync('create_netns', ('sudo', NETNS_SETUP_BIN, netns))
180
Pau Espin Pedrol7381e822020-02-11 14:30:42 +0100181 def change_elf_rpath(self, binary_path, paths):
182 '''
183 Change RPATH field in ELF executable binary.
Pau Espin Pedrol666d6c82020-02-24 10:41:58 +0100184 This feature can be used to tell the loader to load the trial libraries, as
Pau Espin Pedrol7381e822020-02-11 14:30:42 +0100185 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
186 '''
187 patchelf_bin = self.remote_env.get('PATCHELF_BIN', None)
188 if not patchelf_bin:
189 patchelf_bin = 'patchelf'
190 else:
191 self.dbg('Using specific patchelf from %s', patchelf_bin)
192
193 self.run_remote_sync('patchelf', (patchelf_bin, '--set-rpath', paths, binary_path))