Merge ~kissiel/checkbox-support:add-network-tool into checkbox-support:master
- Git
- lp:~kissiel/checkbox-support
- add-network-tool
- Merge into master
Proposed by
Maciej Kisielewski
Status: | Merged |
---|---|
Approved by: | Maciej Kisielewski |
Approved revision: | c62303ba08e0310e5096cf470cdd4781d8b5e439 |
Merged at revision: | 941d3ee75924ee9435d29fcb288e1d08a2ba48e8 |
Proposed branch: | ~kissiel/checkbox-support:add-network-tool |
Merge into: | checkbox-support:master |
Diff against target: |
746 lines (+729/-0) 2 files modified
checkbox_support/scripts/network.py (+727/-0) setup.py (+2/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sylvain Pineau (community) | Needs Fixing | ||
Jonathan Cave (community) | Approve | ||
Review via email: mp+315209@code.launchpad.net |
Commit message
Description of the change
Include 'network' test script in checkbox-script.
It's now used in plainbox-
To post a comment you must log in.
Revision history for this message
Maciej Kisielewski (kissiel) wrote : | # |
Revision history for this message
Jonathan Cave (jocave) wrote : | # |
Straight import from elsewhere, looks fine to me.
review:
Approve
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote : | # |
just grep'ed for ethtool, there's a comment with it we could update with mii-tool instead. Otherwise +1
review:
Needs Fixing
Revision history for this message
Maciej Kisielewski (kissiel) wrote : | # |
> just grep'ed for ethtool, there's a comment with it we could update with mii-
> tool instead. Otherwise +1
Nice catch!
Amended, pushed.
Landing...
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/checkbox_support/scripts/network.py b/checkbox_support/scripts/network.py |
2 | new file mode 100755 |
3 | index 0000000..f66c4ad |
4 | --- /dev/null |
5 | +++ b/checkbox_support/scripts/network.py |
6 | @@ -0,0 +1,727 @@ |
7 | +#!/usr/bin/env python3 |
8 | +""" |
9 | +Copyright (C) 2012-2015 Canonical Ltd. |
10 | + |
11 | +Authors |
12 | + Jeff Marcom <jeff.marcom@canonical.com> |
13 | + Daniel Manrique <roadmr@ubuntu.com> |
14 | + Jeff Lane <jeff@ubuntu.com> |
15 | + |
16 | +This program is free software: you can redistribute it and/or modify |
17 | +it under the terms of the GNU General Public License version 3, |
18 | +as published by the Free Software Foundation. |
19 | + |
20 | +This program is distributed in the hope that it will be useful, |
21 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | +GNU General Public License for more details. |
24 | + |
25 | +You should have received a copy of the GNU General Public License |
26 | +along with this program. If not, see <http://www.gnu.org/licenses/>. |
27 | +""" |
28 | + |
29 | +from argparse import ( |
30 | + ArgumentParser, |
31 | + RawTextHelpFormatter |
32 | +) |
33 | +import datetime |
34 | +import fcntl |
35 | +import ipaddress |
36 | +import logging |
37 | +import os |
38 | +import re |
39 | +import shlex |
40 | +import socket |
41 | +import struct |
42 | +import subprocess |
43 | +import tempfile |
44 | +from subprocess import ( |
45 | + CalledProcessError, |
46 | + check_call, |
47 | + check_output, |
48 | + STDOUT |
49 | +) |
50 | +import sys |
51 | +import time |
52 | + |
53 | + |
54 | +class IPerfPerformanceTest(object): |
55 | + """Measures performance of interface using iperf client |
56 | + and target. Calculated speed is measured against theorectical |
57 | + throughput of selected interface""" |
58 | + |
59 | + def __init__( |
60 | + self, |
61 | + interface, |
62 | + target, |
63 | + fail_threshold, |
64 | + cpu_load_fail_threshold, |
65 | + iperf3, |
66 | + protocol="tcp", |
67 | + data_size="1", |
68 | + run_time=None, |
69 | + scan_timeout=3600): |
70 | + |
71 | + self.iface = Interface(interface) |
72 | + self.target = target |
73 | + self.protocol = protocol |
74 | + self.fail_threshold = fail_threshold |
75 | + self.cpu_load_fail_threshold = cpu_load_fail_threshold |
76 | + self.iperf3 = iperf3 |
77 | + self.data_size = data_size |
78 | + self.run_time = run_time |
79 | + self.scan_timeout = scan_timeout |
80 | + |
81 | + def run(self): |
82 | + # if max_speed is 0, assume it's wifi and move on |
83 | + if self.iface.max_speed == 0: |
84 | + logging.warning("No max speed detected, assuming Wireless device " |
85 | + "and continuing with test.") |
86 | + # Otherwise, no sense in running if we're not running at full speed. |
87 | + elif self.iface.max_speed > self.iface.link_speed: |
88 | + logging.error("Detected link speed (%s) is lower than " |
89 | + "detected max speed (%s)" % |
90 | + (self.iface.link_speed, self.iface.max_speed)) |
91 | + logging.error("Check your device configuration and try again") |
92 | + return 1 |
93 | + |
94 | + # Set the correct binary to run |
95 | + if (self.iperf3): |
96 | + self.executable = "iperf3 -V" |
97 | + else: |
98 | + self.executable = "iperf" |
99 | + |
100 | + # If we set run_time, use that instead to build the command. |
101 | + if self.run_time is not None: |
102 | + cmd = "{} -c {} -t {} -i 1 -f m".format( |
103 | + self.executable, self.target, self.run_time) |
104 | + else: |
105 | + # Because we can vary the data size, we need to vary the timeout as |
106 | + # well. It takes an estimated 15 minutes to send 1GB over 10Mb/s. |
107 | + # 802.11b is 11 Mb/s. So we'll assume 1.2x15 minutes or 18 minutes |
108 | + # or 1080 seconds per Gigabit. This will allow for a long period of |
109 | + # time without timeout to catch devices that slow down, and also |
110 | + # not prematurely end iperf on low-bandwidth devices. |
111 | + self.timeout = 1080*int(self.data_size) |
112 | + cmd = "timeout {} {} -c {} -n {}G -i 1 -f -m".format( |
113 | + self.timeout, self.executable, self.target, self.data_size) |
114 | + |
115 | + logging.debug("Executing command {}".format(cmd)) |
116 | + logging.info("Starting iperf against {}, this could take a while...". |
117 | + format(self.target)) |
118 | + try: |
119 | + iperf_return = check_output( |
120 | + shlex.split(cmd), universal_newlines=True) |
121 | + except CalledProcessError as iperf_exception: |
122 | + if iperf_exception.returncode != 124: |
123 | + # timeout command will return 124 if iperf timed out, so any |
124 | + # other return value means something did fail |
125 | + logging.error("Failed executing iperf: %s", |
126 | + iperf_exception.output) |
127 | + return iperf_exception.returncode |
128 | + else: |
129 | + # this is normal so we "except" this exception and we |
130 | + # "pass through" whatever output iperf did manage to produce. |
131 | + # When confronted with SIGTERM iperf should stop and output |
132 | + # a partial (but usable) result. |
133 | + logging.info("iperf timed out - this should be OK") |
134 | + iperf_return = iperf_exception.output |
135 | + |
136 | + logging.debug(iperf_return) |
137 | + # "CPU Utilization" line present only in iperf3 output |
138 | + cpu = re.findall(r"CPU Utilization.*local/sender\s([\w\.]+)", |
139 | + iperf_return) |
140 | + # iperf3 provides "sender" and "receiver" summaries; remove them |
141 | + iperf_return = re.sub(r".*(sender|receiver)", "", iperf_return) |
142 | + speeds = list(map(float, re.findall(r"([\w\.]+)\sMbits/sec", |
143 | + iperf_return))) |
144 | + invalid_speed = False |
145 | + if speeds: |
146 | + throughput = sum(speeds)/len(speeds) |
147 | + try: |
148 | + percent = throughput / int(self.iface.max_speed) * 100 |
149 | + except (ZeroDivisionError, TypeError): |
150 | + # Catches a condition where the interface functions fine but |
151 | + # mii-tool fails to properly report max speed. In this case |
152 | + # it's up to the reviewer to pass or fail. |
153 | + percent = 0 |
154 | + invalid_speed = True |
155 | + |
156 | + logging.info("Min Transfer speed: {} Mb/s".format(min(speeds))) |
157 | + logging.info("Max Transfer speed: {} Mb/s".format(max(speeds))) |
158 | + logging.info("Avg Transfer speed: {} Mb/s".format(throughput)) |
159 | + if invalid_speed: |
160 | + # If we have no link_speed (e.g. wireless interfaces don't |
161 | + # report this), then we shouldn't penalize them because |
162 | + # the transfer may have been reasonable. So in this case, |
163 | + # we'll exit with a pass-warning. |
164 | + logging.info("Unable to obtain maximum speed.") |
165 | + logging.info("Considering the test as passed.") |
166 | + return 0 |
167 | + # Below is guaranteed to not throw an exception because we'll |
168 | + # have exited above if it did. |
169 | + logging.info("{:03.2f}% of theoretical max {} Mb/s". |
170 | + format(percent, int(self.iface.max_speed))) |
171 | + if cpu: |
172 | + logging.info("") |
173 | + logging.info("CPU utilization: {}%".format(cpu[0])) |
174 | + cpu_load = float(cpu[0]) |
175 | + else: |
176 | + cpu_load = 0.0 |
177 | + if percent < self.fail_threshold or \ |
178 | + cpu_load > self.cpu_load_fail_threshold: |
179 | + logging.warn("Poor network performance detected against {}". |
180 | + format(self.target)) |
181 | + if percent < self.fail_threshold: |
182 | + logging.warn(" Transfer speed: {} Mb/s". |
183 | + format(throughput)) |
184 | + logging.warn(" {:03.2f}% of theoretical max {} Mb/s\n". |
185 | + format(percent, int(self.iface.max_speed))) |
186 | + if cpu_load > self.cpu_load_fail_threshold: |
187 | + logging.warn(" CPU load: {}%".format(cpu_load)) |
188 | + logging.warn(" CPU load is above {}% maximum\n". |
189 | + format(self.cpu_load_fail_threshold)) |
190 | + return 30 |
191 | + |
192 | + logging.debug("Passed benchmark against {}".format(self.target)) |
193 | + else: |
194 | + logging.error("Failed iperf benchmark against {}". |
195 | + format(self.target)) |
196 | + return 1 |
197 | + |
198 | + |
199 | +class StressPerformanceTest: |
200 | + |
201 | + def __init__(self, interface, target, iperf3): |
202 | + self.interface = interface |
203 | + self.target = target |
204 | + self.iperf3 = iperf3 |
205 | + |
206 | + def run(self): |
207 | + if self.iperf3: |
208 | + iperf_cmd = 'timeout 320 iperf3 -c {} -t 300'.format(self.target) |
209 | + else: |
210 | + iperf_cmd = 'timeout 320 iperf -c {} -t 300'.format(self.target) |
211 | + print("Running iperf...") |
212 | + iperf = subprocess.Popen(shlex.split(iperf_cmd)) |
213 | + |
214 | + ping_cmd = 'ping -I {} {}'.format(self.interface, self.target) |
215 | + ping = subprocess.Popen(shlex.split(ping_cmd), stdout=subprocess.PIPE) |
216 | + iperf.communicate() |
217 | + |
218 | + ping.terminate() |
219 | + (out, err) = ping.communicate() |
220 | + |
221 | + if iperf.returncode != 0: |
222 | + return iperf.returncode |
223 | + |
224 | + print("Running ping test...") |
225 | + result = 0 |
226 | + time_re = re.compile('(?<=time=)[0-9]*') |
227 | + for line in out.decode().split('\n'): |
228 | + time = time_re.search(line) |
229 | + |
230 | + if time and int(time.group()) > 2000: |
231 | + print(line) |
232 | + print("ICMP packet was delayed by > 2000 ms.") |
233 | + result = 1 |
234 | + if 'unreachable' in line.lower(): |
235 | + print(line) |
236 | + result = 1 |
237 | + |
238 | + return result |
239 | + |
240 | + |
241 | +class Interface(socket.socket): |
242 | + """ |
243 | + Simple class that provides network interface information. |
244 | + """ |
245 | + |
246 | + def __init__(self, interface): |
247 | + |
248 | + super(Interface, self).__init__( |
249 | + socket.AF_INET, socket.IPPROTO_ICMP) |
250 | + |
251 | + self.interface = interface |
252 | + |
253 | + self.dev_path = os.path.join("/sys/class/net", self.interface) |
254 | + |
255 | + def _read_data(self, type): |
256 | + try: |
257 | + return open(os.path.join(self.dev_path, type)).read().strip() |
258 | + except OSError: |
259 | + logging.info("%s: Attribute not found", type) |
260 | + |
261 | + @property |
262 | + def ipaddress(self): |
263 | + freq = struct.pack('256s', self.interface[:15].encode()) |
264 | + |
265 | + try: |
266 | + nic_data = fcntl.ioctl(self.fileno(), 0x8915, freq) |
267 | + except IOError: |
268 | + logging.error("No IP address for %s", self.interface) |
269 | + return 1 |
270 | + return socket.inet_ntoa(nic_data[20:24]) |
271 | + |
272 | + @property |
273 | + def netmask(self): |
274 | + freq = struct.pack('256s', self.interface.encode()) |
275 | + |
276 | + try: |
277 | + mask_data = fcntl.ioctl(self.fileno(), 0x891b, freq) |
278 | + except IOError: |
279 | + logging.error("No netmask for %s", self.interface) |
280 | + return 1 |
281 | + return socket.inet_ntoa(mask_data[20:24]) |
282 | + |
283 | + @property |
284 | + def link_speed(self): |
285 | + return int(self._read_data("speed")) |
286 | + |
287 | + @property |
288 | + def max_speed(self): |
289 | + # Parse mii-tool data for max speed |
290 | + # search for numbers in the line starting with 'capabilities' |
291 | + # return largest number as max_speed |
292 | + try: |
293 | + info = check_output(['mii-tool', '-v', self.interface], |
294 | + universal_newlines=True, |
295 | + stderr=STDOUT).split('\n') |
296 | + except FileNotFoundError: |
297 | + logging.warning('mii-tool not found! Unable to get max speed') |
298 | + ethinfo = None |
299 | + except CalledProcessError as e: |
300 | + logging.error('mii-tool returned an error!') |
301 | + logging.error(e.output) |
302 | + ethinfo = None |
303 | + finally: |
304 | + regex = re.compile(r'(\d+)(base)([A-Z]+)') |
305 | + speeds = [0] |
306 | + for line in filter(lambda l: 'capabilities' in l, info): |
307 | + for s in line.split(' '): |
308 | + hit = regex.search(s) |
309 | + if hit: |
310 | + speeds.append(int(re.sub("\D", "", hit.group(0)))) |
311 | + return max(speeds) |
312 | + |
313 | + @property |
314 | + def macaddress(self): |
315 | + return self._read_data("address") |
316 | + |
317 | + @property |
318 | + def duplex_mode(self): |
319 | + return self._read_data("duplex") |
320 | + |
321 | + @property |
322 | + def status(self): |
323 | + return self._read_data("operstate") |
324 | + |
325 | + @property |
326 | + def device_name(self): |
327 | + return self._read_data("device/label") |
328 | + |
329 | + |
330 | +def get_test_parameters(args, environ): |
331 | + # Decide the actual values for test parameters, which can come |
332 | + # from one of two possible sources: command-line |
333 | + # arguments, or environment variables. |
334 | + # - If command-line args were given, they take precedence |
335 | + # - Next come environment variables, if set. |
336 | + |
337 | + params = {"test_target_iperf": None} |
338 | + |
339 | + # See if we have environment variables |
340 | + for key in params.keys(): |
341 | + params[key] = os.environ.get(key.upper(), "") |
342 | + |
343 | + # Finally, see if we have the command-line arguments that are the ultimate |
344 | + # override. |
345 | + if args.target: |
346 | + params["test_target_iperf"] = args.target |
347 | + |
348 | + return params |
349 | + |
350 | + |
351 | +def can_ping(the_interface, test_target): |
352 | + working_interface = False |
353 | + num_loops = 0 |
354 | + while (not working_interface) and (num_loops < 48): |
355 | + working_interface = True |
356 | + |
357 | + try: |
358 | + with open(os.devnull, 'wb') as DEVNULL: |
359 | + check_call(["ping", "-I", the_interface, |
360 | + "-c", "1", test_target], |
361 | + stdout=DEVNULL, stderr=DEVNULL) |
362 | + except CalledProcessError as excp: |
363 | + working_interface = False |
364 | + if num_loops == 0: |
365 | + logging.warning("Ping failure on {} ({})". |
366 | + format(the_interface, excp)) |
367 | + |
368 | + if not working_interface: |
369 | + time.sleep(5) |
370 | + num_loops += 1 |
371 | + |
372 | + return working_interface |
373 | + |
374 | + |
375 | +def run_test(args, test_target): |
376 | + # Ensure that interface is fully up by waiting until it can |
377 | + # ping the test server |
378 | + if not can_ping(args.interface, test_target): |
379 | + logging.error("Can't ping test server {} on {}".format(test_target, |
380 | + args.interface)) |
381 | + return 1 |
382 | + |
383 | + # Execute requested networking test |
384 | + if args.test_type.lower() == "iperf": |
385 | + error_number = 0 |
386 | + iperf_benchmark = IPerfPerformanceTest(args.interface, test_target, |
387 | + args.fail_threshold, |
388 | + args.cpu_load_fail_threshold, |
389 | + args.iperf3) |
390 | + if args.datasize: |
391 | + iperf_benchmark.data_size = args.datasize |
392 | + if args.runtime: |
393 | + iperf_benchmark.run_time = args.runtime |
394 | + run_num = 0 |
395 | + while not error_number and run_num < args.num_runs: |
396 | + error_number = iperf_benchmark.run() |
397 | + run_num += 1 |
398 | + logging.info(" Finished run number %s ".center(60, "-"), run_num) |
399 | + elif args.test_type.lower() == "stress": |
400 | + stress_benchmark = StressPerformanceTest(args.interface, |
401 | + test_target, args.iperf3) |
402 | + error_number = stress_benchmark.run() |
403 | + else: |
404 | + logging.error("Unknown test type {}".format(args.test_type)) |
405 | + return 10 |
406 | + return error_number |
407 | + |
408 | + |
409 | +def make_target_list(iface, test_targets, log_warnings): |
410 | + """Convert comma-separated string of test targets into a list form. |
411 | + |
412 | + Converts test target list in string form into Python list form, omitting |
413 | + entries that are not on the current network segment. |
414 | + :param iface: |
415 | + Name of network interface device (eth0, etc.) |
416 | + :param test_targets: |
417 | + Input test targets as string of comma-separated IP addresses or |
418 | + hostnames |
419 | + :param emit_warnings: |
420 | + Whether to log warning messages |
421 | + :returns: |
422 | + List form of input string, minus invalid values |
423 | + """ |
424 | + test_targets_list = test_targets.split(",") |
425 | + net = ipaddress.IPv4Network("{}/{}".format(Interface(iface).ipaddress, |
426 | + Interface(iface).netmask), |
427 | + False) |
428 | + first_addr = net.network_address + 1 |
429 | + last_addr = first_addr + net.num_addresses - 2 |
430 | + return_list = list(test_targets_list) |
431 | + for test_target in test_targets_list: |
432 | + try: |
433 | + test_target_ip = socket.gethostbyname(test_target) |
434 | + except OSError: |
435 | + test_target_ip = test_target |
436 | + try: |
437 | + target = ipaddress.IPv4Address(test_target_ip) |
438 | + if (target < first_addr) or (target > last_addr): |
439 | + if log_warnings: |
440 | + logging.warning("test server {} ({}) is NOT ". |
441 | + format(test_target, target)) |
442 | + logging.warning("within {}; skipping".format(net)) |
443 | + return_list.remove(test_target) |
444 | + except ValueError: |
445 | + if log_warnings: |
446 | + logging.warning("Invalid address: {}; skipping". |
447 | + format(test_target)) |
448 | + return_list.remove(test_target) |
449 | + return_list.reverse() |
450 | + return return_list |
451 | + |
452 | + |
453 | +def interface_test(args): |
454 | + if not ("test_type" in vars(args)): |
455 | + return |
456 | + |
457 | + # Get the actual test data from one of two possible sources |
458 | + test_parameters = get_test_parameters(args, os.environ) |
459 | + |
460 | + if (args.test_type.lower() == "iperf" or |
461 | + args.test_type.lower() == "stress"): |
462 | + test_targets = test_parameters["test_target_iperf"] |
463 | + test_targets_list = make_target_list(args.interface, test_targets, True) |
464 | + |
465 | + # Validate that we got reasonable values |
466 | + if not test_targets_list or "example.com" in test_targets: |
467 | + # Default values found in config file |
468 | + logging.error("Valid target server has not been supplied.") |
469 | + logging.info("Configuration settings can be configured 3 different " |
470 | + "ways:") |
471 | + logging.info("1- If calling the script directly, pass the --target " |
472 | + "option") |
473 | + logging.info("2- Define the TEST_TARGET_IPERF environment variable") |
474 | + logging.info("3- (If running the test via checkbox/plainbox, define " |
475 | + "the ") |
476 | + logging.info("target in /etc/xdg/canonical-certification.conf)") |
477 | + logging.info("Please run this script with -h to see more details on " |
478 | + "how to configure") |
479 | + sys.exit(1) |
480 | + |
481 | + # Testing begins here! |
482 | + # |
483 | + # Make sure that the interface is indeed connected |
484 | + try: |
485 | + check_call(["ip", "link", "set", "dev", args.interface, "up"]) |
486 | + except CalledProcessError as interface_failure: |
487 | + logging.error("Failed to use %s:%s", args.interface, interface_failure) |
488 | + return 1 |
489 | + |
490 | + # Back up routing table, since network down/up process |
491 | + # tends to trash it.... |
492 | + temp = tempfile.TemporaryFile() |
493 | + try: |
494 | + check_call(["ip", "route", "save", "table", "all"], stdout=temp) |
495 | + except CalledProcessError as route_error: |
496 | + logging.warning("Unable to save routing table: %s", route_error) |
497 | + |
498 | + error_number = 0 |
499 | + # Stop all other interfaces |
500 | + extra_interfaces = \ |
501 | + [iface for iface in os.listdir("/sys/class/net") |
502 | + if iface != "lo" and iface != args.interface] |
503 | + |
504 | + for iface in extra_interfaces: |
505 | + logging.debug("Shutting down interface:%s", iface) |
506 | + try: |
507 | + check_call(["ip", "link", "set", "dev", iface, "down"]) |
508 | + except CalledProcessError as interface_failure: |
509 | + logging.error("Failed to shut down %s:%s", |
510 | + iface, interface_failure) |
511 | + error_number = 3 |
512 | + |
513 | + if error_number == 0: |
514 | + start_time = datetime.datetime.now() |
515 | + first_loop = True |
516 | + # Keep testing until a success or we run out of both targets and time |
517 | + while test_targets_list: |
518 | + test_target = test_targets_list.pop().strip() |
519 | + error_number = run_test(args, test_target) |
520 | + elapsed_seconds = (datetime.datetime.now() - start_time).seconds |
521 | + if (elapsed_seconds > args.scan_timeout and not first_loop) or \ |
522 | + not error_number: |
523 | + break |
524 | + if not test_targets_list: |
525 | + logging.info(" Exhausted test target list; trying again " |
526 | + .center(60, "=")) |
527 | + test_targets_list = make_target_list(args.interface, test_targets, False) |
528 | + time.sleep(30) |
529 | + first_loop = False |
530 | + |
531 | + for iface in extra_interfaces: |
532 | + logging.debug("Restoring interface:%s", iface) |
533 | + try: |
534 | + check_call(["ip", "link", "set", "dev", iface, "up"]) |
535 | + except CalledProcessError as interface_failure: |
536 | + logging.error("Failed to restore %s:%s", iface, interface_failure) |
537 | + error_number = 3 |
538 | + |
539 | + # Restore routing table to original state |
540 | + temp.seek(0) |
541 | + try: |
542 | + # Harmless "RTNETLINK answers: File exists" messages on stderr |
543 | + with open(os.devnull, 'wb') as DEVNULL: |
544 | + check_call(["ip", "route", "restore"], stdin=temp, |
545 | + stderr=DEVNULL) |
546 | + except CalledProcessError as restore_failure: |
547 | + logging.warning("Unable to restore routing table: %s", restore_failure) |
548 | + temp.close() |
549 | + |
550 | + return error_number |
551 | + |
552 | + |
553 | +def interface_info(args): |
554 | + |
555 | + info_set = "" |
556 | + if "all" in vars(args): |
557 | + info_set = args.all |
558 | + |
559 | + for key, value in vars(args).items(): |
560 | + if value is True or info_set is True: |
561 | + key = key.replace("-", "_") |
562 | + try: |
563 | + print( |
564 | + key + ":", getattr(Interface(args.interface), key), |
565 | + file=sys.stderr) |
566 | + except AttributeError: |
567 | + pass |
568 | + |
569 | + |
570 | +def main(): |
571 | + |
572 | + intro_message = """ |
573 | +Network module |
574 | + |
575 | +This script provides benchmarking and information for a specified network |
576 | +interface. |
577 | + |
578 | +Example NIC information usage: |
579 | +network info -i eth0 --max-speed |
580 | + |
581 | +For running iperf test: |
582 | +network test -i eth0 -t iperf --target 192.168.0.1 |
583 | +NOTE: The iperf test requires an iperf server running on the same network |
584 | +segment that the test machine is running on. |
585 | + |
586 | +Also, you can use iperf3 rather than iperf2 by specifying the -3 or --iperf3 |
587 | +option like so: |
588 | + |
589 | +network test -i eth0 -t iperf -3 --target 192.168.0.1 |
590 | + |
591 | +Configuration |
592 | +============= |
593 | + |
594 | +Configuration can be supplied in three different ways, with the following |
595 | +priorities: |
596 | + |
597 | +1- Command-line parameters (see above). |
598 | +2- Environment variables (example will follow). |
599 | +3- If run via checkbox/plainbox, /etc/xdg/checkbox-certification.conf |
600 | + can have the below-mentioned environment variables defined in the |
601 | + [environment] section. An example file is provided and can be simply |
602 | + modified with the correct values. |
603 | + |
604 | +Environment variables |
605 | +===================== |
606 | +The variables are: |
607 | +TEST_TARGET_IPERF |
608 | + |
609 | +example config file |
610 | +=================== |
611 | +[environment] |
612 | +TEST_TARGET_IPERF = iperf-server.example.com |
613 | + |
614 | + |
615 | +**NOTE** |
616 | + |
617 | +""" |
618 | + |
619 | + parser = ArgumentParser( |
620 | + description=intro_message, formatter_class=RawTextHelpFormatter) |
621 | + subparsers = parser.add_subparsers() |
622 | + |
623 | + # Main cli options |
624 | + test_parser = subparsers.add_parser( |
625 | + 'test', help=("Run network performance test")) |
626 | + info_parser = subparsers.add_parser( |
627 | + 'info', help=("Gather network info")) |
628 | + |
629 | + # Sub test options |
630 | + action = test_parser.add_mutually_exclusive_group() |
631 | + |
632 | + test_parser.add_argument( |
633 | + '-i', '--interface', type=str, required=True) |
634 | + test_parser.add_argument( |
635 | + '-t', '--test_type', type=str, |
636 | + choices=("iperf", "stress"), default="iperf", |
637 | + help=("[iperf *Default*]")) |
638 | + test_parser.add_argument( |
639 | + '-3', '--iperf3', default=False, action="store_true", |
640 | + help=("Tells the script to use iperf3 for testing, rather than the " |
641 | + "default of iperf2")) |
642 | + test_parser.add_argument('--target', type=str) |
643 | + action.add_argument( |
644 | + '--datasize', type=str, |
645 | + default="1", |
646 | + help=("CANNOT BE USED WITH --runtime. Amount of data to send. For " |
647 | + "iperf tests this will direct iperf to send DATASIZE GB of " |
648 | + "data to the target.")) |
649 | + action.add_argument( |
650 | + '--runtime', type=int, |
651 | + default=60, |
652 | + help=("CANNOT BE USED WITH --datasize. Send data for *runtime* " |
653 | + "seconds. For iperf tests, this will send data for the amount " |
654 | + "of time indicated, rather than until a certain file size is " |
655 | + "reached.")) |
656 | + test_parser.add_argument( |
657 | + '--scan-timeout', type=int, |
658 | + default=60, |
659 | + help=("Sets the maximum time, in seconds, the test will scan for " |
660 | + "iperf servers before giving up.")) |
661 | + test_parser.add_argument( |
662 | + '--config', type=str, |
663 | + default="/etc/checkbox.d/network.cfg", |
664 | + help="Supply config file for target/host network parameters") |
665 | + test_parser.add_argument( |
666 | + '--fail-threshold', type=int, |
667 | + default=40, |
668 | + help=("IPERF Test ONLY. Set the failure threshold (Percent of maximum " |
669 | + "theoretical bandwidth) as a number like 80. (Default is " |
670 | + "%(default)s)")) |
671 | + test_parser.add_argument( |
672 | + '--cpu-load-fail-threshold', type=int, |
673 | + default=100, |
674 | + help=("(IPERF Test ONLY and meaningful ONLY with --iperf3. Set the " |
675 | + "failure threshold (above which the CPU load must not rise) as " |
676 | + "a number like 80. (Default is %(default)s)")) |
677 | + test_parser.add_argument( |
678 | + '--num_runs', type=int, |
679 | + default=1, |
680 | + help=("Number of times to run the test. (Default is %(default)s)")) |
681 | + test_parser.add_argument( |
682 | + '--debug', default=False, action="store_true", |
683 | + help="Turn on verbose output") |
684 | + |
685 | + # Sub info options |
686 | + info_parser.add_argument( |
687 | + '-i', '--interface', type=str, required=True) |
688 | + info_parser.add_argument( |
689 | + '--all', default=False, action="store_true") |
690 | + info_parser.add_argument( |
691 | + '--duplex-mode', default=False, action="store_true") |
692 | + info_parser.add_argument( |
693 | + '--link-speed', default=False, action="store_true") |
694 | + info_parser.add_argument( |
695 | + '--max-speed', default=False, action="store_true") |
696 | + info_parser.add_argument( |
697 | + '--ipaddress', default=False, action="store_true") |
698 | + info_parser.add_argument( |
699 | + '--netmask', default=False, action="store_true") |
700 | + info_parser.add_argument( |
701 | + '--device-name', default=False, action="store_true") |
702 | + info_parser.add_argument( |
703 | + '--macaddress', default=False, action="store_true") |
704 | + info_parser.add_argument( |
705 | + '--status', default=False, action="store_true", |
706 | + help=("displays connection status")) |
707 | + info_parser.add_argument( |
708 | + '--debug', default=False, action="store_true", |
709 | + help="Turn on verbose output") |
710 | + |
711 | + test_parser.set_defaults(func=interface_test) |
712 | + info_parser.set_defaults(func=interface_info) |
713 | + |
714 | + args = parser.parse_args() |
715 | + if (args.func.__name__ is interface_test and |
716 | + not args.cpu_load_fail_threshold != 100 and |
717 | + not args.iperf3): |
718 | + parser.error('--cpu-load-fail-threshold can only be set with ' |
719 | + '--iperf3.') |
720 | + |
721 | + if args.debug: |
722 | + logging.basicConfig(level=logging.DEBUG) |
723 | + else: |
724 | + logging.basicConfig(level=logging.INFO) |
725 | + |
726 | + if 'func' not in args: |
727 | + parser.print_help() |
728 | + else: |
729 | + return args.func(args) |
730 | + |
731 | + |
732 | +if __name__ == "__main__": |
733 | + sys.exit(main()) |
734 | diff --git a/setup.py b/setup.py |
735 | index 6fcfef8..86866ad 100755 |
736 | --- a/setup.py |
737 | +++ b/setup.py |
738 | @@ -80,6 +80,8 @@ setup( |
739 | "checkbox_support.scripts.run_watcher:main"), |
740 | ("checkbox-support-fwts_test=" |
741 | "checkbox_support.scripts.fwts_test:main"), |
742 | + ("checkbox-support-network=" |
743 | + "checkbox_support.scripts.network:main"), |
744 | ], |
745 | }, |
746 | ) |
Launchpad encountered an internal error during the following operation: generating the diff for a merge proposal. It was logged with id OOPS-08e58488e719650225fdf09e931e7103. Sorry for the inconvenience.
ugh