~opencompute-developers/opencompute/checkbox

« back to all changes in this revision

Viewing changes to scripts/network

Updated OCP Checkbox to the final release version of Checkbox which is now in Legacy mode.

Also fixed some unittest failures found during the update.

Incremented release version to match the last release version of Checkbox.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/env python3
2
2
"""
3
 
Copyright (C) 2012 Canonical Ltd.
 
3
Copyright (C) 2012-2014 Canonical Ltd.
4
4
 
5
5
Authors
6
6
  Jeff Marcom <jeff.marcom@canonical.com>
 
7
  Daniel Manrique <roadmr@ubuntu.com>
7
8
 
8
9
This program is free software: you can redistribute it and/or modify
9
10
it under the terms of the GNU General Public License version 3,
53
54
            self,
54
55
            interface,
55
56
            target,
 
57
            fail_threshold,
56
58
            protocol="tcp",
57
59
            mbytes="1024M"):
58
60
 
59
61
        self.iface = Interface(interface)
60
62
        self.target = target
61
63
        self.protocol = protocol
 
64
        self.fail_threshold = fail_threshold
62
65
 
63
66
        self.mbytes = mbytes
64
67
 
86
89
 
87
90
        # 930 Mbits/sec\n'
88
91
        print(iperf_return)
89
 
        match = re.search(r'\d+\s([GM])bits', iperf_return)
 
92
        match = re.search(r'[\d\.]+\s([GM])bits', iperf_return)
90
93
        if match:
91
94
            throughput = match.group(0).split()[0]
92
95
            units = match.group(1)
93
96
            # self.iface.max_speed is always in mb/s, so we need to scale
94
97
            # throughput to match
95
 
            scaled_throughput = int(throughput)
 
98
            scaled_throughput = float(throughput)
96
99
            if units == 'G':
97
100
                scaled_throughput *= 1000
98
101
            if units == 'K':
99
102
                scaled_throughput /= 1000
100
 
            percent = scaled_throughput / int(self.iface.max_speed) * 100
101
 
            print("Transfer speed: {} {}b/s".format(throughput, units))
 
103
            try:
 
104
                percent = scaled_throughput / int(self.iface.max_speed) * 100
 
105
            except (ZeroDivisionError, TypeError) as error:
 
106
                # Catches a condition where the interface functions fine but
 
107
                # ethtool fails to properly report max speed. In this case
 
108
                # it's up to the reviewer to pass or fail.
 
109
                logging.error("Max Speed was not reported properly.  Run "
 
110
                              "ethtool and verify that the card is properly "
 
111
                              "reporting its capabilities.")
 
112
                logging.error(error)
 
113
                percent = 0
 
114
 
 
115
            print("\nTransfer speed: {} {}b/s".format(throughput, units))
102
116
            print("%3.2f%% of " % percent, end="")
103
 
            print("theoretical max %sMb/s" % int(self.iface.max_speed))
 
117
            try:
 
118
                print("theoretical max %sMb/s\n" % int(self.iface.max_speed))
 
119
            except TypeError as error:
 
120
                logging.error("Max Speed was not reported properly.  Run "
 
121
                              "ethtool and verify that the card is properly "
 
122
                              "reporting its capabilities.")
 
123
                logging.error(error)
104
124
 
105
 
            if percent < 40:
 
125
            if percent < self.fail_threshold:
106
126
                logging.warn("Poor network performance detected")
107
127
                return 30
108
128
 
258
278
            return 1
259
279
 
260
280
 
 
281
class StressPerformanceTest:
 
282
 
 
283
    def __init__(self, interface, target):
 
284
        self.interface = interface
 
285
        self.target = target
 
286
 
 
287
    def run(self):
 
288
        iperf_cmd = 'timeout 320 iperf -c {} -t 300'.format(self.target)
 
289
        print("Running iperf...")
 
290
        iperf = subprocess.Popen(shlex.split(iperf_cmd))
 
291
 
 
292
        ping_cmd = 'ping -I {} {}'.format(self.interface, self.target)
 
293
        ping = subprocess.Popen(shlex.split(ping_cmd), stdout=subprocess.PIPE)
 
294
        iperf.communicate()
 
295
 
 
296
        ping.terminate()
 
297
        (out, err) = ping.communicate()
 
298
 
 
299
        if iperf.returncode != 0:
 
300
            return iperf.returncode
 
301
 
 
302
        print("Running ping test...")
 
303
        result = 0
 
304
        time_re = re.compile('(?<=time=)[0-9]*')
 
305
        for line in out.decode().split('\n'):
 
306
            time = time_re.search(line)
 
307
 
 
308
            if time and int(time.group()) > 2000:
 
309
                print(line)
 
310
                print("ICMP packet was delayed by > 2000 ms.")
 
311
                result = 1
 
312
            if 'unreachable' in line.lower():
 
313
                print(line)
 
314
                result = 1
 
315
 
 
316
        return result
 
317
 
 
318
 
261
319
class Interface(socket.socket):
262
320
    """
