blob: ff09f1795e1b4fe7d9a20982715c8431d2d974e8 [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 Pedrole1a58bd2020-04-10 20:46:07 +020023from ..core import log, util, config, process, remote
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +020024from ..core import schema
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020025from . import pcap_recorder, run_node
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020026
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +020027def on_register_schemas():
28 config_schema = {
29 'time': schema.DURATION,
30 }
31 schema.register_config_schema('iperf3cli', config_schema)
32
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +020033def iperf3_result_to_json(log_obj, data):
34 try:
35 # Drop non-interesting self-generated output before json:
36 if not data.startswith('{\n'):
37 data = "{\n" + data.split("\n{\n")[1]
38 # Sometimes iperf3 provides 2 dictionaries, the 2nd one being an error about being interrupted (by us).
39 # json parser doesn't support (raises exception) parsing several dictionaries at a time (not a valid json object).
40 # We are only interested in the first dictionary, the regular results one:
41 data = data.split("\n}")[0] + "\n}"
42 j = json.loads(data)
43 return j
44 except Exception as e:
45 log_obj.log('failed parsing iperf3 output: "%s"' % data)
46 raise e
Pau Espin Pedrol64f0b1b2018-11-09 17:44:10 +010047
48
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020049class IPerf3Server(log.Origin):
50
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010051 DEFAULT_SRV_PORT = 5003
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010052 LOGFILE = 'iperf3_srv.json'
53 REMOTE_DIR = '/tmp'
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010054
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020055 def __init__(self, testenv, ip_address):
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020056 super().__init__(log.C_RUN, 'iperf3-srv_%s' % ip_address.get('addr'))
57 self.run_dir = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020058 self.process = None
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010059 self._run_node = None
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020060 self.testenv = testenv
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020061 self.ip_address = ip_address
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +010062 self._port = IPerf3Server.DEFAULT_SRV_PORT
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010063 self.log_file = None
64 self.rem_host = None
65 self.remote_log_file = None
66 self.log_copied = False
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +020067 self.logfile_supported = False # some older versions of iperf doesn't support --logfile arg
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010068
69 def cleanup(self):
70 if self.process is None:
71 return
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +020072 if self.runs_locally() or not self.logfile_supported:
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010073 return
74 # copy back files (may not exist, for instance if there was an early error of process):
75 try:
76 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
77 except Exception as e:
78 self.log(repr(e))
79
80 def runs_locally(self):
81 locally = not self._run_node or self._run_node.is_local()
82 return locally
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020083
84 def start(self):
85 self.log('Starting iperf3-srv')
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010086 self.log_copied = False
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020087 self.run_dir = util.Dir(self.testenv.suite().get_run_dir().new_dir(self.name()))
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010088 self.log_file = self.run_dir.new_file(IPerf3Server.LOGFILE)
89 if self.runs_locally():
90 self.start_locally()
91 else:
92 self.start_remotely()
Pau Espin Pedrol8a725862018-10-26 15:54:28 +020093
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +010094 def start_remotely(self):
95 self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
96 remote_prefix_dir = util.Dir(IPerf3Server.REMOTE_DIR)
97 remote_run_dir = util.Dir(remote_prefix_dir.child('srv-' + str(self)))
98 self.remote_log_file = remote_run_dir.child(IPerf3Server.LOGFILE)
99
100 self.rem_host.recreate_remote_dir(remote_run_dir)
101
102 args = ('iperf3', '-s', '-B', self.addr(),
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200103 '-p', str(self._port), '-J')
104 if self.logfile_supported:
105 args += ('--logfile', self.remote_log_file,)
106
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100107 self.process = self.rem_host.RemoteProcess(self.name(), args)
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200108 self.testenv.remember_to_stop(self.process)
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100109 self.process.launch()
110
111 def start_locally(self):
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200112 pcap_recorder.PcapRecorder(self.testenv, self.run_dir.new_dir('pcap'), None,
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200113 'host %s and port not 22' % self.addr())
114
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100115 args = ('iperf3', '-s', '-B', self.addr(),
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200116 '-p', str(self._port), '-J')
117 if self.logfile_supported:
118 args += ('--logfile', os.path.abspath(self.log_file),)
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100119
120 self.process = process.Process(self.name(), self.run_dir, args, env={})
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200121 self.testenv.remember_to_stop(self.process)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200122 self.process.launch()
123
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100124 def set_run_node(self, run_node):
125 self._run_node = run_node
126
Pau Espin Pedrolcf6a3602018-11-14 16:38:19 +0100127 def set_port(self, port):
128 self._port = port
129
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200130 def stop(self):
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200131 self.testenv.stop_process(self.process)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200132
133 def get_results(self):
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200134 if self.logfile_supported:
135 if not self.runs_locally() and not self.log_copied:
136 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
137 self.log_copied = True
138 with open(self.log_file) as f:
139 return iperf3_result_to_json(self, f.read())
140 else:
141 return iperf3_result_to_json(self, self.process.get_stdout())
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200142
143 def addr(self):
144 return self.ip_address.get('addr')
145
146 def port(self):
147 return self._port
148
Pau Espin Pedrol0df63172018-11-14 16:39:30 +0100149 def __str__(self):
150 return "%s:%u" %(self.addr(), self.port())
151
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200152 def running(self):
153 return not self.process.terminated()
154
155 def create_client(self):
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200156 return IPerf3Client(self.testenv, self)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200157
158class IPerf3Client(log.Origin):
159
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100160 REMOTE_DIR = '/tmp'
161 LOGFILE = 'iperf3_cli.json'
162
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200163 def __init__(self, testenv, iperf3srv):
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200164 super().__init__(log.C_RUN, 'iperf3-cli_%s' % iperf3srv.addr())
165 self.run_dir = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200166 self.process = None
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100167 self._run_node = None
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200168 self.server = iperf3srv
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200169 self.testenv = testenv
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100170 self.log_file = None
171 self.rem_host = None
172 self.remote_log_file = None
173 self.log_copied = False
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200174 self.logfile_supported = False # some older versions of iperf doesn't support --logfile arg
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100175
176 def runs_locally(self):
177 locally = not self._run_node or self._run_node.is_local()
178 return locally
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200179
Andre Puschmann2dcc4312020-03-28 15:34:00 +0100180 def prepare_test_proc(self, downlink=False, netns=None, time_sec=None):
181 if time_sec is None:
182 values = config.get_defaults('iperf3cli')
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200183 config.overlay(values, self.testenv.suite().config().get('iperf3cli', {}))
Andre Puschmann2dcc4312020-03-28 15:34:00 +0100184 time_sec_str = values.get('time', time_sec)
185
186 # Convert duration to seconds
187 if isinstance(time_sec_str, str) and time_sec_str.endswith('h'):
188 time_sec = int(time_sec_str[:-1]) * 3600
189 elif isinstance(time_sec_str, str) and time_sec_str.endswith('m'):
190 time_sec = int(time_sec_str[:-1]) * 60
191 else:
192 time_sec = int(time_sec_str)
193
194 assert(time_sec)
195
196 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 +0100197 self.log_copied = False
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200198 self.run_dir = util.Dir(self.testenv.suite().get_run_dir().new_dir(self.name()))
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100199 self.log_file = self.run_dir.new_file(IPerf3Client.LOGFILE)
200 if self.runs_locally():
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100201 return self.prepare_test_proc_locally(downlink, netns, time_sec)
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100202 else:
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100203 return self.prepare_test_proc_remotely(downlink, netns, time_sec)
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200204
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100205 def prepare_test_proc_remotely(self, downlink, netns, time_sec):
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100206 self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
207
208 remote_prefix_dir = util.Dir(IPerf3Client.REMOTE_DIR)
209 remote_run_dir = util.Dir(remote_prefix_dir.child('cli-' + str(self)))
210 self.remote_log_file = remote_run_dir.child(IPerf3Client.LOGFILE)
211
212 self.rem_host.recreate_remote_dir(remote_run_dir)
213
214 popen_args = ('iperf3', '-c', self.server.addr(),
215 '-p', str(self.server.port()), '-J',
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100216 '-t', str(time_sec))
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200217 if self.logfile_supported:
218 popen_args += ('--logfile', self.remote_log_file,)
Pau Espin Pedrol905e5032020-03-02 15:35:33 +0100219 if downlink:
220 popen_args += ('-R',)
221
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100222 if netns:
223 self.process = self.rem_host.RemoteNetNSProcess(self.name(), netns, popen_args, env={})
224 else:
225 self.process = self.rem_host.RemoteProcess(self.name(), popen_args, env={})
226 return self.process
227
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100228 def prepare_test_proc_locally(self, downlink, netns, time_sec):
Pau Espin Pedrola442cb82020-05-05 12:54:37 +0200229 pcap_recorder.PcapRecorder(self.testenv, self.run_dir.new_dir('pcap'), None,
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200230 'host %s and port not 22' % self.server.addr(), netns)
231
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200232 popen_args = ('iperf3', '-c', self.server.addr(),
233 '-p', str(self.server.port()), '-J',
Pau Espin Pedrol085a17e2020-03-02 18:14:02 +0100234 '-t', str(time_sec))
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200235 if self.logfile_supported:
236 popen_args += ('--logfile', os.path.abspath(self.log_file),)
Pau Espin Pedrol905e5032020-03-02 15:35:33 +0100237 if downlink:
238 popen_args += ('-R',)
239
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200240 if netns:
241 self.process = process.NetNSProcess(self.name(), self.run_dir, netns, popen_args, env={})
242 else:
243 self.process = process.Process(self.name(), self.run_dir, popen_args, env={})
Pau Espin Pedrol0fc74372018-11-14 16:44:57 +0100244 return self.process
245
246 def run_test_sync(self, netns=None):
247 self.prepare_test_proc(netns)
Pau Espin Pedrol79df7392018-11-12 18:15:30 +0100248 self.process.launch_sync()
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200249 return self.get_results()
250
251 def get_results(self):
Pau Espin Pedrolfdd8e3a2020-04-14 11:30:24 +0200252 if self.logfile_supported:
253 if not self.runs_locally() and not self.log_copied:
254 self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
255 self.log_copied = True
256 with open(self.log_file) as f:
257 return iperf3_result_to_json(self, f.read())
258 else:
259 return iperf3_result_to_json(self, self.process.get_stdout())
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200260
Pau Espin Pedrolc852ad82020-02-11 17:43:58 +0100261 def set_run_node(self, run_node):
262 self._run_node = run_node
263
264 def __str__(self):
265 # FIXME: somehow differentiate between several clients connected to same server?
266 return "%s:%u" %(self.server.addr(), self.server.port())
267
Pau Espin Pedrol8a725862018-10-26 15:54:28 +0200268# vim: expandtab tabstop=4 shiftwidth=4