blob: 331a3ba588e392276f1538a40648a41d24ced1be [file] [log] [blame]
Pau Espin Pedrol8a725862018-10-26 15:54:28 +02001# osmo_gsm_tester: specifics for running an iperf3 client and server
2#
3# Copyright (C) 2018 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 os
21import json
22
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020023from .core import log, util, config, process, remote
24from . import pcap_recorder, run_node
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020025
Pau Espin Pedrol64f0b1b2018-11-09 17:44:10 +010026def iperf3_result_to_json(file):
27 with open(file) as f:
28 # Sometimes iperf3 provides 2 dictionaries, the 2nd one being an error about being interrupted (by us).
29 # json parser doesn't support (raises exception) parsing several dictionaries at a time (not a valid json object).
30 # We are only interested in the first dictionary, the regular results one:
31 d = f.read().split("\n}\n")[0] + "\n}\n"
32 data = json.loads(d)
33 return data
34
35
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020036class IPerf3Server(log.Origin):
37
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010038 DEFAULT_SRV_PORT = 5003
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010039 LOGFILE = 'iperf3_srv.json'
40 REMOTE_DIR = '/tmp'
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010041
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020042 def __init__(self, suite_run, ip_address):
43 super().__init__(log.C_RUN, 'iperf3-srv_%s' % ip_address.get('addr'))
44 self.run_dir = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020045 self.process = None
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010046 self._run_node = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020047 self.suite_run = suite_run
48 self.ip_address = ip_address
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010049 self._port = IPerf3Server.DEFAULT_SRV_PORT
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010050 self.log_file = None
51 self.rem_host = None
52 self.remote_log_file = None
53 self.log_copied = False
54
55 def cleanup(self):
56 if self.process is None:
57 return
58 if self.runs_locally():
59 return
60 # copy back files (may not exist, for instance if there was an early error of process):
61 try:
62 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
63 except Exception as e:
64 self.log(repr(e))
65
66 def runs_locally(self):
67 locally = not self._run_node or self._run_node.is_local()
68 return locally
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020069
70 def start(self):
71 self.log('Starting iperf3-srv')
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010072 self.log_copied = False
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020073 self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010074 self.log_file = self.run_dir.new_file(IPerf3Server.LOGFILE)
75 if self.runs_locally():
76 self.start_locally()
77 else:
78 self.start_remotely()
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020079
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010080 def start_remotely(self):
81 self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
82 remote_prefix_dir = util.Dir(IPerf3Server.REMOTE_DIR)
83 remote_run_dir = util.Dir(remote_prefix_dir.child('srv-' + str(self)))
84 self.remote_log_file = remote_run_dir.child(IPerf3Server.LOGFILE)
85
86 self.rem_host.recreate_remote_dir(remote_run_dir)
87
88 args = ('iperf3', '-s', '-B', self.addr(),
89 '-p', str(self._port), '-J',
90 '--logfile', self.remote_log_file)
91 self.process = self.rem_host.RemoteProcess(self.name(), args)
92 self.suite_run.remember_to_stop(self.process)
93 self.process.launch()
94
95 def start_locally(self):
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020096 pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
97 'host %s and port not 22' % self.addr())
98
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010099 args = ('iperf3', '-s', '-B', self.addr(),
100 '-p', str(self._port), '-J',
101 '--logfile', os.path.abspath(self.log_file))
102
103 self.process = process.Process(self.name(), self.run_dir, args, env={})
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200104 self.suite_run.remember_to_stop(self.process)
105 self.process.launch()
106
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100107 def set_run_node(self, run_node):
108 self._run_node = run_node
109
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +0100110 def set_port(self, port):
111 self._port = port
112
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200113 def stop(self):
114 self.suite_run.stop_process(self.process)
115
116 def get_results(self):
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100117 if not self.runs_locally() and not self.log_copied:
118 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
119 self.log_copied = True
Pau Espin Pedrol64f0b1b2018-11-09 17:44:10 +0100120 return iperf3_result_to_json(self.log_file)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200121
122 def addr(self):
123 return self.ip_address.get('addr')
124
125 def port(self):
126 return self._port
127
Pau Espin Pedrol0df63172018-11-14 16:39:30 +0100128 def __str__(self):
129 return "%s:%u" %(self.addr(), self.port())
130
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200131 def running(self):
132 return not self.process.terminated()
133
134 def create_client(self):
135 return IPerf3Client(self.suite_run, self)
136
137class IPerf3Client(log.Origin):
138
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100139 REMOTE_DIR = '/tmp'
140 LOGFILE = 'iperf3_cli.json'
141
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200142 def __init__(self, suite_run, iperf3srv):
143 super().__init__(log.C_RUN, 'iperf3-cli_%s' % iperf3srv.addr())
144 self.run_dir = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200145 self.process = None
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100146 self._run_node = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200147 self.server = iperf3srv
148 self.suite_run = suite_run
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100149 self.log_file = None
150 self.rem_host = None
151 self.remote_log_file = None
152 self.log_copied = False
153
154 def runs_locally(self):
155 locally = not self._run_node or self._run_node.is_local()
156 return locally
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200157
Andre Puschmann2dcc4312020-03-28 15:34:00 +0100158 def prepare_test_proc(self, downlink=False, netns=None, time_sec=None):
159 if time_sec is None:
160 values = config.get_defaults('iperf3cli')
161 config.overlay(values, self.suite_run.config().get('iperf3cli', {}))
162 time_sec_str = values.get('time', time_sec)
163
164 # Convert duration to seconds
165 if isinstance(time_sec_str, str) and time_sec_str.endswith('h'):
166 time_sec = int(time_sec_str[:-1]) * 3600
167 elif isinstance(time_sec_str, str) and time_sec_str.endswith('m'):
168 time_sec = int(time_sec_str[:-1]) * 60
169 else:
170 time_sec = int(time_sec_str)
171
172 assert(time_sec)
173
174 self.log('Preparing iperf3-client connecting to %s:%d (time=%ds)' % (self.server.addr(), self.server.port(), time_sec))
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100175 self.log_copied = False
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200176 self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100177 self.log_file = self.run_dir.new_file(IPerf3Client.LOGFILE)
178 if self.runs_locally():
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100179 return self.prepare_test_proc_locally(downlink, netns, time_sec)
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100180 else:
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100181 return self.prepare_test_proc_remotely(downlink, netns, time_sec)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200182
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100183 def prepare_test_proc_remotely(self, downlink, netns, time_sec):
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100184 self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
185
186 remote_prefix_dir = util.Dir(IPerf3Client.REMOTE_DIR)
187 remote_run_dir = util.Dir(remote_prefix_dir.child('cli-' + str(self)))
188 self.remote_log_file = remote_run_dir.child(IPerf3Client.LOGFILE)
189
190 self.rem_host.recreate_remote_dir(remote_run_dir)
191
192 popen_args = ('iperf3', '-c', self.server.addr(),
193 '-p', str(self.server.port()), '-J',
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100194 '--logfile', self.remote_log_file,
195 '-t', str(time_sec))
Pau Espin Pedrol905e5032020-03-02 15:35:33 +0100196 if downlink:
197 popen_args += ('-R',)
198
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100199 if netns:
200 self.process = self.rem_host.RemoteNetNSProcess(self.name(), netns, popen_args, env={})
201 else:
202 self.process = self.rem_host.RemoteProcess(self.name(), popen_args, env={})
203 return self.process
204
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100205 def prepare_test_proc_locally(self, downlink, netns, time_sec):
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200206 pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
207 'host %s and port not 22' % self.server.addr(), netns)
208
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200209 popen_args = ('iperf3', '-c', self.server.addr(),
210 '-p', str(self.server.port()), '-J',
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100211 '--logfile', os.path.abspath(self.log_file),
212 '-t', str(time_sec))
Pau Espin Pedrol905e5032020-03-02 15:35:33 +0100213 if downlink:
214 popen_args += ('-R',)
215
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200216 if netns:
217 self.process = process.NetNSProcess(self.name(), self.run_dir, netns, popen_args, env={})
218 else:
219 self.process = process.Process(self.name(), self.run_dir, popen_args, env={})
Pau Espin Pedrol0fc74372018-11-14 16:44:57 +0100220 return self.process
221
222 def run_test_sync(self, netns=None):
223 self.prepare_test_proc(netns)
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100224 self.process.launch_sync()
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200225 return self.get_results()
226
227 def get_results(self):
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100228 if not self.runs_locally() and not self.log_copied:
229 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
230 self.log_copied = True
Pau Espin Pedrol64f0b1b2018-11-09 17:44:10 +0100231 return iperf3_result_to_json(self.log_file)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200232
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100233 def set_run_node(self, run_node):
234 self._run_node = run_node
235
236 def __str__(self):
237 # FIXME: somehow differentiate between several clients connected to same server?
238 return "%s:%u" %(self.server.addr(), self.server.port())
239
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200240# vim: expandtab tabstop=4 shiftwidth=4