263
321
    Simple class that provides network interface information.
321
379
        return self._read_data("device/label")
322
380
 
323
381
 
 
382
def get_test_parameters(args, environ, config_filename):
 
383
    # Decide the actual values for test parameters, which can come
 
384
    # from one of three possible sources: a config file, command-line
 
385
    # arguments, or environment variables.
 
386
    # - If command-line args were given, they take precedence
 
387
    # - Next come environment variables, if set.
 
388
    # - Last, values in the config file are used if present.
 
389
 
 
390
    params = {"test_target_ftp": None,
 
391
              "test_user": None,
 
392
              "test_pass": None,
 
393
              "test_target_iperf": None}
 
394
 
 
395
    #First (try to) load values from config file
 
396
    config = configparser.SafeConfigParser()
 
397
 
 
398
    try:
 
399
        with open(config_filename) as config_file:
 
400
            config.readfp(config_file)
 
401
            params["test_target_ftp"] = config.get("FTP", "Target")
 
402
            params["test_user"] = config.get("FTP", "User")
 
403
            params["test_pass"] = config.get("FTP", "Pass")
 
404
            params["test_target_iperf"] = config.get("IPERF", "Target")
 
405
    except FileNotFoundError as err:
 
406
        pass  # No biggie, we can still get configs from elsewhere
 
407
 
 
408
    # Next see if we have environment variables to override the config file
 
409
    # "partial" overrides are not allowed; if an env variable is missing,
 
410
    # we won't use this at all.
 
411
    if all([param.upper() in os.environ for param in params.keys()]):
 
412
        for key in params.keys():
 
413
            params[key] = os.environ[key.upper()]
 
414
 
 
415
    # Finally, see if we have the command-line arguments that are the ultimate
 
416
    # override. Again, we will only override if we have all of them.
 
417
    if args.target and args.username and args.password:
 
418
        params["test_target_ftp"] = args.target
 
419
        params["test_user"] = args.username
 
420
        params["test_pass"] = args.password
 
421
        params["test_target_iperf"] = args.target
 
422
 
 
423
    return params
 
424
 
 
425
 
324
426
def interface_test(args):
325
427
    if not "test_type" in vars(args):
326
428
        return
327
429
 
 
430
    # Determine whether to use the default or user-supplied config
 
431
    # file name.
 
432
    DEFAULT_CFG = "/etc/checkbox.d/network.cfg"
 
433
    if not "config" in vars(args):
 
434
        config_filename = DEFAULT_CFG
 
435
    else:
 
436
        config_filename = args.config
 
437
 
 
438
    # Get the actual test data from one of three possible sources
 
439
    test_parameters = get_test_parameters(args, os.environ, config_filename)
 
440
 
 
441
    test_user = test_parameters["test_user"]
 
442
    test_pass = test_parameters["test_pass"]
 
443
    if (args.test_type.lower() == "iperf" or
 
444
            args.test_type.lower() == "stress"):
 
445
        test_target = test_parameters["test_target_iperf"]
 
446
    else:
 
447
        test_target = test_parameters["test_target_ftp"]
 
448
 
 
449
    # Validate that we got reasonable values
 
450
    if "example.com" in test_target:
 
451
        # Default values found in config file
 
452
        logging.error("Please supply target via: %s", config_filename)
 
453
        sys.exit(1)
 
454
 
 
455
    # Testing begins here!
 
456
    #
328
457
    # Check and make sure that interface is indeed connected
329
458
    try:
330
459
        cmd = "ip link set dev %s up" % args.interface
336
465
    # Give interface enough time to get DHCP address
337
466
    time.sleep(10)
338
467
 
339
 
    # Open Network config file
340
 
    DEFAULT_CFG = "/etc/checkbox.d/network.cfg"
341
 
    if not "config" in vars(args):
342
 
        config_file = DEFAULT_CFG
343
 
    else:
344
 
        config_file = args.config
345
 
 
346
 
    config = configparser.SafeConfigParser()
347
 
    config.readfp(open(config_file))
348
 
 
349
 
    # Set default network config options
350
 
    test_target = args.target
351
 
    test_user = args.username
352
 
    test_pass = args.password
353
 
 
354
 
    if test_target is None:
355
 
        # Set FTP parameters based on config file
356
 
        test_target = config.get("FTP", "Target")
357
 
        test_user = config.get("FTP", "User")
358
 
        test_pass = config.get("FTP", "Pass")
359
 
 
360
 
        if args.test_type.lower() == "iperf":
361
 
            test_target = config.get("IPERF", "Target")
362
 
 
363
 
        if "example.com" in test_target:
364
 
            # Default values found in config file
365
 
            logging.error("Please supply target via: %s", config_file)
366
 
            sys.exit(1)
367
 
 
368
 
 
369
468
    result = 0
370
469
    # Stop all other interfaces
371
470
    extra_interfaces = \
