Merge ~kissiel/checkbox-support:add-network-tool into checkbox-support: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)
Reviewer Review Type Date Requested Status
Sylvain Pineau (community) Needs Fixing
Jonathan Cave (community) Approve
Review via email: mp+315209@code.launchpad.net

Description of the change

Include 'network' test script in checkbox-script.

It's now used in plainbox-provider-checkbox, and soon, plainbox-provider-snappy. Instead of having to copy and paste this script around, let's have it in checkbox-support.

To post a comment you must log in.
Revision history for this message
Maciej Kisielewski (kissiel) wrote :

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

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
1diff --git a/checkbox_support/scripts/network.py b/checkbox_support/scripts/network.py
2new file mode 100755
3index 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())
734diff --git a/setup.py b/setup.py
735index 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 )

Subscribers

People subscribed via source and target branches