392
491
            result = ftp_benchmark.run()
393
492
 
394
493
        elif args.test_type.lower() == "iperf":
395
 
            iperf_benchmark = IPerfPerformanceTest(args.interface, test_target)
 
494
            iperf_benchmark = IPerfPerformanceTest(args.interface, test_target, 
 
495
                                                   args.fail_threshold)
396
496
            result = iperf_benchmark.run()
397
 
    
 
497
 
 
498
        elif args.test_type.lower() == "stress":
 
499
            stress_benchmark = StressPerformanceTest(args.interface,
 
500
                                                     test_target)
 
501
            result = stress_benchmark.run()
 
502
 
398
503
    for iface in extra_interfaces:
399
504
        logging.debug("Restoring interface:%s", iface)
400
505
        try:
404
509
            logging.error("Failed to use %s:%s", cmd, interface_failure)
405
510
            result = 3
406
511
 
407
 
    sys.exit(result)
 
512
    return result
 
513
 
408
514
 
409
515
def interface_info(args):
410
516
 
425
531
 
426
532
def main():
427
533
 
428
 
    intro_message = "Network module\n\nThis script provides benchmarking " \
429
 
    + "and information for a specified network interface.\n\n\n" \
430
 
    + "Example NIC information usage:\nnetwork info -i eth0 --max-speed " \
431
 
    + "\n\nFor running ftp benchmark test: \nnetwork test -i eth0 -t ftp " \
432
 
    + "--target 192.168.0.1 --username USERID --password PASSW0RD " \
433
 
    + "--filesize-2\n\nPlease note that this script can use configuration " \
434
 
    + "values supplied via a config file.\nExample config file:\n[FTP]\n" \
435
 
    + "Target: 192.168.1.23\nUser: FTPUser\nPass:PassW0Rd\n" \
436
 
    + "[IPERF]\nTarget: 192.168.1.45\n**NOTE**\nDefault config location " \
437
 
    + "is /etc/checkbox.d/network.cfg"
438
 
 
 
534
    intro_message = """
 
535
Network module
 
536
 
 
537
This script provides benchmarking and information for a specified network
 
538
interface.
 
539
 
 
540
Example NIC information usage:
 
541
network info -i eth0 --max-speed
 
542
 
 
543
For running ftp benchmark test:
 
544
network test -i eth0 -t ftp
 
545
--target 192.168.0.1 --username USERID --password PASSW0RD
 
546
--filesize-2
 
547
 
 
548
Configuration
 
549
=============
 
550
 
 
551
Configuration can be supplied in three different ways, with the following
 
552
priorities:
 
553
 
 
554
1- Command-line parameters (see above).
 
555
2- Environment variables (example will follow).
 
556
3- Configuration file (example will follow).
 
557
   Default config location is /etc/checkbox.d/network.cfg
 
558
 
 
559
Environment variables
 
560
=====================
 
561
ALL environment variables must be defined, even if empty, for them to be
 
562
picked up. The variables are:
 
563
TEST_TARGET_FTP
 
564
TEST_USER
 
565
TEST_PASS
 
566
TEST_TARGET_IPERF
 
567
 
 
568
example config file
 
569
===================
 
570
[FTP]
 
571
 
 
572
Target: 192.168.1.23
 
573
User: FTPUser
 
574
Pass:PassW0Rd
 
575
 
 
576
[IPERF]
 
577
Target: 192.168.1.45
 
578
**NOTE**
 
579
 
 
580
"""
439
581
 
440
582
    parser = ArgumentParser(
441
583
        description=intro_message, formatter_class=RawTextHelpFormatter)
451
593
    test_parser.add_argument(
452
594
        '-i', '--interface', type=str, required=True)
453
595
    test_parser.add_argument(
454
 
        '-t', '--test_type', type=str, 
455
 
        choices=("ftp", "iperf"), default="ftp",
 
596
        '-t', '--test_type', type=str,
 
597
        choices=("ftp", "iperf", "stress"), default="ftp",
456
598
        help=("[FTP *Default*]"))
457
599
    test_parser.add_argument('--target', type=str)
458
600
    test_parser.add_argument(
466
608
        '--config', type=str,
467
609
        default="/etc/checkbox.d/network.cfg",
468
610
        help="Supply config file for target/host network parameters")
 
611
    test_parser.add_argument(
 
612
        '--fail-threshold', type=int,
 
613
        default=40,
 
614
        help=("IPERF Test ONLY. Set the failure threshold (Percent of maximum "
 
615
              "theoretical bandwidth) as a number like 80.  (Default is "
 
616
              "%(default)s)"))
469
617
 
470
618
    # Sub info options
471
619
    info_parser.add_argument(
493
641
 
494
642
    args = parser.parse_args()
495
643
 
496
 
    args.func(args)
 
644
    return args.func(args)
497
645
 
498
646
 
499
647
if __name__ == "__main__":
500
 
    main()
 
648
    sys.exit(main())