diff --git a/.gitignore b/.gitignore index 7c517bb8..93dbe70a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ coverage.xml *.pot # Django stuff: -*.log local_settings.py # Sphinx documentation @@ -73,3 +72,18 @@ results/ # ctas TAGS +tags + +# Alternate config files +*.json + +# Apple foo +.AppleDouble +.DS_Store + +# Patch foo +*.rej +*.orig + +# geckodriver log +geckodriver.log diff --git a/.jenkinsfile b/.jenkinsfile new file mode 100644 index 00000000..66b631ab --- /dev/null +++ b/.jenkinsfile @@ -0,0 +1,40 @@ +def changeBranch = "change-${GERRIT_CHANGE_NUMBER}-${GERRIT_PATCHSET_NUMBER}" + +pipeline { + agent { label 'boardfarm' } + + stages { + stage('checkout gerrit change') { + steps { + // TODO: don't hardcode user name + git url: 'ssh://mattsm@review.gerrithub.io:29418/lgirdk/boardfarm' + sh "git fetch origin ${GERRIT_REFSPEC}:${changeBranch}" + sh "git rebase --abort || true" + sh "git reset --hard HEAD" + sh "git checkout ${changeBranch}" + sh "git rebase origin/master" + } + } + stage('run bft test') { + steps { + ansiColor('xterm') { + sh 'git show HEAD' + sh 'pip install --user -r requirements.txt' + sh 'yes | BFT_DEBUG=y ./bft -b qemux86-openwrt -r http://c4.lgirdk.com/~mattsm/openwrt-x86-generic-combined-ext4.vmdk -y -x travisci' + sh 'grep tests_fail...0, results/test_results.json' + } + } + } + stage('post results to gerrit') { + steps { + sh '''#!/bin/bash + cat results/test_results.json | jq '.test_results[] | [ .grade, .name, .message, .elapsed_time ] | @tsv' | \ + sed -e 's/"//g' -e 's/\\t/ /g' | \ + while read -r line; do + echo $line >> message + done + ''' + } + } + } +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..2d2a50e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +dist: trusty +sudo: required +language: python +python: + - "2.7" +before_install: + - sudo apt-get -qq update + - sudo apt-get remove docker docker-engine docker.io + - sudo apt-get install apt-transport-https ca-certificates curl software-properties-common + - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + - sudo apt-get -qq update + - sudo apt-get install -y qemu-system-x86 docker-ce python-tk + - sudo apt-get install -y libsnmp-dev snmp-mibs-downloader +script: + - ./bft -l + - ./bft -i + - docker build -t bft:node boardfarm/bft-node + - yes | ./bft -b qemux86-openwrt -r https://www.dropbox.com/s/ak58icpzta1f0if/openwrt-x86-generic-combined-ext4.vmdk?dl=0 -y -x travisci + - grep tests_fail...0, results/test_results.json + - cat results/all.log +after_failure: + - yes | BFT_DEBUG=y ./bft -b qemux86-openwrt -r https://www.dropbox.com/s/ak58icpzta1f0if/openwrt-x86-generic-combined-ext4.vmdk?dl=0 -y -x travisci + - cat results/all.log diff --git a/README.md b/README.md index d08eeed4..64f52444 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,46 @@ Software Setup If you are a Linux user, and wish to run and/or develop tests, please clone this repository and then install needed libaries: ```shell -apt-get install python-pip curl +apt-get install python-pip curl python-tk libsnmp-dev snmp-mibs-downloader cd openwrt/ pip install -r requirements.txt ``` The file `config.py` is the main configuration. For example, it sets the location of the file that describes the available hardware. +### Compiling Qemu to simulate devices + +[Qemu](https://www.qemu.org/) does full system simulation, and boardfarm can use that tool to run tests on simulated devices (e.g. a simulated OpenWrt router). If you want to use qemu, you will want a recent version (3.0 or higher). You may have to compile on your own system, and if so here's how: + +```sh +sudo apt-get update +sudo apt-get install build-essential zlib1g-dev pkg-config \ + libglib2.0-dev binutils-dev libboost-all-dev autoconf \ + libtool libssl-dev libpixman-1-dev libpython-dev python-pip \ + python-capstone virtualenv wget +wget https://download.qemu.org/qemu-4.0.0.tar.xz +tar xvJf qemu-4.0.0.tar.xz +cd qemu-4.0.0 +./configure +make +sudo make install +``` + +### Getting Docker for simulated devices + +Docker is also useful for creating simulated devices for running tests. To get the latest stable docker and set it up correctly: + +```sh +sudo apt-get update +sudo apt-get install apt-transport-https ca-certificates curl software-properties-common +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - +sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" +sudo apt-get update +sudo apt-get install -y docker-ce +# Add your user to the docker group so that you can run docker without sudo +sudo usermod -aG docker $USER +``` + Hardware Setup -------------- @@ -230,4 +263,39 @@ The best automated tests share a few qualities: The goal is to catch bugs in the software being tested. It is an annoying distraction when tests themselves crash. Keep your tests simple so that others can easily figure them out. +Boardfarm overlays +----------------- + +Recently support for having overlays in a separate directory (e.g. a separat +git repository was added. This allows your own private tests, boards, and +testsuites, templates that you want to keep separate for whatever reason. +It's expected that if this is not private you can create a git repo and share +your layer with others. + +The layout of the overlay will look exactly the same as the boardfarm repo: + +``` +my_overlay/ +├── devices +│   ├── my_device.py +├── html +│   └── template_results.html +├── tests +│   ├── this.py +│   ├── that.py +│   └── foobar.py +└── testsuites.cfg +``` + +Tests, devices, and testsuites in your overlay are added to the available +tests, devices, testsuites. The email templates replace the ones from the main +boardfarm repo + +To specify an over lay you simply need to the space separated overlays to the +BFT_OVERLAY environment variable: + +export BFT_OVERLAY="../boardfarm-foo/ ../boardfarm-bar" + +Then after running bft you should have access to your new tests, device types, etc + Good luck and thanks for reading! diff --git a/bft b/bft deleted file mode 100755 index 49d48738..00000000 --- a/bft +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import datetime -import inspect -import os -import random -import sys -import unittest2 -import junitxml -import json - -# Put this directory into the python path, so -# that devices may be imported. -import site -site.addsitedir(os.path.dirname(os.path.realpath(__file__))) - -from devices import board_decider, debian, logstash, elasticlogger - -def setup_legacy_devices(config): - '''This will setup legacy hard coded devices to into the config''' - - config.wan = None - config.lan = None - config.wlan = None - config.wlan5g = None - config.wlan2g = None - - if config.board.get('wan_device'): - config.wan = debian.DebianBox(config.board.get('wan_device'), - color='cyan', reboot=config.reboot_vms, - location=config.board.get('location'), - username=config.board.get('wan_username', "root"), - password=config.board.get('wan_password', "bigfoot1"), - port=config.board.get('wan_port', "22")) - if config.board.get('lan_device'): - config.lan = debian.DebianBox(config.board.get('lan_device'), color='blue', reboot=config.reboot_vms, - username=config.board.get('lan_username', "root"), - password=config.board.get('lan_password', "bigfoot1"), - port=config.board.get('lan_port', "22")) - if config.board.get('wlan_device'): - config.wlan = debian.DebianBox(config.board.get('wlan_device'), color='green', reboot=config.reboot_vms, - username=config.board.get('wlan_username', "root"), - password=config.board.get('wlan_password', "bigfoot1"), - port=config.board.get('wlan_port', "22")) - if config.board.get('5g_device'): - config.wlan5g = debian.DebianBox(config.board.get('5g_device'), color='grey', reboot=config.reboot_vms, - username=config.board.get('5g_username', "root"), - password=config.board.get('5g_password', "bigfoot1"), - port=config.board.get('5g_port', "22")) - if config.board.get('2g_device'): - config.wlan2g = debian.DebianBox(config.board.get('2g_device'), color='magenta', reboot=config.reboot_vms, - username=config.board.get('2g_username', "root"), - password=config.board.get('2g_password', "bigfoot1"), - port=config.board.get('2g_port', "22")) - - -def setup_dynamic_devices(config): - '''Sets up dynamic devices from devices node in JSON config file''' - config.devices = [] - for device in config.board['devices']: - if device['type'] == 'debian': - d = debian.DebianBox(name=device.get('ipaddr', None), - color=device.get('color', 'black'), - reboot=config.reboot_vms, - username=device.get('username', "root"), - password=device.get('password', "bigfoot1"), - port=device.get('port', "22"), - pre_cmd_host=device.get('pre_cmd_host', None), - cmd=device.get('cmd', None), - post_cmd_host=device.get('post_cmd_host', None), - post_cmd=device.get('post_cmd', None), - cleanup_cmd=device.get('cleanup_cmd', None)) - setattr(config, device['name'], d) - config.devices.append(device['name']) - else: - print("Unknown device type for %d", device) - -def main(): - '''Connect to devices, run tests, record results.''' - - # Read command-line arguments - import arguments - config = arguments.parse() - - import library - import devices - from termcolor import colored - from library import print_bold - - # Connect to any board in list - connected_to_board = False - random.shuffle(config.BOARD_NAMES) - for name in config.BOARD_NAMES: - try: - config.board = config.boardfarm_config[name] - except Exception as e: - print(e) - print("Error reading info about board %s from board farm configuration." % name) - break - - print_bold("Connecting to board named = %s, type = %s ..." % (name, config.board['board_type'])) - try: - # If we find a dynamic device with the tftpd-server option let's use that for the server - # otherwise we use the device with the 'wan' name... finally if we don't find an ip address let's - # manually figure it out later - tftp_server = None - tftp_port = "22" - try: - if 'devices' in config.board: - tftp_dev = [ x for x in config.board['devices'] if 'tftpd-server' in x.get('options', "") ] - if len(tftp_dev) == 1: - tftp_server = tftp_dev[0]['ipaddr'] - tftp_port = tftp_dev[0].get('port', '22') - else: - print("WARN: more than one TFTP server is configured, please pick one") - elif tftp_server is None: - if len(tftp_dev) == 1: - tftp_dev = [ x for x in config.board['devices'] if x['name'] == 'wan' ] - tftp_server = tftp_dev[0]['ipaddr'] - tftp_port = tftp_dev[0].get('port', '22') - else: - print("WARN: more than one TFTP server is configured, please pick one") - except: - # if not by ipaddr let's connect to the lan device later and update - pass - - # Connect to board - config.console = board_decider.board(config.board['board_type'], - conn_cmd=config.board['conn_cmd'], - power_ip=config.board.get('powerip', None), - power_outlet=config.board.get('powerport', None), - web_proxy=config.board.get('lan_device', None), - tftp_server=config.board.get('wan_device', tftp_server), - tftp_username=config.board.get('wan_username', 'root'), - tftp_password=config.board.get('wan_password', 'bigfoot1'), - tftp_port=config.board.get('wan_port', tftp_port), - connection_type=config.board.get('connection_type', None), - ssh_password=config.board.get('ssh_password', None), - power_username=config.board.get('power_username', None), - power_password=config.board.get('power_password', None)) - print_bold("dut device console = %s" % colored("black", 'grey')) - - setup_legacy_devices(config) - - if 'devices' in config.board: - setup_dynamic_devices(config) - - # legacy wan device - if tftp_server is None and hasattr(config, 'wan'): - saved = config.console.logfile_read - config.console.logfile_read = None - config.console.tftp_server = config.wan.get_ip_addr("eth0") - config.console.tftp_username = "root" - config.console.tftp_password = "bigfoot1" - config.console.tftp_port = "22" - config.console.logfile_read = saved - - except Exception as e: - print(e) - connected_to_board = False - continue - connected_to_board = True - break - if not connected_to_board: - print_bold("Failed to connect to any board") - sys.exit(2) - - try: - print_bold("Using Board %s, User %s" % (name, os.environ['BUILD_USER_ID'])) - except: - print_bold("Using Board %s, User %s" % (name, os.environ['USER'])) - - # Make devices (board, lan, wan, available to tests easily) - devices.initialize_devices(config) - - # Write board info to json file and stdout - config.board['station'] = name - print_bold('\n==========') - library.print_board_info(config.board) - - # Run tests - os.environ['TEST_START_TIME'] = datetime.datetime.now().strftime("%s") - result_name = os.path.join(config.output_dir + "test_results.xml") - result_file = open(result_name, "w") - result = junitxml.JUnitXmlResult(result_file) - result.startTestRun() - tests_to_run = [] - suite = unittest2.TestSuite() - # Add tests from specified suite - print_bold('==========\nTest suite "%s" has been specified, will attempt to run tests:' % config.TEST_SUITE) - import tests - import testsuites - for i, name in enumerate(testsuites.list_tests[config.TEST_SUITE]): - if isinstance(name, str): - test = getattr(tests, name) - else: - test = name - print_bold(" %s %s from %s" % (i+1, test.__name__, inspect.getfile(test))) - tests_to_run.append(test(config)) - if hasattr(config, 'EXTRA_TESTS') and config.EXTRA_TESTS: - if tests_to_run[-1].__class__.__name__ == "Interact": - print_bold("Last test is interact in testsuite, removing") - tests_to_run.pop() - - print_bold("Extra tests specified on command line:") - try: - for t in [getattr(tests, name) for name in config.EXTRA_TESTS]: - print_bold(" %s" % t) - tests_to_run.append(t(config)) - except: - print_bold("Unable to find specified extra tests, aborting...") - sys.exit(1) - - for x in tests_to_run: - suite.addTest(x) - - print_bold('==========') - try: - print_bold(suite.run(result)) - except KeyboardInterrupt: - print_bold("Run interrupted. Wrapping up...") - result.stopTestRun() - try: - config.console.close() - config.lan.close() - config.wan.close() - except Exception as e: - print(e) - print_bold("For some reason, could not close a connection.") - print_bold("Wrote %s" % result_name) - library.print_board_info(config.board) - result_file.close() - - with open(os.path.join(config.output_dir, 'console.log'), 'w') as clog: - clog.write(config.console.log) - - os.environ['TEST_END_TIME'] = datetime.datetime.now().strftime("%s") - - # Write test result messages to a file - full_results = library.process_test_results(tests_to_run) - json.dump(full_results, - open(os.path.join(config.output_dir + 'test_results.json'), 'w'), - indent=4, - sort_keys=True) - - # run all analysis classes (post processing) - # also, never fail so we don't block automation - try: - import analysis - for cstr in dir(analysis): - c = getattr(analysis, cstr) - if inspect.isclass(c) and issubclass(c, analysis.Analysis): - c().analyze(config.console.log, config.output_dir) - except Exception as e: - if not issubclass(type(e), (StopIteration)): - print("Failed to run anaylsis:") - print(e) - - # Try to remotely log information about this run - info_for_remote_log = dict(config.board) - info_for_remote_log.update(full_results) - try: - info_for_remote_log['duration'] = int(os.environ['TEST_END_TIME'])-int(os.environ['TEST_START_TIME']) - except: - pass - if hasattr(config, 'TEST_SUITE'): - info_for_remote_log['test_suite'] = str(config.TEST_SUITE) - # logstash cannot handle multi-level json, remove full test results - info_for_remote_log.pop('test_results', None) - # but we will add back specific test results data - for t in tests_to_run: - if hasattr(t, 'override_kibana_name'): - n = t.override_kibana_name - else: - n = t.__class__.__name__ - for k, v in t.logged.items(): - info_for_remote_log[n + '-' + k] = v - if hasattr(t, 'result_grade'): - info_for_remote_log[n + "-result"] = t.result_grade - - try: - if config.logging_server is not None: - logstash.RemoteLogger(config.logging_server).log(info_for_remote_log) - except Exception as e: - print(e) - print("Unable to access logging_server specified in config. " - "Results stored only locally.") - - try: - if config.elasticsearch_server is not None: - elasticlogger.ElasticsearchLogger(config.elasticsearch_server).log(info_for_remote_log) - else: - print("No elasticsearch_server specified in config. Results stored locally") - except Exception as e: - print(e) - print("Unable to store results to elasticsearch_server specified in config. " - "Results stored locally.") - - # Create Pretty HTML output - import make_human_readable - try: - title_str = make_human_readable.get_title() - make_human_readable.xmlresults_to_html(full_results['test_results'], title=title_str, - output_name=os.path.join(config.output_dir, "results.html"), - board_info=config.board) - except Exception as e: - print(e) - print("Unable to create HTML results") - - # Send url of pretty html results to MySQL build database - try: - library.send_results_to_myqsl(config.TEST_SUITE, config.output_dir) - except Exception as e: - print(e) - print("Unable to log results to mysql database.") - - -if __name__ == '__main__': - main() diff --git a/bft b/bft new file mode 120000 index 00000000..a80fd037 --- /dev/null +++ b/bft @@ -0,0 +1 @@ +boardfarm/bft \ No newline at end of file diff --git a/bft-node/Dockerfile b/bft-node/Dockerfile deleted file mode 100644 index 07af8978..00000000 --- a/bft-node/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM debian:8 - -RUN echo "root:bigfoot1" | chpasswd - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - iproute \ - net-tools \ - openssh-server \ - isc-dhcp-server \ - isc-dhcp-client \ - procps \ - iptables \ - lighttpd \ - tinyproxy \ - curl \ - apache2-utils \ - nmap \ - pppoe \ - tftpd-hpa \ - tcpdump \ - iperf \ - iperf3 \ - netcat - -RUN mkdir /var/run/sshd -RUN sed -i 's/PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config - -EXPOSE 22 diff --git a/boardfarm/__init__.py b/boardfarm/__init__.py new file mode 100644 index 00000000..81167843 --- /dev/null +++ b/boardfarm/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2019 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. diff --git a/analysis/__init__.py b/boardfarm/analysis/__init__.py similarity index 100% rename from analysis/__init__.py rename to boardfarm/analysis/__init__.py diff --git a/analysis/analysis.py b/boardfarm/analysis/analysis.py similarity index 72% rename from analysis/analysis.py rename to boardfarm/analysis/analysis.py index ffb0b258..27a34f0b 100644 --- a/analysis/analysis.py +++ b/boardfarm/analysis/analysis.py @@ -9,11 +9,11 @@ import re # no repr -newline = r"\r\n\[[^\]]+\] " -newline_match = r"\r\n\[([^\]]+)\] " +newline = r"\r\n\[[^\]]+\]" +newline_match = r"\r\n\[([^\]]+)\]" # with repr -newline_re = r"\\r\\n\[[^\]]+\] " -newline_re_match = r"\\r\\n\[([^\]]+)\] " +newline_re = r"\\r\\n\[[^\]]+\]" +newline_re_match = r"\\r\\n\[([^\]]+)\]" def prepare_log(log): '''Strips some stuff from outside logs so we can parse''' @@ -35,23 +35,27 @@ class Analysis(): def analyze(self, console_log, output_dir): pass - def make_graph(self, data, ylabel, fname, ts=None, xlabel="seconds since boot (probably)", output_dir=None): + def make_graph(self, data, ylabel, fname, ts=None, xlabel="seconds", output_dir=None): '''Helper function to make a PNG graph''' if not output_dir: return - import matplotlib as mpl - mpl.use('Agg') + # strings -> float for graphing + ts = [float(i) for i in ts] + try: + data = [float(i) for i in data] + except: + data = [int(i) for i in data] + import matplotlib.pyplot as plt - plt.gca().yaxis.set_major_formatter(mpl.ticker.FormatStrFormatter('%d')) - plt.gca().xaxis.set_major_formatter(mpl.ticker.FormatStrFormatter('%d')) if ts is None: plt.plot(data) else: - plt.plot(ts, data) + plt.plot(ts, data, marker='o') plt.ylabel(ylabel) plt.xlabel(xlabel) + plt.gcf().set_size_inches(12, 8) plt.savefig(os.path.join(output_dir, "%s.png" % fname)) plt.clf() diff --git a/analysis/connections.py b/boardfarm/analysis/connections.py similarity index 100% rename from analysis/connections.py rename to boardfarm/analysis/connections.py diff --git a/analysis/oom.py b/boardfarm/analysis/oom.py similarity index 100% rename from analysis/oom.py rename to boardfarm/analysis/oom.py diff --git a/analysis/panic.py b/boardfarm/analysis/panic.py similarity index 100% rename from analysis/panic.py rename to boardfarm/analysis/panic.py diff --git a/analysis/ps.py b/boardfarm/analysis/ps.py similarity index 100% rename from analysis/ps.py rename to boardfarm/analysis/ps.py diff --git a/analysis/sb_connections.py b/boardfarm/analysis/sb_connections.py similarity index 100% rename from analysis/sb_connections.py rename to boardfarm/analysis/sb_connections.py diff --git a/analysis/slab.py b/boardfarm/analysis/slab.py similarity index 100% rename from analysis/slab.py rename to boardfarm/analysis/slab.py diff --git a/analysis/vmstat.py b/boardfarm/analysis/vmstat.py similarity index 97% rename from analysis/vmstat.py rename to boardfarm/analysis/vmstat.py index d641bebe..fdb37fd1 100644 --- a/analysis/vmstat.py +++ b/boardfarm/analysis/vmstat.py @@ -19,6 +19,9 @@ def analyze(self, console_log, output_dir): data[k].append(int(v)) timestamps[k].append(float(t)) + if len(data) == 0: + return + sz = len(data.itervalues().next()) for k in data: if len(data[k]) > 1: diff --git a/arguments.py b/boardfarm/arguments.py similarity index 60% rename from arguments.py rename to boardfarm/arguments.py index 19ddcc91..b971c8c7 100755 --- a/arguments.py +++ b/boardfarm/arguments.py @@ -14,10 +14,13 @@ import sys import json import unittest2 +import traceback try: from urllib.request import urlopen + import urllib except: from urllib2 import urlopen + import urllib2 as urllib import re import library @@ -51,34 +54,39 @@ def parse(): usage='bft [options...]', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=HELP_EPILOG) - parser.add_argument('-l', '--list_tests', action='store_true', help='List available tests and exit') + parser.add_argument('-a', '--analysis', metavar='', type=str, default=None, help='Only run post processing analysis on logs') + parser.add_argument('-b', '--board_type', metavar='', type=str, nargs='+', default=None, help='MODEL(s) of board to connect to') + parser.add_argument('-c', '--config_file', metavar='', type=str, default=None, help='JSON config file for boardfarm') + parser.add_argument('-e', '--extend', metavar='', type=str, default=None, action="append", help='NAME of extra test to run') + parser.add_argument('-f', '--filter', metavar='', type=str, default=None, action="append", help='Regex filter off arbitrary board parameters') + parser.add_argument('-g', '--golden', metavar='', type=str, default=[], nargs='+', help='Path to JSON results to compare against (golden master)') parser.add_argument('-i', '--inventory', action='store_true', help='List available boards and exit') - parser.add_argument('-y', '--batch', action='store_true', help='Run in unattended mode - do not spawn console on failed test') - parser.add_argument('-t', '--retry', type=int, default=0, help='How many times to retry every test if it fails') parser.add_argument('-k', '--kernel', metavar='', type=str, default=None, help='URL or file PATH of Kernel image to flash') - parser.add_argument('-r', '--rootfs', metavar='', type=str, default=None, help='URL or file PATH of Rootfs image to flash') - parser.add_argument('--nfsroot', metavar='', type=str, default=None, help='URL or file PATH of Rootfs image to flash') + parser.add_argument('-l', '--list_tests', action='store_true', help='List available tests and exit') parser.add_argument('-m', '--meta_img_loc', metavar='', type=str, default=None, help='URL or file PATH to meta image to flash') + parser.add_argument('-n', '--board_names', metavar='', type=str, nargs='+', default=[], help='NAME(s) of boards to run on') + owrt_tests_dir = os.path.join(os.getcwd(), "results", '') + parser.add_argument('-o', '--output_dir', metavar='', type=str, default=owrt_tests_dir, help='Directory to output results files too') parser.add_argument('-p', '--package', metavar='', type=str, action="append", default=None, help='URL or file PATH of ipk install after boot') - parser.add_argument('-u', '--uboot', metavar='', type=str, default=None, help=argparse.SUPPRESS) + parser.add_argument('-q', '--feature', metavar='', type=str, default=[], nargs='+', help='Features required for this test run') + parser.add_argument('-r', '--rootfs', metavar='', type=str, default=None, help='URL or file PATH of Rootfs image to flash') parser.add_argument('-s', '--sysupgrade', metavar='', type=str, default=None, help='URL or file PATH to Sysupgrade image') - parser.add_argument('-x', '--testsuite', metavar='', type=str, default=None, help='NAME of test suite to run') - parser.add_argument('-e', '--extend', metavar='', type=str, default=None, action="append", help='NAME of extra test to run') - parser.add_argument('-n', '--board_names', metavar='', type=str, nargs='+', default=[], help='NAME(s) of boards to run on') - parser.add_argument('-b', '--board_type', metavar='', type=str, nargs='+', default=None, help='MODEL(s) of board to connect to') - parser.add_argument('-w', '--wan', metavar='', type=str, default='dhcp', help='WAN protocol, dhcp (default) or pppoe') + parser.add_argument('-t', '--retry', type=int, default=0, help='How many times to retry every test if it fails') + parser.add_argument('-u', '--uboot', metavar='', type=str, default=None, help=argparse.SUPPRESS) parser.add_argument('-v', '--reboot-vms', action="store_true", help='Reboot VMs before starting tests') - parser.add_argument('-f', '--filter', metavar='', type=str, default=None, action="append", help='Regex filter off arbitrary board parameters') - parser.add_argument('-a', '--analysis', metavar='', type=str, default=None, help='Only run post processing analysis on logs') - owrt_tests_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results", '') - parser.add_argument('-o', '--output_dir', metavar='', type=str, default=owrt_tests_dir, help='Directory to output results files too') + parser.add_argument('-w', '--wan', metavar='', type=str, default='dhcp', help='WAN protocol, dhcp (default) or pppoe') + parser.add_argument('-x', '--testsuite', metavar='', type=str, default=None, help='NAME of test suite to run') + parser.add_argument('-y', '--batch', action='store_true', help='Run in unattended mode - do not spawn console on failed test') parser.add_argument('-z', '--no-network', action='store_true', help='Skip basic network tests when booting') - parser.add_argument('-c', '--config_file', metavar='', type=str, default=boardfarm_config_location, help='JSON config file for boardfarm') + parser.add_argument('--bootargs', metavar='', type=str, default=None, help='bootargs to set or append to default args (board dependant)') + parser.add_argument('--nfsroot', metavar='', type=str, default=None, help='URL or file PATH of Rootfs image to flash') + parser.add_argument('--version', action='version', version='%(prog)s {}'.format(library.version), help='show version and exit') args = parser.parse_args() if args.list_tests: import tests + tests.init(config) # Print all classes that are a subclass of TestCase for e in dir(tests): thing = getattr(tests, e) @@ -91,16 +99,70 @@ def parse(): sys.exit(0) try: - if args.config_file.startswith("http"): - data = urlopen(args.config_file).read().decode() + if args.config_file is not None: + config.boardfarm_config_location = args.config_file + + if config.boardfarm_config_location.startswith("http"): + data = urlopen(config.boardfarm_config_location).read().decode() else: - data = open(args.config_file, 'r').read() + data = open(config.boardfarm_config_location, 'r').read() + config.boardfarm_config = json.loads(data) + + if "_redirect" in config.boardfarm_config and args.config_file is None: + print("Using boardfarm config file at %s" % config.boardfarm_config['_redirect']) + print("Please set your default config by doing:") + print(' export BFT_CONFIG="%s"' % config.boardfarm_config['_redirect']) + print("If you want to use local config, remove the _redirect line.") + data = urlopen(config.boardfarm_config['_redirect']).read().decode() + config.boardfarm_config_location = config.boardfarm_config['_redirect'] + config.boardfarm_config = json.loads(data) + + config.boardfarm_config.pop('_redirect', None) + + if 'locations' in config.boardfarm_config: + location = config.boardfarm_config['locations'] + del config.boardfarm_config['locations'] + + for board in config.boardfarm_config: + if 'location' in config.boardfarm_config[board]: + board_location = config.boardfarm_config[board]['location'] + if board_location in location: + for key, value in location[board_location].iteritems(): + if type(value) == list: + config.boardfarm_config[board][key].extend(value) + else: + config.boardfarm_config[board][key] = value + except Exception as e: print(e) print('Unable to access/read Board Farm configuration\n%s' % boardfarm_config_location) sys.exit(1) + # Check if boardfarm configuration is empty + if not config.boardfarm_config: + print("ERROR! Boardfarm config at %s is empty, so" % args.config_file) + print("either all stations are in use or disabled.") + sys.exit(10) + # Check if given board type(s) have any overlap with available board types from config + if args.board_type: + all_board_types = [config.boardfarm_config[key].get('board_type') for key in config.boardfarm_config] + if not (set(args.board_type) & set(all_board_types)): + print("ERROR! You specified board types: %s " % " ".join(args.board_type)) + print("but that is not an existing & available type of board.") + print("Please choose a board type from:") + print("\n".join([" * %s" % x for x in set(all_board_types)])) + sys.exit(10) + # Check if given board name(s) are present in available boards + if args.board_names: + all_board_names = [key for key in config.boardfarm_config if key != "locations"] + if not (set(args.board_names) & set(all_board_names)): + print("ERROR! You specified board names: %s " % " ".join(args.board_names)) + print("but that is not an existing & available board.") + print("Please choose a board name from:") + print("\n".join([" * %s" % x for x in sorted(all_board_names)])) + sys.exit(10) + config.batch = args.batch if args.inventory: @@ -143,8 +205,28 @@ def parse(): continue if x.startswith('http://') or x.startswith('https://'): try: + def add_basic_auth(login_str, request): + '''Adds Basic auth to http request, pass in login:password as string''' + import base64 + encodeuser = base64.b64encode(login_str.encode('utf-8')).decode("utf-8") + authheader = "Basic %s" % encodeuser + request.add_header("Authorization", authheader) + + import ssl + context = ssl._create_unverified_context() + + req = urllib.Request(x) + + try: + import netrc, urlparse + n = netrc.netrc() + login, unused, password = n.authenticators(urlparse.urlparse(x).hostname) + add_basic_auth("%s:%s" % (login, password), req) + except (TypeError, ImportError, IOError, netrc.NetrcParseError): + pass + # If url returns 404 or similar, raise exception - urlopen(x, timeout=20) + urlopen(req, timeout=20, context=context) except Exception as e: print(e) print('Error trying to access %s' % x) @@ -180,9 +262,16 @@ def parse(): for cstr in dir(analysis): c = getattr(analysis, cstr) if inspect.isclass(c) and issubclass(c, analysis.Analysis): + sys.stdout.write("Running analysis class = %s... " % c) console_log = open(args.analysis, 'r').read() from analysis.analysis import prepare_log - c().analyze(prepare_log(console_log), config.output_dir) + try: + c().analyze(prepare_log(console_log), config.output_dir) + print("DONE!") + except Exception as e: + print("FAILED!") + traceback.print_exc(file=sys.stdout) + continue exit(0) if args.board_type: @@ -199,6 +288,28 @@ def parse(): config.boardfarm_config[b]['available_for_autotests'] == False: # Skip this board continue + if args.feature != [] : + if 'feature' not in config.boardfarm_config[b]: + continue + features = config.boardfarm_config[b]['feature'] + if 'devices' in config.boardfarm_config[b]: + seen_names = [] + for d in config.boardfarm_config[b]['devices']: + if 'feature' in d: + # since we only connect to one type of device + # we need to ignore the features on the other ones + # even though they should be the same + if d['name'] in seen_names: + continue + seen_names.append(d['name']) + + if type(d['feature']) is str or type(d['feature']) is unicode: + d['feature'] = [d['feature']] + features.extend(x for x in d['feature'] if x not in features) + if type(features) is str or type(features) is unicode: + features = [features] + if set(args.feature) != set(args.feature) & set(features): + continue for t in args.board_type: if config.boardfarm_config[b]['board_type'].lower() == t.lower(): if args.filter: @@ -208,7 +319,7 @@ def parse(): config.BOARD_NAMES.append(b) if not config.BOARD_NAMES: print("ERROR! No boards meet selection requirements and have available_for_autotests = True.") - sys.exit(1) + sys.exit(10) else: if not args.board_names: print("ERROR") @@ -223,6 +334,9 @@ def parse(): config.WAN_PROTO = args.wan config.reboot_vms = args.reboot_vms config.setup_device_networking = not args.no_network + config.bootargs = args.bootargs + config.golden = args.golden + config.features = args.feature return config diff --git a/boardfarm/bft b/boardfarm/bft new file mode 100755 index 00000000..a474946d --- /dev/null +++ b/boardfarm/bft @@ -0,0 +1,543 @@ +#!/usr/bin/env python + +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import atexit +import hashlib +import inspect +import json +import os +import random +import sys +import time +import traceback +from datetime import datetime + +from zephyr import zephyr_reporter + +# Put this directory into the python path, so +# that devices may be imported. +import site +site.addsitedir(os.path.dirname(os.path.realpath(__file__))) + +from devices import get_device +from dbclients import logstash, elasticlogger, mongodblogger, boardfarmwebclient + +import matplotlib +matplotlib.use('Agg') + +def setup_dynamic_devices(config, env=None, start=None): + '''Sets up dynamic devices from devices node in JSON config file''' + + config.devices = [] + for device in config.board['devices']: + if device['name'] in config.devices: + print("Skipping duplicate device type: %s" % device['name']) + continue + + device['reboot'] = config.reboot_vms + device['env'] = env + device['lan_network'] = config.console.lan_network + device['lan_gateway'] = config.console.lan_gateway + device['start'] = start + + s = time.time() + dyn_dev = get_device(device['type'], **device) + if 'BFT_DEBUG' in os.environ: + print("Time to instantiate device = %s" % (time.time() - s)) + + def create_device_helper(name, dev): + setattr(config, name, dev) + config.devices.append(name) + + # TODO: should this be here for should each device type set it? + dev.start = start + + # TODO: set the following: + # reboot=config.reboot_vms, + # env=env, + # lan_network=config.console.lan_network, + # lan_gateway=config.console.lan_gateway, + # config=device) + + # if this device is a wan cmts provisioner, we set the device name + # TODO: this should be generic + if getattr(dev, 'wan_cmts_provisioner', False): + setattr(config, 'provisioner', dev) + config.devices.append('provisioner') + + if dyn_dev is not None: + if 'name' not in device: + raise Exception("Device in config is not named! This is required") + create_device_helper(device['name'], dyn_dev) + + for dev in getattr(dyn_dev, 'extra_devices', []): + if hasattr(dev, 'name'): + create_device_helper(dev.name, dev) + else: + raise Exception("Extra device in config is not named! This is required") + + continue + + print("Unknown device type for %s" % device) + +def main(): + '''Connect to devices, run tests, record results.''' + + # Read command-line arguments + import arguments + config = arguments.parse() + + import library + import devices + from termcolor import colored + from library import print_bold + + os.environ["TERM"] = "dumb" + + start = datetime.now() + + # Setup boardfarm client even if config is a local file (in which + # case this will do nothing) + bfweb = boardfarmwebclient.BoardfarmWebClient(config.boardfarm_config_location, + bf_version=library.version, + debug=os.environ.get("BFT_DEBUG", False)) + + # Connect to any board in list + connected_to_board = False + random.shuffle(config.BOARD_NAMES) + + def sortFunc(x): + # TODO: add configurable priorities for each type of feature + # e.g. wifi is probably one we never want to use unless requested + if 'feature' in config.boardfarm_config[x]: + if type(config.boardfarm_config[x]['feature']) is list: + return len(config.boardfarm_config[x]['feature']) + else: + return 1 + else: + return -1 + + # move boards with a feature to end of the list + config.BOARD_NAMES = sorted(config.BOARD_NAMES, key=sortFunc) + + for name in config.BOARD_NAMES: + try: + config.board = config.boardfarm_config[name] + except Exception as e: + print(e) + print("Error reading info about board %s from board farm configuration." % name) + break + + print_bold("Connecting to board named = %s, type = %s ..." % (name, config.board['board_type'])) + try: + # None is legacy for tftp_server from before dynamic devices, leave it for now... + tftp_server = None + tftp_port = "22" + + uniqid = hashlib.md5("%0.100f" % time.time()).hexdigest()[:15] + env = {"wan_iface": "wan%s" % uniqid[:12], + "lan_iface": "lan%s" % uniqid[:12], + "uniq_id": uniqid} + + + # Connect to board + config.console = devices.board_decider(config.board['board_type'], + conn_cmd=config.board['conn_cmd'], + power_ip=config.board.get('powerip', None), + power_outlet=config.board.get('powerport', None), + web_proxy=config.board.get('lan_device', None), + tftp_server=config.board.get('wan_device', tftp_server), + tftp_username=config.board.get('wan_username', 'root'), + tftp_password=config.board.get('wan_password', 'bigfoot1'), + tftp_port=config.board.get('wan_port', tftp_port), + connection_type=config.board.get('connection_type', None), + ssh_password=config.board.get('ssh_password', None), + power_username=config.board.get('power_username', None), + power_password=config.board.get('power_password', None), + rootfs=config.ROOTFS, + kernel=config.KERNEL, + config=config.board, + env=env, + start=start) + print_bold("dut device console = %s" % colored("black", 'grey')) + config.console.start = start + + if 'devices' in config.board: + setup_dynamic_devices(config, env=env, start=start) + + def get_tftp_config(dev): + saved = dev.logfile_read + dev.logfile_read = None + if 'wan-no-eth0' in dev.kwargs.get('options', ""): + config.console.tftp_server = dev.get_interface_ipaddr("eth1") + else: + config.console.tftp_server = dev.get_interface_ipaddr("eth0") + dev.logfile_read = saved + config.console.tftp_username = "root" + config.console.tftp_password = "bigfoot1" + config.console.tftp_port = "22" + config.console.tftp_dev = dev + + # check devices after they start for tftpd-server option if + # if we still have not configured a tftp server + if tftp_server is None: + for x in config.board['devices']: + if 'tftpd-server' in x.get('options', ""): + get_tftp_config(getattr(config, x['name'])) + # TODO: how do we handle multiple tftp servers, break for now + break + else: + # check if the tftp_server is an unresolved name and resolve the ip + for x in config.board['devices']: + if tftp_server == x.get('name', ""): + get_tftp_config(getattr(config, tftp_server)) + # call for ip addr too since we want to fields populated + if tftp_server == x.get('ipaddr', ""): + config.console.tftp_dev = getattr(config, x.get('name')) + + except KeyboardInterrupt: + print_bold("Keyboard interrupt") + sys.exit(2) + except Exception as e: + print(e) + traceback.print_exc(file=sys.stdout) + connected_to_board = False + continue + connected_to_board = True + break + if not connected_to_board: + print_bold("Failed to connect to any board") + sys.exit(2) + + try: + print_bold("Using Board %s, User %s" % (name, os.environ['BUILD_USER_ID'])) + except: + print_bold("Using Board %s, User %s" % (name, os.environ['USER'])) + + # Store name of station in config for convenience + config.board['station'] = name + + # Notify boardfarm server of station & devices we are using + bfweb.checkout(config.board) + # Checkin station & devices when we exit + atexit.register(bfweb.checkin) + + # Make devices (board, lan, wan, available to tests easily) + devices.initialize_devices(config) + + # Update config from board info + if hasattr(config.console, "update_config"): + config.console.update_config() + + print_bold('\n==========') + library.printd(config.board) + + # Run tests + os.environ['TEST_START_TIME'] = datetime.now().strftime("%s") + tests_to_run = [] + # Add tests from specified suite + print_bold('==========\nTest suite "%s" has been specified, will attempt to run tests:' % config.TEST_SUITE) + import tests + tests.init(config) + import testsuites + if config.TEST_SUITE not in testsuites.list_tests: + print_bold("Unable to find testsuite %s, aborting..." % config.TEST_SUITE) + sys.exit(1) + for i, name in enumerate(testsuites.list_tests[config.TEST_SUITE]): + if isinstance(name, str): + if not hasattr(tests, name): + print_bold("\tTest %s skipped, not found..." % name) + continue + test = getattr(tests, name) + test.start = start + else: + test = name + print_bold(" %s %s from %s" % (i+1, test.__name__, inspect.getfile(test))) + tests_to_run.append(test(config)) + if hasattr(config, 'EXTRA_TESTS') and config.EXTRA_TESTS: + if tests_to_run[-1].__class__.__name__ == "Interact": + print_bold("Last test is interact in testsuite, removing") + tests_to_run.pop() + + print_bold("Extra tests specified on command line:") + try: + for name in config.EXTRA_TESTS: + t = getattr(tests, name, None) + if t is None: + raise Exception("Unable to load %s test from tests class!!!! Parsing of test selected via -e failed" % name) + print_bold(" %s" % t) + test = t(config) + test.start = start + tests_to_run.append(test) + except Exception as e: + print_bold(e) + print_bold("Unable to find specified extra tests, aborting...") + sys.exit(1) + + print_bold('==========') + try: + tests_pass = tests_fail = tests_skip = 0 + curr_test = None + for test in tests_to_run: + curr_test = test + test.run() + curr_test = None + grade = getattr(test, "result_grade", None) + if grade == "OK" or grade == "Unexp OK": + tests_pass += 1 + elif grade == "FAIL" or grade == "Exp FAIL": + tests_fail += 1 + elif grade == "SKIP" or grade is None: + tests_skip += 1 + + except KeyboardInterrupt: + print_bold("Run interrupted. Wrapping up...") + if curr_test is not None: + curr_test.recover() + + print_bold("Results run=%d failures=%d skipped=%d" % (tests_pass, tests_fail, tests_skip)) + + try: + config.console.close() + if 'devices' in config.board: + for device in config.devices: + getattr(config, device).close() + else: + if config.lan is not None: + config.lan.close() + if config.wan is not None: + config.wan.close() + except Exception as e: + print(e) + print_bold("For some reason, could not close a connection.") + library.printd(config.board) + + combined_list = [] + def add_to_combined_list(log, name, combined_list=combined_list): + for line in log.split('\r\n'): + try: + if line is '': + continue + if line.startswith('\n'): + line = line[1:] + if line.startswith(' ['): + line = line[1:] + ts, text = line.split(']', 1) + combined_list.append({"time": float(ts[1:-1]), "text": str(text), "name": name}) + except: + print("Failed to parse log line = %s" % repr(line)) + pass + + idx = 1 + console_combined = [] + for console in config.console.consoles: + with open(os.path.join(config.output_dir, 'console-%s.log' % idx), 'w') as clog: + clog.write(console.log) + add_to_combined_list(console.log, "console-%s" % idx) + add_to_combined_list(console.log_calls, "console-%s" % idx) + add_to_combined_list(console.log, "", console_combined) + idx = idx + 1 + + def write_combined_log(combined_list, fname): + with open(os.path.join(config.output_dir, fname), 'w') as clog: + for e in combined_list: + try: + if e['name'] == "": + clog.write('[%s]%s\r\n' % (e['time'], e['text'])) + else: + clog.write('%s: [%s] %s\n' % (e['name'], e['time'], e['text'])) + except: + print("failed to parse line: %s" % repr(e)) + + import operator + console_combined.sort(key=operator.itemgetter('time')) + write_combined_log(console_combined, "console-combined.log") + + for device in config.devices: + with open(os.path.join(config.output_dir, device + ".log"), 'w') as clog: + d = getattr(config, device) + if hasattr(d, 'log'): + clog.write(d.log) + add_to_combined_list(d.log, device) + add_to_combined_list(d.log_calls, device) + + for test in tests_to_run: + if hasattr(test, 'log') and test.log != "": + with open(os.path.join(config.output_dir, '%s.log' % test.__class__.__name__), 'w') as clog: + clog.write(test.log) + if hasattr(test, 'log_calls'): + add_to_combined_list(test.log_calls, test.__class__.__name__) + + combined_list.sort(key=operator.itemgetter('time')) + write_combined_log(combined_list, "all.log") + + os.environ['TEST_END_TIME'] = datetime.now().strftime("%s") + + # grab golden master results + golden = {} + if config.golden is not []: + import requests + for g in golden: + try: + golden.update(requests.get(config.golden).json()) + except: + print_bold("Failed to fetch golden master results, skipping...") + + # Write test result messages to a file + full_results = library.process_test_results(tests_to_run, golden) + json.dump(full_results, + open(os.path.join(config.output_dir + 'test_results.json'), 'w'), + indent=4, + sort_keys=True) + + # run all analysis classes (post processing) + # also, never fail so we don't block automation + try: + fname = "console-combined.log" + with open(os.path.join(config.output_dir, fname), 'r') as f: + clog = f.read() + if not clog: + print("Skipping analysis because %s is empty..." % fname) + else: + import analysis + for cstr in dir(analysis): + c = getattr(analysis, cstr) + if inspect.isclass(c) and issubclass(c, analysis.Analysis): + c().analyze(clog, config.output_dir) + except Exception as e: + if not issubclass(type(e), (StopIteration)): + print("Failed to run anaylsis:") + print(e) + + # Try to remotely log information about this run + info_for_remote_log = dict(config.board) + info_for_remote_log.update(full_results) + info_for_remote_log['bft_version'] = library.version + try: + info_for_remote_log['duration'] = int(os.environ['TEST_END_TIME'])-int(os.environ['TEST_START_TIME']) + except: + pass + if hasattr(config, 'TEST_SUITE'): + info_for_remote_log['test_suite'] = str(config.TEST_SUITE) + # logstash cannot handle multi-level json, remove full test results + info_for_remote_log.pop('test_results', None) + # but we will add back specific test results data + for t in tests_to_run: + def prepare_results_for_kibana(test, prefix=""): + if hasattr(test, 'override_kibana_name'): + n = test.override_kibana_name + elif hasattr(test, 'name'): + n = test.name + else: + n = test.__class__.__name__ + + n = prefix + n + + for k, v in test.logged.items(): + info_for_remote_log[n + '-' + k] = v + if hasattr(test, 'result_grade'): + info_for_remote_log[n + "-result"] = test.result_grade + + return n + + prefix = prepare_results_for_kibana(t) + "-" + for subtest in t.subtests: + prepare_results_for_kibana(subtest, prefix=prefix) + + # Convert python objects to things that can be stored in + # JSON, like strings and numbers. + info_for_remote_log = library.clean_for_json(info_for_remote_log) + # Remove reserved key names + info_for_remote_log.pop('_id', None) + + try: + if config.logging_server is not None: + logstash.RemoteLogger(config.logging_server).log(info_for_remote_log) + except Exception as e: + print(e) + print("Unable to access logging_server specified in config. " + "Results stored only locally.") + + try: + if config.elasticsearch_server is not None: + elasticlogger.ElasticsearchLogger(config.elasticsearch_server).log(info_for_remote_log) + else: + print("No elasticsearch_server specified in config. Results stored locally") + except Exception as e: + print(e) + print("Unable to store results to elasticsearch_server specified in config. " + "Results stored locally.") + + try: + if hasattr(config, 'mongodb') and config.mongodb['host'] is not None: + mongodblogger.MongodbLogger(**config.mongodb).log(info_for_remote_log) + else: + print("Needed mongodb parameters are not set, see config. Results stored locally.") + except Exception as e: + print(e) + print("Unable to store results to mongodb specified in config. " + "Results stored locally.") + + if set(('BFT_AWS_ACCESS_KEY', 'BFT_AWS_SECRET_ACCESS_KEY', 'BFT_AWS_BUCKET')).issubset(os.environ): + try: + import boto3 + + filename = datetime.utcnow().strftime("%Y-%m-%dT%H-%M-%S.000Z") + '.json' + s3 = boto3.resource('s3', + aws_access_key_id=os.environ['BFT_AWS_ACCESS_KEY'], + aws_secret_access_key=os.environ['BFT_AWS_SECRET_ACCESS_KEY']) + s3object = s3.Object(os.environ['BFT_AWS_BUCKET'], filename) + s3object.put(Body=(bytes(json.dumps(info_for_remote_log, default=str).encode('UTF-8')))) + except Exception as e: + print("Failed to load data in AWS bucket") + print(e) + + #Update the results in Zephyr + try: + result_data = json.load(open('./results/test_results.json')) + test_cases_list = [[r["name"], r["grade"]] for r in result_data["test_results"]] + zephyr_reporter.update_zephyr(test_cases_list) + except Exception as e: + print(e) + print("Unable to Update results in Zephyr") + + # Create Pretty HTML output + import make_human_readable + try: + title_str = make_human_readable.get_title() + make_human_readable.xmlresults_to_html(full_results['test_results'], title=title_str, + output_name=os.path.join(config.output_dir, "results.html"), + board_info=config.board) + except Exception as e: + print(e) + print("Unable to create HTML results") + + # Send url of pretty html results to MySQL build database + try: + library.send_results_to_myqsl(config.TEST_SUITE, config.output_dir) + except Exception as e: + print(e) + print("Unable to log results to mysql database.") + + for t in tests_to_run: + if t.log_to_file is not None and hasattr(t, 'stop_time'): + filename = type(t).__name__ + '-' + time.strftime("%Y%m%d-%H%M%S") + ".txt" + testtime = t.stop_time - t.start_time + with open(os.path.join(config.output_dir, filename), 'w') as log: + log.write('\t=======================================================') + log.write('\n\tTest case ID: %s' % (type(t).__name__)) + log.write('\n\tTest case Description: %s' % (type(t).__doc__)) + log.write('\n\t=======================================================\n') + log.write(t.log_to_file) + log.write('\n\t=======================================================') + log.write('\n\t%s test result: %s' % (type(t).__name__, t.result_grade)) + log.write('\n\tTotal test time: %s seconds' % testtime) + log.write('\n\t=======================================================') +if __name__ == '__main__': + main() diff --git a/boardfarm/bft-node/Dockerfile b/boardfarm/bft-node/Dockerfile new file mode 100755 index 00000000..b63235b7 --- /dev/null +++ b/boardfarm/bft-node/Dockerfile @@ -0,0 +1,52 @@ +FROM debian:9 + +RUN echo "root:bigfoot1" | chpasswd + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + apache2-utils \ + curl \ + dnsutils \ + iperf \ + iperf3 \ + iproute \ + iptables \ + isc-dhcp-server \ + isc-dhcp-client \ + lighttpd \ + net-tools \ + netcat \ + nmap \ + openssh-server \ + pppoe \ + psmisc \ + procps \ + python-pip \ + python-mysqldb \ + tinyproxy \ + traceroute \ + tftpd-hpa \ + tcpdump \ + vim-common \ + xinetd \ + less \ + wget \ + iw \ + wpasupplicant \ + ntpdate \ + build-essential + +# NOTE: apparmor will interfere with dhclient, disable on HOST by running: +# sudo service apparmor stop +# sudo service apparmor teardown + +RUN mkdir /var/run/sshd +RUN sed -i 's/.*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config + +# The following lines compile a shim to bind a process to an IP address +# using LD_PRELOAD. To run the shim use the following syntax: +# BIND_ADDR="X.X.X.X" LD_PRELOAD=/usr/lib/bind.so [command to run] +RUN wget http://daniel-lange.com/software/bind.c -O /root/bind.c +RUN cd /root; sed -i '/#include /a #include ' ./bind.c; gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE; strip bind.so; mv ./bind.so /usr/lib/ + +EXPOSE 22 diff --git a/boardfarm/boardfarm_config_example.json b/boardfarm/boardfarm_config_example.json new file mode 100644 index 00000000..33415e5f --- /dev/null +++ b/boardfarm/boardfarm_config_example.json @@ -0,0 +1,131 @@ +{ + "new_device": { + "board_type": "Unknown", + "conn_cmd": "cu -l /dev/ttyUSB1 -s 115200", + "connection_type": "local_serial", + "devices": [ + { + "cleanup_cmd": "docker stop wan; docker rm wan; sudo ip link set dev enx00249b14dc6e down", + "cmd": "docker run --name wan --privileged -it bft:node /bin/bash", + "color": "cyan", + "name": "wan", + "options": "tftpd-server", + "post_cmd": "ip link set enx00249b14dc6e name eth1", + "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' wan) dev enx00249b14dc6e", + "pre_cmd_host": "docker build -t bft:node bft-node", + "type": "debian" + }, + { + "cleanup_cmd": "docker stop lan; docker rm lan; sudo ip link set dev enxc05627904e8c down", + "cmd": "docker run --name lan --privileged -it bft:node /bin/bash", + "color": "blue", + "name": "lan", + "post_cmd": "ip link set enxc05627904e8c name eth1", + "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' lan) dev enxc05627904e8c", + "pre_cmd_host": "docker build -t bft:node bft-node", + "type": "debian" + } + ], + "notes": "Unknown device with docker containers attached to WAN/LAN" + }, + "qemux86-1": { + "board_type": "qemux86", + "conn_cmd": "sudo qemu-system-i386 -m 256 -nographic --enable-kvm -netdev tap,id=wan,ifname=${wan_iface} -device e1000,netdev=wan -netdev tap,id=lan,ifname=${lan_iface} -device e1000,netdev=lan", + "connection_type": "local_serial", + "devices": [ + { + "cleanup_cmd": "docker stop wan-${uniq_id}; docker rm wan-${uniq_id}", + "color": "cyan", + "ipaddr": "localhost", + "name": "wan", + "options": "tftpd-server", + "port": "5910", + "pre_cmd_host": "source deploy-boardfarm-nodes.sh; create_container_stub wan-${uniq_id} 5910 9910 ${wan_iface}", + "type": "debian" + }, + { + "cleanup_cmd": "docker stop lan-${uniq_id}; docker rm lan-${uniq_id}", + "color": "blue", + "http_proxy": "localhost:9909", + "ipaddr": "localhost", + "name": "lan", + "port": "5909", + "pre_cmd_host": "source deploy-boardfarm-nodes.sh; create_container_stub lan-${uniq_id} 5909 9909 ${lan_iface}", + "type": "debian" + } + ], + "notes": "QEMU emulated devices" + }, + "qemux86-openwrt-1": { + "board_type": "qemux86-openwrt", + "conn_cmd": "sudo qemu-system-i386 -m 256 -nographic --enable-kvm -netdev tap,id=lan,ifname=${lan_iface} -device e1000,netdev=lan -netdev tap,id=wan,ifname=${wan_iface} -device e1000,netdev=wan", + "connection_type": "local_serial", + "devices": [ + { + "iface": "${wan_iface}", + "name": "wan-factory", + "targets": [ + { + "color": "blue", + "img": "bft:node", + "name": "wan", + "type": "debian" + } + ], + "type": "docker-factory" + }, + { + "iface": "${lan_iface}", + "name": "lan-factory", + "targets": [ + { + "color": "cyan", + "http_proxy": "localhost:9909", + "img": "bft:node", + "name": "lan", + "options": "tftpd-server", + "type": "debian" + }, + { + "color": "cyan", + "http_proxy": "localhost:9910", + "img": "bft:node", + "name": "lan2", + "type": "debian" + } + ], + "type": "docker-factory" + } + ], + "notes": "QEMU emulated devices" + }, + "rpi3-1": { + "board_type": "rpi3", + "conn_cmd": "cu -l /dev/ttyUSB1 -s 115200", + "connection_type": "local_serial", + "devices": [ + { + "cleanup_cmd": "docker stop wan; docker rm wan; sudo ip link set dev enx00249b14dc6e down", + "cmd": "docker run --name wan --privileged -it bft:node /bin/bash", + "color": "cyan", + "name": "wan", + "options": "tftpd-server", + "post_cmd": "ip link set enx00249b14dc6e name eth1", + "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' wan) dev enx00249b14dc6e", + "pre_cmd_host": "docker build -t bft:node bft-node", + "type": "debian" + }, + { + "cleanup_cmd": "docker stop lan; docker rm lan; sudo ip link set dev enxc05627904e8c down", + "cmd": "docker run --name lan --privileged -it bft:node /bin/bash", + "color": "blue", + "name": "lan", + "post_cmd": "ip link set enxc05627904e8c name eth1", + "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' lan) dev enxc05627904e8c", + "pre_cmd_host": "docker build -t bft:node bft-node", + "type": "debian" + } + ], + "notes": "Rpi3 device with docker containers attached to WAN/LAN" + } +} diff --git a/boardfarm/config.py b/boardfarm/config.py new file mode 100644 index 00000000..ae8481dc --- /dev/null +++ b/boardfarm/config.py @@ -0,0 +1,110 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import os +import sys + +local_path = os.path.dirname(os.path.realpath(__file__)) + +# Boardfarm configuration describes test stations - see boardfarm doc. +# Can be local or remote file. +boardfarm_config_location = os.environ.get('BFT_CONFIG', os.path.join(local_path, 'boardfarm_config_example.json')) + +# Test Suite config files. Standard python config file format. +testsuite_config_files = [os.path.join(local_path, 'testsuites.cfg'), ] + +layerconfs = [] +if 'BFT_OVERLAY' in os.environ: + for overlay in os.environ['BFT_OVERLAY'].split(' '): + overlay = os.path.realpath(overlay) + testsuites_path = os.path.join(overlay, 'testsuites.cfg') + layerconf_path = os.path.join(overlay, 'layerconf.py') + if os.path.isfile(testsuites_path): + testsuite_config_files.append(testsuites_path) + if os.path.isfile(layerconf_path): + sys.path.insert(0, overlay) + import layerconf as tmp + layerconfs.append((overlay, tmp)) + sys.path.pop(0) + +# Logstash server - a place to send JSON-format results to +# when finished. Set to None or name:port, e.g. 'logstash.mysite.com:1300' +logging_server = None + +# Elasticsearch server. Data in JSON-format can be directly sent here. +# Set to None or to a valid host, see documentation: +# https://elasticsearch-py.readthedocs.org/en/master/api.html#elasticsearch +elasticsearch_server = os.environ.get('BFT_ELASTICSERVER', None) + +# MongoDB server. Data in JSON-format can be directly sent here. +mongodb = {"host": os.environ.get('BFT_MONGOHOST', None), + "username": os.environ.get('BFT_MONGOUSER', None), + "password": os.environ.get('BFT_MONGOPASS', None) + } + +# Code change server like gerrit, github, etc... Used only in display +# of the results html file to list links to code changes tested. +code_change_server = None + +cdrouter_server = os.environ.get('BFT_CDROUTERSERVER', None) +cdrouter_config = os.environ.get('BFT_CDROUTERCONFIG', None) +cdrouter_wan_iface = os.environ.get('BFT_CDROUTERWANIFACE', "eth1") +cdrouter_lan_iface = os.environ.get('BFT_CDROUTERLANIFACE', "eth2") + + +# creates a small dictionary of all the options +# this will probably grow as options are added +option_dict = { + "proxy":["normal","sock5"], + "webdriver":["chrome","ffox"], + "disp":["xvfb", "xephyr", "xvnc"], + "disp_port":["0"], + "disp_size":["1366x768"] + } + +# the syntax is +# BFT_OPTIONS="proxy=normal webdriver=chrome" +default_proxy_type = "normal" +default_web_driver = "ffox" +default_display_backend = "xvnc" +default_display_backend_port = "0" # i.e. use any available ports +default_display_backend_size = "1366x768" + +if 'BFT_OPTIONS' in os.environ: + for option in os.environ['BFT_OPTIONS'].split(' '): + k,v = option.split(':') + if option_dict.get(k) and (v in option_dict[k]): + if k == "proxy": + default_proxy_type = v + if k == "webdriver": + default_web_driver = v + if k == "disp": + default_display_backend = v + elif k == "disp_port": + # quick validation + i = int(v) # if not a valid num python will throw and exception + if i != 0 and not 1024 <= i <= 65535: + print("Warning: display backend port: %i not in range (1024-65535)" % i) + exit(1) + default_display_backend_port = v + elif k == "disp_size": + default_display_backend_size = v + else: + print("Warning: Ignoring option: %s (misspelled?)" % option) + +def get_display_backend_size(): + xc,yc = default_display_backend_size.split('x') + x = int(xc) + y = int(yc) + return x,y + +if 'BFT_DEBUG' in os.environ: + print("Using proxy:"+default_proxy_type) + print("Using webdriver:"+default_web_driver) + print("Using disp:"+default_display_backend) + print("Using disp_port:"+default_display_backend_port) + print("Using disp_size:"+default_display_backend_size) diff --git a/boardfarm/configs/cdrouter-rdkb.conf b/boardfarm/configs/cdrouter-rdkb.conf new file mode 100644 index 00000000..28d4be90 --- /dev/null +++ b/boardfarm/configs/cdrouter-rdkb.conf @@ -0,0 +1,3 @@ +testvar dhcpClientStart 10.0.0.2 +testvar dhcpClientEnd 10.0.0.253 +testvar lanIp 10.0.0.1 diff --git a/boardfarm/dbclients/__init__.py b/boardfarm/dbclients/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/boardfarm/dbclients/boardfarmwebclient.py b/boardfarm/dbclients/boardfarmwebclient.py new file mode 100644 index 00000000..9a182fb4 --- /dev/null +++ b/boardfarm/dbclients/boardfarmwebclient.py @@ -0,0 +1,129 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +#!/usr/bin/env python + +import platform +import os +import re +import socket +import sys + +import requests + +class BoardfarmWebClient(object): + ''' + Handles interacting with a boardfarm server. For checking out + stations, etc. + ''' + + def __init__(self, config_url, bf_version="1.0.0", debug=False): + self.config_url = config_url + self.bf_version = bf_version + self.debug = debug + self.server_url = None + self.server_version = None + self.checked_out = None + # If config isn't on a server, do nothing + if not config_url.startswith('http'): + return + self.headers = {'user-agent': self._user_agent()} + self.default_data = {'hostname': socket.gethostname(), + 'username': os.environ.get('BUILD_USER_ID', None) or \ + os.environ.get('USER', None), + 'build_url': os.environ.get('BUILD_URL', None) + } + try: + # See if this is a boardfarm server by checking the root /api path + self.server_url = re.search('http.*/api', self.config_url).group(0) + r = requests.get(self.server_url, headers=self.headers) + data = r.json() + self.server_version = data.get('version', None) + print("Using %s as boardfarm server, version %s" % + (self.server_url, self.server_version)) + except Exception as e: + if self.debug: + print(e) + print("The server hosting '%s' does not appear to be a " + "boardfarm server." % self.config_url) + + def _user_agent(self): + bfversion = 'Boardfarm %s' % self.bf_version + s = platform.system() + py = "Python %s.%s.%s" % (sys.version_info[:3]) + try: + system = platform.system() + if system == 'Linux': + s = "%s %s" % platform.linux_distribution()[:2] + elif system == 'Darwin': + s = "MacOS %s" % platform.mac_ver()[0] + elif system == 'Windows': + s = "Windows %s" % platform.win32_ver()[0] + except Exception as e: + if self.debug: + print('Unable to get more specific system info') + return ";".join([bfversion, py, s]) + + def post_note(self, name, note): + ''' + If an error is encountered with a station, use this function + to send a message to the boardfarm server. Something short + and useful for display. + ''' + if not self.server_version: + return + try: + url = self.server_url + "/stations/" + name + requests.post(url, json={"note": note}, headers=self.headers) + except Exception as e: + if self.debug: + print(e) + print("Failed to notify boardfarm server with message.") + + def checkout(self, config): + if not self.server_version: + return + try: + # Gather all the '_id' keys out of the config + station_id = config.get('_id', None) + device_ids = [] + if "devices" in config: + device_ids = [x["_id"] for x in config["devices"] if "_id" in x] + self.checked_out = {"ids": [station_id] + device_ids, + "name": config.get("station", None)} + self.checked_out.update(self.default_data) + url = self.server_url + "/checkout" + requests.post(url, json=self.checked_out, headers=self.headers) + print("Notified boardfarm server of checkout") + if self.debug: + print(self.checked_out) + except Exception as e: + if self.debug: + print(e) + print("Failed to notify boardfarm server of checkout") + + def checkin(self): + if not self.server_version or not self.checked_out: + return + try: + url = self.server_url + "/checkin" + requests.post(url, json=self.checked_out, headers=self.headers) + print("Notified boardfarm server of checkin") + if self.debug: + print(self.checked_out) + except Exception as e: + if self.debug: + print(e) + print("Failed to notify boardfarm server of checkin") + +if __name__ == '__main__': + bf_config = "http://boardfarm.myexamplesite.com/api/bf_config" + #bf_config = "../bf_config.json" + bfweb = BoardfarmWebClient(bf_config, debug=False) + bfweb.checkout({"_id": "1111", + "station": "rpi3", + "devices": [{"_id": "1112"}]}) + bfweb.checkin() diff --git a/devices/elasticlogger.py b/boardfarm/dbclients/elasticlogger.py similarity index 79% rename from devices/elasticlogger.py rename to boardfarm/dbclients/elasticlogger.py index 5dbfbb6e..b2e9b4cc 100644 --- a/devices/elasticlogger.py +++ b/boardfarm/dbclients/elasticlogger.py @@ -13,12 +13,22 @@ try: import elasticsearch + from elasticsearch.serializer import JSONSerializer except Exception as e: print(e) print("Please install needed module:\n" " sudo pip install -U elasticsearch") sys.exit(1) +class Serializer(JSONSerializer): + def default(self, obj): + try: + return JSONSerializer.default(self, obj) + except TypeError as e: + return str(obj) + except: + return "Unable to serialize" + class ElasticsearchLogger(object): ''' Write data directly to an elasticsearch cluster. @@ -29,7 +39,7 @@ def __init__(self, server, index='boardfarm', doc_type='bft_run'): self.index = index + "-" + datetime.datetime.utcnow().strftime("%Y.%m.%d") self.doc_type = doc_type # Connect to server - self.es = elasticsearch.Elasticsearch([server]) + self.es = elasticsearch.Elasticsearch([server], serializer=Serializer()) # Set default data username = os.environ.get('BUILD_USER_ID', None) if username is None: @@ -39,7 +49,6 @@ def __init__(self, server, index='boardfarm', doc_type='bft_run'): 'user': username, 'build_url': os.environ.get('BUILD_URL', 'None'), 'change_list': os.environ.get('change_list', 'None'), - 'apss': os.environ.get('apss', 'None').split('-')[0], 'manifest': os.environ.get('manifest', 'None'), } @@ -48,7 +57,7 @@ def log(self, data, debug=False): self.default_data['@timestamp'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") data.update(self.default_data) result = self.es.index(index=self.index, doc_type=self.doc_type, body=data) - if result and 'created' in result and result['created'] == True: + if result and u'result' in result and result[u'result'] == u'created': doc_url = "%s%s/%s/%s" % (self.server, self.index, self.doc_type, result['_id']) print("Elasticsearch: Data stored at %s" % (doc_url)) else: diff --git a/devices/logstash.py b/boardfarm/dbclients/logstash.py similarity index 96% rename from devices/logstash.py rename to boardfarm/dbclients/logstash.py index 8dddb429..36232873 100644 --- a/devices/logstash.py +++ b/boardfarm/dbclients/logstash.py @@ -27,7 +27,6 @@ def __init__(self, server, subtype='demo'): 'user': username, 'build_url': os.environ.get('BUILD_URL', 'None'), 'change_list': os.environ.get('change_list', 'None'), - 'apss': os.environ.get('apss', 'None').split('-')[0], 'manifest': os.environ.get('manifest', 'None'), } self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) diff --git a/boardfarm/dbclients/mongodblogger.py b/boardfarm/dbclients/mongodblogger.py new file mode 100644 index 00000000..a480877c --- /dev/null +++ b/boardfarm/dbclients/mongodblogger.py @@ -0,0 +1,77 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +#!/usr/bin/env python + +import datetime +import json +import os +import socket +import sys + +import ipaddress +import pymongo + +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, ipaddress.IPv4Network) or \ + isinstance(obj, ipaddress.IPv4Address): + return str(obj) + elif isinstance(obj, datetime.datetime): + return str(obj) + else: + try: + return json.JSONEncoder.default(self, obj) + except: + print("WARNING: mongodblogger ComplexEncoder can't handle type %s" % type(obj)) + return "" + +def pprint(x): + '''Pretty print an object''' + print(json.dumps(x, sort_keys=True, indent=2, cls=ComplexEncoder)) + +class MongodbLogger(object): + ''' + Write data directly to mongodb. + ''' + + def __init__(self, host, username, password, + db_name='boardfarm', + collection_name='bft_run'): + self.host = host + self.username = username + self.password = password + self.db_name = db_name + self.collection_name = collection_name + # Connect to host + connect_str = "mongodb+srv://%s:%s@%s/test?retryWrites=true&w=majority" % (self.username, self.password, self.host) + self.client = pymongo.MongoClient(connect_str) + self.db = self.client[self.db_name] + self.collection = self.db[self.collection_name] + # Set default data + username = os.environ.get('BUILD_USER_ID', None) + if username is None: + username = os.environ.get('USER', '') + self.default_data = { + 'hostname': socket.gethostname(), + 'user': username, + 'build_url': os.environ.get('BUILD_URL', 'None'), + 'change_list': os.environ.get('change_list', 'None'), + 'manifest': os.environ.get('manifest', 'None'), + } + + def log(self, data, debug=False): + # Put in default data + self.default_data['timestamp'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") + data.update(self.default_data) + # Handle object types that json normally can't (converts them to a string or number) + data = json.loads(json.dumps(data, cls=ComplexEncoder)) + if debug: + print("Storing into mongodb:") + pprint(data) + post_id = self.collection.insert_one(data).inserted_id + doc_url = "%s; db: %s; collection: %s; _id: %s" % (self.host, self.db_name, self.collection_name, post_id) + print("Mongodb: Data stored at %s" % (doc_url)) diff --git a/boardfarm/deploy-boardfarm-nodes.sh b/boardfarm/deploy-boardfarm-nodes.sh new file mode 100755 index 00000000..440d6ba8 --- /dev/null +++ b/boardfarm/deploy-boardfarm-nodes.sh @@ -0,0 +1,412 @@ +#!/bin/bash -xe + +IFACE=${1:-undefined} +START_VLAN=${2:-101} +END_VLAN=${3:-144} +OPTS=${4:-"both"} # both, odd, even, odd-dhcp, even-dhcp +BRINT=br-bft +BF_IMG=${BF_IMG:-"bft:node"} +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +STARTSSHPORT=5000 +STARTWEBPORT=8000 + +if ! docker inspect --type=image $BF_IMG > /dev/null 2>&1 ; then + (cd $DIR; docker build -t $BF_IMG ${BF_IMG/:/-}) +fi + +random_private_mac () { + python - <> /etc/iproute2/rt_tables" + docker exec $cname ip route add default via $docker_gw_ip table mgmt + docker exec $cname ip rule add from $docker_dev_ip table mgmt + docker exec $cname ip rule add to $docker_dev_ip table mgmt + docker exec $cname ip rule add from $docker_nw table mgmt + docker exec $cname ip rule add to $docker_nw table mgmt + + docker cp $cname:root/.bashrc bashrc_$cname + echo "alias mgmt='BIND_ADDR=$docker_dev_ip LD_PRELOAD=/usr/lib/bind.so '" >> bashrc_$cname + docker cp bashrc_$cname $cname:root/.bashrc; rm bashrc_$cname +} +# creates container running with ssh on eth0, adds DUT facing interface only +create_container_stub () { + local cname=${1} + local sshport=${2} + local proxyport=${3} + local ifacedut=${4} + + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $sshport:22 \ + -p $proxyport:8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format '{{.State.Pid}}' $cname) + # this should avoid the cli wrapping onto itself + docker exec $cname bash -c 'echo "stty columns 200" >> /root/.bashrc' + sudo ip link set netns $cspace dev $ifacedut + docker exec $cname ip link set $ifacedut name eth1 + docker exec $cname ip link set dev eth1 address $(random_private_mac $vlan) + + isolate_management ${cname} +} + +# eth0 is docker private network, eth1 is vlan on specific interface +create_container_eth1_vlan () { + local vlan=$1 + local offset=${2:-0} + + cname=bft-node-$IFACE-$vlan-$offset + + sudo ip link del $IFACE.$vlan || true + sudo ip link add link $IFACE name $IFACE.$vlan type vlan id $vlan + + create_container_stub $cname \ + $(( $STARTSSHPORT + $offset + $vlan )) \ + $(( $STARTWEBPORT + $offset + $vlan )) \ + $IFACE.$vlan +} + +# eth0 is docker private network, eth1 is vlan on specific interface within a bridge +create_container_eth1_bridged_vlan () { + local vlan=$1 + local offset=${2:-0} + + # verify settings are correct + # TODO: verify the set + sudo sysctl -w net.bridge.bridge-nf-call-arptables=0 + sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0 + sudo sysctl -w net.bridge.bridge-nf-call-iptables=0 + + cname=bft-node-$IFACE-$vlan-$offset + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( $STARTSSHPORT + $offset + $vlan )):22 \ + -p $(( $STARTWEBPORT + $offset + $vlan )):8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format '{{.State.Pid}}' $cname) + isolate_management ${cname} + + # create bridge + sudo ip link add br-$IFACE.$vlan type bridge || true + sudo ip link set br-$IFACE.$vlan up + + # create uplink vlan on IFACE + sudo ip link delete $IFACE.$vlan || true + sudo ip link add link $IFACE name $IFACE.$vlan type vlan id $vlan + sudo ip link set dev $IFACE.$vlan address $(random_private_mac $vlan) + sudo ip link set $IFACE.$vlan master br-$IFACE.$vlan + sudo ip link set $IFACE up + sudo ip link set $IFACE.$vlan up + + # add veth for new container (one per container vs. the two above are shared) + sudo ip link add v$IFACE-$vlan-$offset type veth peer name eth1 netns $cspace + sudo ip link set v$IFACE-$vlan-$offset master br-$IFACE.$vlan + sudo ip link set v$IFACE-$vlan-$offset up + + docker exec $cname ip link set eth1 up +} + +# eth0 is docker private network, eth1 is vlan on specific interface within a bridge +create_container_eth1_macvtap_vlan () { + local vlan=$1 + local offset=${2:-0} + + # verify settings are correct + # TODO: verify the set + sudo sysctl -w net.bridge.bridge-nf-call-arptables=0 + sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0 + sudo sysctl -w net.bridge.bridge-nf-call-iptables=0 + + cname=bft-node-$IFACE-$vlan-$offset + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( $STARTSSHPORT + $offset + $vlan )):22 \ + -p $(( $STARTWEBPORT + $offset + $vlan )):8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format '{{.State.Pid}}' $cname) + isolate_management ${cname} + + # create uplink vlan on IFACE + sudo ip link add link $IFACE name $IFACE.$vlan type vlan id $vlan + sudo ip link set dev $IFACE.$vlan address $(random_private_mac $vlan) + sudo ip link set $IFACE.$vlan up + + # add veth for new container (one per container vs. the two above are shared) + sudo ip link add link $IFACE.$vlan name eth1 type macvtap + sudo ip link set netns $cspace dev eth1 + + docker exec $cname ip link set eth1 up +} + +# eth0/eth1 are both dhcp on the main network +create_container_eth1_dhcp () { + local vlan=$1 + + cname=bft-node-$IFACE-$vlan + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -d --network=none $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format '{{.State.Pid}}' $cname) + + # create lab network access port + sudo ip link add tempfoo link $IFACE type macvlan mode bridge + sudo ip link set dev tempfoo up + sudo ip link set netns $cspace dev tempfoo + docker exec $cname ip link set tempfoo name eth1 + docker exec $cname ifconfig eth1 up + docker exec $cname dhclient eth1 +} + +# eth1 is on main network and static +create_container_eth1_static () { + local name=$1 + local ip=$2 + local default_route=$3 + local driver=${4:-macvlan} + local ipv6_addr=${5:-"0"} + local ipv6_default=${6:-"0"} + + cname=bft-node-$IFACE-$name + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -d --network=none $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format {{.State.Pid}} $cname) + + # create lab network access port + if [ "$driver" = "ipvlan" ] + then + sudo ip link add tempfoo link $IFACE type $driver mode l2 + else + sudo ip link add tempfoo link $IFACE type $driver mode bridge + fi + sudo ip link set dev tempfoo up + sudo ip link set netns $cspace dev tempfoo + docker exec $cname ip link set tempfoo name eth1 + docker exec $cname ip link set eth1 up + docker exec $cname ip addr add $ip dev eth1 + docker exec $cname ip route add default via $default_route dev eth1 + docker exec $cname ping $default_route -c3 + + ! [ "$ipv6_addr" != "0" -a "$ipv6_default" != "0" ] && echo "Error: missing ipv6 params" && return + + docker exec $cname sysctl net.ipv6.conf.eth1.disable_ipv6=0 + docker exec $cname ip -6 addr add $ipv6_addr dev eth1 + docker exec $cname ip -6 route add default via $ipv6_default dev eth1 + sleep 3 + docker exec $cname bash -c "ping -c3 $ipv6_default" +} + +# eth1 is on main network and static +create_container_eth1_static_ipvlan () { + local name=$1 + local ip=$2 + local default_route=$3 + + cname=bft-node-$IFACE-$name + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -d --network=none $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format {{.State.Pid}} $cname) + + # create lab network access port + sudo ip link add tempfoo link $IFACE type ipvlan mode l2 + sudo ip link set dev tempfoo up + sudo ip link set netns $cspace dev tempfoo + docker exec $cname ip link set tempfoo name eth1 + docker exec $cname ip link set eth1 up + docker exec $cname ip addr add $ip dev eth1 + docker exec $cname ip route add default via $default_route dev eth1 + docker exec $cname ping $default_route -c3 +} + +# eth0 is docker private network, eth1 is static ip +create_container_eth1_static_linked () { + local name=$1 + local ip=$2 + local default_route=$3 + local offset=${4:-0} + local driver=${5:-macvlan} + local ipv6_addr=${6:-"0"} + local ipv6_default=${7:-"0"} + + cname=bft-node-$IFACE-$name + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( $STARTSSHPORT + $offset )):22 \ + -p $(( $STARTWEBPORT + $offset )):8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format {{.State.Pid}} $cname) + isolate_management ${cname} + + # create lab network access port + if [ "$driver" = "ipvlan" ] + then + sudo ip link add tempfoo link $IFACE type $driver mode l2 + else + # driver can be macvtap or macvlan. default=macvlan + sudo ip link add tempfoo link $IFACE type $driver mode bridge + fi + sudo ip link set dev tempfoo up + sudo ip link set netns $cspace dev tempfoo + docker exec $cname ip link set tempfoo name eth1 + docker exec $cname ip link set eth1 up + docker exec $cname ip addr add $ip dev eth1 + docker exec $cname ip route del default dev eth0 + docker exec $cname ip route add default via $default_route dev eth1 + docker exec $cname ping $default_route -c3 + + ! [ "$ipv6_addr" != "0" -a "$ipv6_default" != "0" ] && echo "Error: missing ipv6 params" && return + + docker exec $cname sysctl net.ipv6.conf.eth1.disable_ipv6=0 + docker exec $cname ip -6 addr add $ipv6_addr dev eth1 + docker exec $cname ip -6 route add default via $ipv6_default dev eth1 + sleep 3 + docker exec $cname bash -c "ping -c3 $ipv6_default" +} + +# eth0 is docker private network, eth1 physical device +create_container_eth1_phys () { + local dev=$1 + local offset=$2 + + cname=bft-node-$dev + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( $STARTSSHPORT + $offset )):22 \ + -p $(( $STARTWEBPORT + $offset )):8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + cspace=$(docker inspect --format {{.State.Pid}} $cname) + + # create lab network access port + sudo ip link set netns $cspace dev $dev + docker exec $cname ip link set $dev name wlan1 + docker exec $cname ip link set wlan1 up +} + +# eth0 is docker private network, eth1 with device +create_container_eth1_wifi () { + local dev=$1 + local offset=${2:-40000} + local proxy_dir=${3:-"0"} + local proxy_ip=${4:-"0"} + + cname=bft-node-$dev + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( $STARTSSHPORT + $offset )):22 \ + -p $(( $STARTWEBPORT + $offset )):8080 \ + -d $BF_IMG /usr/sbin/sshd -D + + isolate_management ${cname} + + #add proxy details if specified + local docker_gw_ip=$(ip -4 addr show docker0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [ "$proxy_dir" != "0" ] && [ "$proxy_ip" != "0" ] + then + docker cp $proxy_dir/proxy.conf $cname:/etc/apt/apt.conf.d/ + docker exec $cname ip route add $proxy_ip via $docker_gw_ip table mgmt + fi + cspace=$(docker inspect --format {{.State.Pid}} $cname) + isolate_management ${cname} + + # create lab network access port + # rfkill and ip need to be added as rootLessCommands on the host + # if Wi-Fi was associated to an SSID on the host, on pushing the interface + # to container rfkill releases the wifi resource from host. + sudo rfkill unblock wifi + sudo iw phy $(cat /sys/class/net/"$dev"/phy80211/name) set netns $cspace + docker exec $cname ip link set $dev name wlan1 + docker exec $cname ip link set wlan1 up +} + +#voice container +create_container_voice () { + #will be from /dev ACM dev name + local dev=$1 + #keep offset as 40000 + local offset=${2:-1} + local proxy_dir=${3:-"0"} + local proxy_ip=${4:-"0"} + + cname=bft-node-$dev + docker stop $cname && docker rm $cname + docker run --name $cname --privileged -h $cname --restart=always \ + -p $(( 4000 + $offset )):22 \ + -d $BF_IMG /usr/sbin/sshd -D + + #add proxy details if specified + local docker_gw_ip=$(ip -4 addr show docker0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [ "$proxy_dir" != "0" ] && [ "$proxy_ip" != "0" ] + then + docker cp $proxy_dir/proxy.conf $cname:/etc/apt/apt.conf.d/ + docker exec $cname ip route add $proxy_ip via $docker_gw_ip + fi + docker exec $cname ln -s /dev/tty$dev /root/line-$dev +} + +[ "$IFACE" = "undefined" ] && return + +echo "Creating nodes starting on vlan $START_VLAN to $END_VLAN on iface $IFACE" + +for vlan in $(seq $START_VLAN $END_VLAN); do + echo "Creating node on vlan $vlan" + + create_container_eth1_vlan $vlan + + [ "$OPTS" = "both" ] && { local_route; continue; } + if [ $((vlan%2)) -eq 0 ]; then + [ "$OPTS" = "even" ] && local_route + elif [ "$OPTS" = "odd" ]; then + local_route + fi +done + +echo "Running the command below will stop all containers and clean up everything:" +echo 'docker stop $(docker ps -q) && docker rm $(docker ps -a -q)' diff --git a/boardfarm/devices/__init__.py b/boardfarm/devices/__init__.py new file mode 100644 index 00000000..4756ddd9 --- /dev/null +++ b/boardfarm/devices/__init__.py @@ -0,0 +1,135 @@ +''' + + This directory contains classes for connecting to and controlling + devices over a network. + +''' +import os +import sys +import glob +import inspect +import pexpect +import termcolor + +# insert tests lib so devices and tests can share the same libraries +sys.path.insert(0, os.path.dirname(__file__) + '/../tests') +sys.path.insert(0, os.path.dirname(__file__)) + +board = None +lan = None +wan = None +wlan = None +wlan2g = None +wlan5g = None +prompt = None + +device_files = glob.glob(os.path.dirname(__file__)+"/*.py") +device_files += [e.replace('/__init__', '') for e in glob.glob(os.path.dirname(__file__) + '/*/__init__.py')] +if 'BFT_OVERLAY' in os.environ: + for overlay in os.environ['BFT_OVERLAY'].split(' '): + overlay = os.path.realpath(overlay) + sys.path.insert(0, overlay + '/devices') + device_files += glob.glob(overlay + '/devices/*.py') + device_files += [e.replace('/__init__', '') for e in glob.glob(overlay + '/devices/*/__init__.py')] + + sys.path.insert(0, overlay + '/tests') + + sys.path.insert(0, os.getcwd() + '/devices') + +device_mappings = { } +for x in sorted([os.path.basename(f)[:-3] for f in device_files if not "__" in f]): + exec("import %s as device_file" % x) + device_mappings[device_file] = [] + for obj in dir(device_file): + ref = getattr(device_file, obj) + if inspect.isclass(ref) and hasattr(ref, "model"): + device_mappings[device_file].append(ref) + exec("from %s import %s" % (x, obj)) + +def check_for_cmd_on_host(cmd, msg=None): + '''Prints an error message with a suggestion on how to install the command''' + from lib.common import cmd_exists + if not cmd_exists(cmd): + termcolor.cprint("\nThe command '"+cmd+"' is NOT installed on your system. Please install it.", None, attrs=['bold']) + if msg is not None: print(cmd+": "+msg) + import sys + if sys.platform == "linux2": + import platform + if "Ubuntu" in platform.dist() or "debian" in platform.dist(): + print("To install run:\n\tsudo apt install ") + exit(1) + print("To install refer to your system SW app installation instructions") + +def initialize_devices(configuration): + # Init random global variables. To Do: clean these. + global power_ip, power_outlet + conn_cmd = configuration.board.get('conn_cmd') + power_ip = configuration.board.get('powerip', None) + power_outlet = configuration.board.get('powerport', None) + # Init devices + global board, lan, wan, wlan, wlan2g, wlan5g, prompt + board = configuration.console + lan = None + wan = None + wlan = None + wlan2g = None + wlan5g = None + + for device in configuration.devices: + globals()[device] = getattr(configuration, device) + + board.root_type = None + # Next few lines combines all the prompts into one list of unique prompts. + # It lets test writers use "some_device.expect(prompt)" + prompt = [] + for d in (board, lan, wan, wlan): + prompt += getattr(d, "prompt", []) + prompt = list(set(prompt)) + +def get_device(model, **kwargs): + for device_file, devs in device_mappings.iteritems(): + for dev in devs: + if 'model' in dev.__dict__: + + attr = dev.__dict__['model'] + + if type(attr) is str and model != attr: + continue + elif type(attr) is tuple and model not in attr: + continue + + try: + return dev(model, **kwargs) + except pexpect.EOF: + msg = "Failed to connect to a %s, unable to connect (in use) or possibly misconfigured" % model + raise Exception(msg) + + return None + +def board_decider(model, **kwargs): + if any('conn_cmd' in s for s in kwargs): + if any(u'kermit' in s for s in kwargs['conn_cmd']): + check_for_cmd_on_host('kermit',"telnet equivalent command. It has lower CPU usage than telnet,\n\ +and works exactly the same way (e.g. kermit -J [])\n\ +You are seeing this message as your configuration is now using kermit instead of telnet.") + + dynamic_dev = get_device(model, **kwargs) + if dynamic_dev is not None: + return dynamic_dev + + # Default for all other models + print("\nWARNING: Unknown board model '%s'." % model) + print("Please check spelling, your environment setup, or write an appropriate class " + "to handle that kind of board.") + + if 'BFT_OVERLAY' in os.environ: + print("\nIs this correct? BFT_OVERLAY=%s\n" % os.environ['BFT_OVERLAY']) + else: + print("No BFT_OVERLAY is set, do you need one?") + + if 'BFT_CONFIG' in os.environ: + print("\nIs this correct? BFT_CONFIG=%s\n" % os.environ['BFT_CONFIG']) + else: + print("No BFT_CONFIG is set, do you need one?") + + return openwrt_router.OpenWrtRouter(model, **kwargs) diff --git a/boardfarm/devices/axiros_acs.py b/boardfarm/devices/axiros_acs.py new file mode 100755 index 00000000..800ab870 --- /dev/null +++ b/boardfarm/devices/axiros_acs.py @@ -0,0 +1,187 @@ +import os +import time + +from zeep import Client +from zeep.wsse.username import UsernameToken +from zeep.transports import Transport + +from requests import Session +from requests.auth import HTTPBasicAuth + +from xml.etree import ElementTree + +if "BFT_DEBUG" in os.environ: + import logging.config + + logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'verbose': { + 'format': '%(name)s: %(message)s' + } + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + }, + }, + 'loggers': { + 'zeep.transports': { + 'level': 'DEBUG', + 'propagate': True, + 'handlers': ['console'], + }, + } + }) + + +class AxirosACS(): + + model = "axiros_acs_soap" + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.username = self.kwargs['username'] + self.password = self.kwargs['password'] + self.ipaddr = self.kwargs['ipaddr'] + self.port = self.kwargs.get('port', None) + + if self.port is not None: + target = self.ipaddr + ":" + self.port + else: + target = self.ipaddr + + self.wsdl = "http://" + target + "/live/CPEManager/DMInterfaces/soap/getWSDL" + + session = Session() + session.auth = HTTPBasicAuth(self.username, self.password) + + self.client = Client(wsdl=self.wsdl, transport=Transport(session=session), + wsse=UsernameToken(self.username, self.password)) + + name = "acs_server" + + def __str__(self): + return "AxirosACS" + + def close(self): + pass + + def get(self, serial_number, param, wait=8): + GetParameterValuesParametersClassArray_type = self.client.get_type('ns0:GetParameterValuesParametersClassArray') + GetParameterValuesParametersClassArray_data = GetParameterValuesParametersClassArray_type([param]) + + CommandOptionsTypeStruct_type = self.client.get_type('ns0:CommandOptionsTypeStruct') + CommandOptionsTypeStruct_data = CommandOptionsTypeStruct_type() + + CPEIdentifierClassStruct_type = self.client.get_type('ns0:CPEIdentifierClassStruct') + CPEIdentifierClassStruct_data = CPEIdentifierClassStruct_type(cpeid=serial_number) + + # get raw soap response (parsing error with zeep) + with self.client.settings(raw_response=True): + response = self.client.service.GetParameterValues( + GetParameterValuesParametersClassArray_data, CommandOptionsTypeStruct_data, CPEIdentifierClassStruct_data) + + ticketid = None + root = ElementTree.fromstring(response.content) + for value in root.iter('ticketid'): + ticketid = value.text + break + + if ticketid is None: + return None + return self.Axiros_GetTicketValue(ticketid, wait=wait) + + def getcurrent(self, serial_number, param): + self.get(serial_number, param + '.', wait=20) + # TODO: note: verified ticket was sent to ACS with all the results in the param namespace + # however the get above does not pull the results so we can't check them here but that's + # not a major issue since the API does not do that for the current implementation + + def set(self, serial_number, attr, value): + SetParameterValuesParametersClassArray_type = self.client.get_type('ns0:SetParameterValuesParametersClassArray') + SetParameterValuesParametersClassArray_data = SetParameterValuesParametersClassArray_type([ + {'key': attr, 'value': value}]) + + CommandOptionsTypeStruct_type = self.client.get_type('ns0:CommandOptionsTypeStruct') + CommandOptionsTypeStruct_data = CommandOptionsTypeStruct_type() + + CPEIdentifierClassStruct_type = self.client.get_type('ns0:CPEIdentifierClassStruct') + CPEIdentifierClassStruct_data = CPEIdentifierClassStruct_type(cpeid=serial_number) + + # get raw soap response (parsing error with zeep) + with self.client.settings(raw_response=True): + response = self.client.service.SetParameterValues( + SetParameterValuesParametersClassArray_data, CommandOptionsTypeStruct_data, CPEIdentifierClassStruct_data) + + ticketid = None + root = ElementTree.fromstring(response.content) + for value in root.iter('ticketid'): + ticketid = value.text + break + + if ticketid is None: + return None + + return self.Axiros_GetTicketValue(ticketid) + + def Axiros_GetListOfCPEs(self): + CPESearchOptionsClassStruct_type = self.client.get_type('ns0:CPESearchOptionsClassStruct') + CPESearchOptionsClassStruct_data = CPESearchOptionsClassStruct_type() + + CommandOptionsForCPESearchStruct_type = self.client.get_type('ns0:CommandOptionsForCPESearchStruct') + CommandOptionsForCPESearchStruct_data = CommandOptionsForCPESearchStruct_type() + + response = self.client.service.GetListOfCPEs( + CPESearchOptionsClassStruct_data, CommandOptionsForCPESearchStruct_data) + if response['code'] != 200: + return None + + return response + + def Axiros_GetTicketResponse(self, ticketid): + response = self.client.service.get_generic_sb_result(ticketid) + + if response['code'] != 200: + return None + + return response['code'] + + def Axiros_GetTicketValue(self, ticketid, wait=8): + for i in range(wait): + time.sleep(1) + with self.client.settings(raw_response=True): + ticket_resp = self.client.service.get_generic_sb_result(ticketid) + + root = ElementTree.fromstring(ticket_resp.content) + for value in root.iter('code'): + break + if (value.text != '200'): + continue + for value in root.iter('value'): + return value.text + return None + + +if __name__ == '__main__': + import sys + + if ':' in sys.argv[1]: + ip = sys.argv[1].split(':')[0] + port = sys.argv[1].split(':')[1] + else: + ip = sys.argv[1] + port = 80 + + acs = AxirosACS(ipaddr=ip, port=port, username=sys.argv[2], password=sys.argv[3]) + + acs.Axiros_GetListOfCPEs() + + ret = acs.get('DEAP805811D5', 'Device.DeviceInfo.SoftwareVersion') + print(ret) + + ret = acs.get('DEAP805811D5', 'Device.WiFi.SSID.1.SSID') + print(ret) diff --git a/boardfarm/devices/base.py b/boardfarm/devices/base.py new file mode 100644 index 00000000..1d237fec --- /dev/null +++ b/boardfarm/devices/base.py @@ -0,0 +1,294 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +import ipaddress +import pexpect +import os +import time +import common +import error_detect +import signal +from lib.bft_logging import LoggerMeta, o_helper + +# To Do: maybe make this config variable +BFT_DEBUG = "BFT_DEBUG" in os.environ + + +class BaseDevice(pexpect.spawn): + __metaclass__ = LoggerMeta + log = "" + log_calls = "" + + prompt = ['root\\@.*:.*#', ] + delaybetweenchar = None + + def get_interface_ipaddr(self, interface): + '''Get ipv4 address of interface ''' + raise Exception("Not implemented!") + + def get_interface_ip6addr(self, interface): + '''Get ipv6 address of interface ''' + raise Exception("Not implemented!") + + def get_interface_macaddr(self, interface): + '''Get the interface mac address ''' + raise Exception("Not implemented!") + + def get_seconds_uptime(self): + '''Return seconds since last reboot. Stored in /proc/uptime''' + raise Exception("Not implemented!") + + def get_logfile_read(self): + if hasattr(self, "_logfile_read"): + return self._logfile_read + else: + return None + + def expect_prompt(self, timeout=30): + self.expect(self.prompt, timeout=timeout) + + def check_output(self, cmd, timeout=30): + '''Send a string to device, then return the output + between that string and the next prompt.''' + self.sendline("\n" + cmd) + self.expect_exact(cmd, timeout=5) + try: + self.expect(self.prompt, timeout=timeout) + except Exception: + self.sendcontrol('c') + raise Exception("Command did not complete within %s seconds. Prompt was not seen." % timeout) + return self.before.strip() + + def write(self, string): + self._logfile_read.write(string) + + def set_logfile_read(self, value): + if value == None: + self._logfile_read = None + return + + if isinstance(value, o_helper): + self._logfile_read = value + elif value is not None: + self._logfile_read = o_helper(self, value, getattr(self, "color", None)) + + logfile_read = property(get_logfile_read, set_logfile_read) + + def interact(self, escape_character=chr(29), + input_filter=None, output_filter=None): + + o = self._logfile_read + self.logfile_read = None + ret = super(BaseDevice, self).interact(escape_character, + input_filter, output_filter) + self.logfile_read = o + + return ret + + # perf related + def parse_sar_iface_pkts(self, wan, lan): + self.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){6}\r\n') + idle = float(self.match.group(1)) + self.expect("Average.*rxmcst/s.*\r\n") + + wan_pps = None + client_pps = None + if lan is None: + exp = [wan] + else: + exp = [wan, lan] + + for x in range(0, len(exp)): + i = self.expect(exp) + if i == 0: # parse wan stats + self.expect("(\d+.\d+)\s+(\d+.\d+)") + wan_pps = float(self.match.group(1)) + float(self.match.group(2)) + if i == 1: + self.expect("(\d+.\d+)\s+(\d+.\d+)") + client_pps = float(self.match.group(1)) + float(self.match.group(2)) + + return idle, wan_pps, client_pps + + def check_perf(self): + self.sendline('uname -r') + self.expect('uname -r') + self.expect(self.prompt) + + self.kernel_version = self.before + + self.sendline('\nperf --version') + i = self.expect(['not found', 'perf version']) + self.expect(self.prompt) + + if i == 0: + return False + + return True + + def check_output_perf(self, cmd, events): + perf_args = self.perf_args(events) + + self.sendline("perf stat -a -e %s time %s" % (perf_args, cmd)) + + def parse_perf(self, events): + mapping = self.parse_perf_board() + ret = [] + + for e in mapping: + if e['name'] not in events: + continue + self.expect("(\d+) %s" % e['expect']) + e['value'] = int(self.match.group(1)) + ret.append(e) + + return ret + + # end perf related + + # Optional send and expect functions to try and be fancy at catching errors + def send(self, s): + if BFT_DEBUG: + if 'pexpect/__init__.py: sendline():' in error_detect.caller_file_line(3): + idx = 4 + else: + idx = 3 + common.print_bold("%s = sending: %s" % + (error_detect.caller_file_line(idx), repr(s))) + + if self.delaybetweenchar is not None: + ret = 0 + for char in s: + ret += super(BaseDevice, self).send(char) + time.sleep(self.delaybetweenchar) + return ret + + return super(BaseDevice, self).send(s) + + def expect_helper(self, pattern, wrapper, *args, **kwargs): + if not BFT_DEBUG: + return wrapper(pattern, *args, **kwargs) + + if 'base.py: expect():' in error_detect.caller_file_line(3) or \ + 'base.py: expect_exact():' in error_detect.caller_file_line(3): + idx = 5 + else: + idx = 3 + common.print_bold("%s = expecting: %s" % + (error_detect.caller_file_line(idx), repr(pattern))) + try: + ret = wrapper(pattern, *args, **kwargs) + + frame = error_detect.caller_file_line(idx) + + if hasattr(self.match, "group"): + common.print_bold("%s = matched: %s" % + (frame, repr(self.match.group()))) + else: + common.print_bold("%s = matched: %s" % + (frame, repr(pattern))) + return ret + except: + common.print_bold("expired") + raise + + def expect(self, pattern, *args, **kwargs): + wrapper = super(BaseDevice, self).expect + + return self.expect_helper(pattern, wrapper, *args, **kwargs) + + def expect_exact(self, pattern, *args, **kwargs): + wrapper = super(BaseDevice, self).expect_exact + + return self.expect_helper(pattern, wrapper, *args, **kwargs) + + def sendcontrol(self, char): + if BFT_DEBUG: + common.print_bold("%s = sending: control-%s" % + (error_detect.caller_file_line(3), repr(char))) + + return super(BaseDevice, self).sendcontrol(char) + + def enable_ipv6(self, interface): + '''Enable ipv6 in interface ''' + raise Exception("Not implemented!") + + def disable_ipv6(self, interface): + '''Disable IPv6 in interface ''' + raise Exception("Not implemented!") + + def set_printk(self, CUR=1, DEF=1, MIN=1, BTDEF=7): + '''Print the when debug enabled ''' + raise Exception("Not implemented!") + + def prefer_ipv4(self, pref=True): + """Edits the /etc/gai.conf file + + This is to give/remove ipv4 preference (by default ipv6 is preferred) + See /etc/gai.conf inline comments for more details + """ + raise Exception("Not implemented!") + + def ping(self, ping_ip, source_ip=None, ping_count=4, ping_interface=None): + '''Check Ping verification from device ''' + raise Exception("Not implemented!") + + def reset(self, break_into_uboot=False): + '''Power-cycle this device.''' + if not break_into_uboot: + self.power.reset() + return + for attempt in range(3): + try: + self.power.reset() + self.expect('U-Boot', timeout=30) + self.expect('Hit any key ') + self.sendline('\n\n\n\n\n\n\n') # try really hard + self.expect(self.uprompt, timeout=4) + # Confirm we are in uboot by typing any command. + # If we weren't in uboot, we wouldn't see the command + # that we type. + self.sendline('echo FOO') + self.expect('echo FOO', timeout=4) + self.expect(self.uprompt, timeout=4) + return + except Exception as e: + print(e) + print("\nWe appeared to have failed to break into U-Boot...") + + def check_memory_addresses(self): + '''Check/set memory addresses and size for proper flashing.''' + raise Exception("Not implemented!") + + def flash_uboot(self, uboot): + raise Exception('Code not written for flash_uboot for this board type, %s' % self.model) + + def flash_rootfs(self, ROOTFS): + raise Exception('Code not written for flash_rootfs for this board type, %s' % self.model) + + def flash_linux(self, KERNEL): + raise Exception('Code not written for flash_linux for this board type, %s.' % self.model) + + def flash_meta(self, META_BUILD, wan, lan): + raise Exception('Code not written for flash_meta for this board type, %s.' % self.model) + + def prepare_nfsroot(self, NFSROOT): + raise Exception('Code not written for prepare_nfsroot for this board type, %s.' % self.model) + + def kill_console_at_exit(self): + '''killing console ''' + self.kill(signal.SIGKILL) + + def get_dns_server(self): + '''Getting dns server ip address ''' + raise Exception("Not implemented!") + + def touch(self): + '''Keeps consoles active, so they don't disconnect for long running activities''' + self.sendline() + + def boot_linux(self, rootfs=None, bootargs=""): + raise Exception("\nWARNING: We don't know how to boot this board to linux " + "please write the code to do so.") diff --git a/devices/common.py b/boardfarm/devices/common.py similarity index 54% rename from devices/common.py rename to boardfarm/devices/common.py index 2bca909e..75236aa1 100644 --- a/devices/common.py +++ b/boardfarm/devices/common.py @@ -6,6 +6,7 @@ # The full text can be found in LICENSE in the root directory. # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from lib.common import cmd_exists import binascii import os import pexpect @@ -14,6 +15,7 @@ import termcolor + def get_file_magic(fname, num_bytes=4): '''Return the first few bytes from a file to determine the type.''' if fname.startswith("http://") or fname.startswith("https://"): @@ -26,26 +28,31 @@ def get_file_magic(fname, num_bytes=4): f.close() return binascii.hexlify(data) -def copy_file_to_server(cmd, password): + +def copy_file_to_server(cmd, password, target="/tftpboot/"): '''Requires a command like ssh/scp to transfer a file, and a password. - Run the command and enter the password if asked for one.''' + Run the command and enter the password if asked for one. + NOTE: The command must print the filename once the copy has completed''' for attempt in range(5): try: print_bold(cmd) - p = pexpect.spawn(command='/bin/bash', args=['-c', cmd], timeout=120) + p = pexpect.spawn(command='/bin/bash', args=['-c', cmd], timeout=240) p.logfile_read = sys.stdout - i = p.expect(["yes/no", "password:", "/tftpboot/.*"]) + i = p.expect(["yes/no", "password:", "%s.*" % target]) if i == 0: - p.sendline("yes") - i = p.expect(["not used", "password:", "/tftpboot/.*"], timeout=45) + p.sendline("yes") + i = p.expect(["not used", "password:", "%s.*" % target], timeout=45) if i == 1: - p.sendline("%s" % password) - p.expect("/tftpboot/.*", timeout=120) + p.sendline("%s" % password) + p.expect("%s.*" % target, timeout=120) fname = p.match.group(0).strip() print_bold("\nfile: %s" % fname) + except pexpect.EOF: + print_bold("EOF exception: unable to extract filename (should be echoed by command)!") + print_bold("EOF exception: command: %s" % cmd) except Exception as e: print_bold(e) print_bold("tried to copy file to server and failed!") @@ -55,26 +62,36 @@ def copy_file_to_server(cmd, password): print_bold("Unable to copy file to server, exiting") raise Exception("Unable to copy file to server") + def download_from_web(url, server, username, password, port): - try: - urllib2.urlopen(url) - except urllib2.HTTPError as e: - print_bold("HTTP url %s returned %s, exiting" % (url, e.code)) - sys.exit(10) - except urllib2.URLError as e: - print_bold("HTTP url %s returned %s, exiting" % (url, e.args)) - sys.exit(11) - cmd = "curl -L -k '%s' 2>/dev/null | ssh -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -x %s@%s \"tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % (url, port, username, server) + pipe = "" + if cmd_exists('pv'): + pipe = " pv | " + + cmd = "curl -n -L -k '%s' 2>/dev/null | %s ssh -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -x %s@%s \"mkdir -p /tftpboot/tmp; tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % ( + url, pipe, port, username, server) return copy_file_to_server(cmd, password) + def scp_to_tftp_server(fname, server, username, password, port): # local file verify it exists first if not os.path.isfile(fname): print_bold("File passed as parameter does not exist! Failing!\n") sys.exit(10) - cmd = "cat %s | ssh -p %s -x %s@%s \"tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % (fname, port, username, server) + pipe = "" + if cmd_exists('pv'): + pipe = " pv | " + + cmd = "cat %s | %s ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p %s -x %s@%s \"mkdir -p /tftpboot/tmp; tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % ( + fname, pipe, port, username, server) return copy_file_to_server(cmd, password) + +def scp_from(fname, server, username, password, port, dest): + cmd = "scp -P %s -r %s@%s:%s %s; echo DONE" % (port, username, server, fname, dest) + copy_file_to_server(cmd, password, target='DONE') + + def print_bold(msg): termcolor.cprint(msg, None, attrs=['bold']) diff --git a/devices/configreader.py b/boardfarm/devices/configreader.py similarity index 100% rename from devices/configreader.py rename to boardfarm/devices/configreader.py diff --git a/devices/connection_decider.py b/boardfarm/devices/connection_decider.py similarity index 87% rename from devices/connection_decider.py rename to boardfarm/devices/connection_decider.py index f9eaf751..273d7b41 100644 --- a/devices/connection_decider.py +++ b/boardfarm/devices/connection_decider.py @@ -2,6 +2,7 @@ import local_serial_connection import ssh_connection import local_cmd +import kermit_connection def connection(conn_type, device, **kwargs): ''' @@ -19,6 +20,9 @@ def connection(conn_type, device, **kwargs): if conn_type in ("local_cmd"): return local_cmd.LocalCmd(device=device, **kwargs) + if conn_type in ("kermit_cmd"): + return kermit_connection.KermitConnection(device=device, **kwargs) + # Default for all other models print("\nWARNING: Unknown connection type '%s'." % type) print("Please check spelling, or write an appropriate class " diff --git a/boardfarm/devices/cougarpark.py b/boardfarm/devices/cougarpark.py new file mode 100644 index 00000000..a5392ff9 --- /dev/null +++ b/boardfarm/devices/cougarpark.py @@ -0,0 +1,168 @@ +# Copyright (c) 2017 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import common +import openwrt_router +import pexpect +import ipaddress +import connection_decider +import signal +import linux +import sys + +KEY_ESCAPE = '\x1B' +KEY_UP = '\x1b[A' +KEY_DOWN = '\x1b[B' +KEY_F4 = '\x1b[OS' + +MODE_DISABLED = 0 +MODE_NSGMII1 = 2 +MODE_NSGMII2 = 3 + +class CougarPark(openwrt_router.OpenWrtRouter): + ''' + Intel Cougar Park board + ''' + model = ("cougarpark") + + wan_iface = "erouter0" + lan_iface = "brlan0" + + lan_network = ipaddress.IPv4Network(u"192.168.0.0/24") + lan_gateway = ipaddress.IPv4Address(u"192.168.0.1") + + uprompt = ["Shell>"] + delaybetweenchar = 0.2 + uboot_ddr_addr = "0x10000000" + uboot_eth = "eth0" + + arm = None + + def __init__(self, *args, **kwargs): + super(CougarPark, self).__init__(*args, **kwargs) + + del kwargs['conn_cmd'] + self.arm = pexpect.spawn.__new__(linux.LinuxDevice) + arm_conn = connection_decider.connection(kwargs['connection_type'], device=self.arm, conn_cmd=self.conn_list[1], **kwargs) + arm_conn.connect() + self.consoles.append(self.arm) + self.arm.logfile_read = sys.stdout + self.arm.start = self.start + + def kill_console_at_exit(self): + self.kill(signal.SIGKILL) + self.arm.kill(signal.SIGKILL) + + def wait_for_boot(self): + ''' + Break into Shell. + ''' + # Try to break into uboot + self.expect('Remaining timeout:', timeout=30) + self.send(KEY_ESCAPE) + self.expect('startup.nsh',timeout=30) + self.send(KEY_ESCAPE) + self.expect_exact(self.uprompt, timeout=30) + + def switch_to_mode(self, index): + self.sendline('exit') + self.expect_exact('Device Manager') + self.send(KEY_DOWN) + self.send(KEY_DOWN) + self.sendline(KEY_DOWN) + self.expect_exact('System Setup') + self.sendline() + self.expect_exact('Puma7 Configuration') + self.sendline(KEY_DOWN) + self.expect_exact('BIOS Network Configuration') + self.send(KEY_DOWN) + self.send(KEY_DOWN) + self.sendline(KEY_DOWN) + self.expect_exact('Disabled') + self.send(KEY_UP) + self.send(KEY_UP) + self.send(KEY_UP) + self.send(KEY_UP) + for i in range(1,index): + self.send(KEY_DOWN) + self.sendline() + self.send(KEY_F4) + self.send(KEY_F4) + self.send('Y') + self.send(KEY_ESCAPE) + self.send(KEY_UP) + self.send(KEY_UP) + self.sendline(KEY_UP) + self.sendline() + self.wait_for_boot() + + def setup_uboot_network(self, tftp_server): + from devices import lan + + # we override to use LAN because that's the only way it works for this device + lan.start_tftp_server() + tftp_server_ip = lan.gw + self.tftp_server_int = lan.gw + + # line sep for UEFI + self.linesep = '\x0D' + + self.switch_to_mode(MODE_NSGMII2) + + self.sendline('ifconfig -l') + self.expect_exact(self.uprompt) + self.sendline('ifconfig -c %s' % self.uboot_eth) + self.expect_exact(self.uprompt, timeout=30) + self.sendline('ifconfig -s %s static %s 255.255.255.0 %s' % (self.uboot_eth, tftp_server+1, tftp_server)) + self.expect_exact(self.uprompt, timeout=30) + self.sendline('ifconfig -l') + self.expect_exact(self.uprompt) + self.sendline('ping %s' % tftp_server) + if 0 == self.expect(['Echo request sequence 1 timeout', '10 packets transmitted, 10 received, 0% packet loss, time 0ms']): + raise Exception("Failing to ping tftp server, aborting") + self.expect_exact(self.uprompt, timeout=30) + + def flash_linux(self, KERNEL): + print("\n===== Updating kernel and rootfs =====\n") + from devices import lan + filename = self.prepare_file(KERNEL, tserver=lan.ipaddr, tport=lan.port) + + self.sendline('tftp -p %s -d %s %s' % (self.uboot_ddr_addr, lan.tftp_server_ip_int(), filename)) + self.expect_exact('TFTP general status Success') + if 0 == self.expect_exact(['TFTP TFTP Read File status Time out'] + self.uprompt, timeout=60): + raise Exception("TFTP timed out") + + self.sendline('update -a A -s %s' % self.uboot_ddr_addr) + if 0 == self.expect_exact(['UImage has wrong version magic', 'Congrats! Looks like everything went as planned! Your flash has been updated! Have a good day!']): + raise Exception("Image looks corrupt") + self.expect_exact(self.uprompt, timeout=30) + + def boot_linux(self, rootfs=None, bootargs=None): + common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) + self.switch_to_mode(MODE_DISABLED) + self.sendline('npcpu start') + self.sendline('bootkernel -c %kernel_cmd_line%') + self.delaybetweenchar = None + + def wait_for_networkxxx(self): + self.sendline('ip link set %s down' % self.wan_iface) + self.expect(self.prompt) + self.sendline('ip link set %s name foobar' % self.wan_iface) + self.expect(self.prompt) + self.sendline('ip link set foobar up') + self.expect(self.prompt) + self.sendline('brctl delif brlan0 nsgmii0') + self.expect(self.prompt) + self.sendline('brctl addbr %s' % self.wan_iface) + self.expect(self.prompt) + self.sendline('brctl addif %s nsgmii0' % self.wan_iface) + self.expect(self.prompt) + self.sendline('brctl addif %s foobar' % self.wan_iface) + self.expect(self.prompt) + self.sendline('dhclient %s' % self.wan_iface) + self.expect(self.prompt) + super(CougarPark, self).wait_for_network() diff --git a/boardfarm/devices/debian.py b/boardfarm/devices/debian.py new file mode 100755 index 00000000..19dd7fab --- /dev/null +++ b/boardfarm/devices/debian.py @@ -0,0 +1,767 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import sys +import time +import pexpect +import linux +import atexit +import os +import ipaddress + +from termcolor import colored, cprint + +class DebianBox(linux.LinuxDevice): + ''' + A linux machine running an ssh server. + ''' + + model = ('debian') + prompt = ['root\\@.*:.*#', '/ # ', ".*:~ #" ] + static_route = None + static_ip = False + wan_dhcp = False + wan_dhcpv6 = False + wan_no_eth0 = False + pkgs_installed = False + install_pkgs_after_dhcp = False + is_bridged = False + shared_tftp_server = False + wan_dhcp_server = True + tftp_device = None + tftp_dir = '/tftpboot' + mgmt_dns = None + + iface_dut = "eth1" + gw = None + + # TODO: does this need to be calculated? + gwv6 = None + ipv6_prefix = 64 + + def __init__(self, + *args, + **kwargs): + self.args = args + self.kwargs = kwargs + name = kwargs.pop('name', None) + ipaddr = kwargs.pop('ipaddr', None) + color = kwargs.pop('color', 'black') + username = kwargs.pop('username', 'root') + password = kwargs.pop('password', 'bigfoot1') + port = kwargs.pop('port', '22') + output = kwargs.pop('output', sys.stdout) + reboot = kwargs.pop('reboot', False) + location = kwargs.pop('location', None) + pre_cmd_host = kwargs.pop('pre_cmd_host', None) + cmd = kwargs.pop('cmd', None) + post_cmd_host = kwargs.pop('post_cmd_host', None) + post_cmd = kwargs.pop('post_cmd', None) + cleanup_cmd = kwargs.pop('cleanup_cmd', None) + env = kwargs.pop('env', None) + lan_network = kwargs.pop('lan_network', ipaddress.IPv4Network(u"192.168.1.0/24")) + lan_gateway = kwargs.pop('lan_gateway', ipaddress.IPv4Address(u"192.168.1.1")) + + self.http_proxy = kwargs.pop('http_proxy', None) + + if pre_cmd_host is not None: + sys.stdout.write("\tRunning pre_cmd_host.... ") + sys.stdout.flush() + phc = pexpect.spawn(command='bash', args=['-c', pre_cmd_host], env=env) + phc.expect(pexpect.EOF, timeout=120) + print("\tpre_cmd_host done") + + if ipaddr is not None: + pexpect.spawn.__init__(self, + command="ssh", + args=['%s@%s' % (username, ipaddr), + '-p', port, + '-o', 'StrictHostKeyChecking=no', + '-o', 'UserKnownHostsFile=/dev/null', + '-o', 'ServerAliveInterval=60', + '-o', 'ServerAliveCountMax=5']) + + self.ipaddr = ipaddr + + if cleanup_cmd is not None: + self.cleanup_cmd = cleanup_cmd + atexit.register(self.run_cleanup_cmd) + + if cmd is not None: + sys.stdout.write("\tRunning cmd.... ") + sys.stdout.flush() + pexpect.spawn.__init__(self, command="bash", args=['-c', cmd], env=env) + self.ipaddr = None + print("\tcmd done") + + self.name = name + self.color = color + self.output = output + self.username = username + if username != "root": + self.prompt.append('%s\\@.*:.*$' % username) + self.password = password + self.port = port + self.location = location + self.env=env + self.lan_network = lan_network + self.lan_gateway = lan_gateway + self.tftp_device = self + + + # we need to pick a non-conflicting private network here + # also we want it to be consistant and not random for a particular + # board + if self.gw is None: + if (lan_gateway - lan_network.num_addresses).is_private: + self.gw = lan_gateway - lan_network.num_addresses + else: + self.gw = lan_gateway + lan_network.num_addresses + + self.gw_ng = ipaddress.IPv4Interface(str(self.gw).decode('utf-8') + '/' + str(lan_network.netmask)) + self.nw = self.gw_ng.network + self.gw_prefixlen = self.nw.prefixlen + + # override above values if set in wan options + if 'options' in kwargs: + options = [x.strip() for x in kwargs['options'].split(',')] + for opt in options: + if opt.startswith('wan-static-ip:'): + value = opt.replace('wan-static-ip:', '') + self.gw = ipaddress.IPv4Address(value.split('/')[0]) + if '/' not in value: + value = value + (u'/24') + # TODO: use IPv4 and IPv6 interface object everywhere in this class + self.gw_ng = ipaddress.IPv4Interface(value) + self.nw = self.gw_ng.network + self.gw_prefixlen = self.nw.prefixlen + self.static_ip = True + if opt.startswith('wan-static-ipv6:'): + if "/" in opt: + ipv6_interface=ipaddress.IPv6Interface(opt.replace('wan-static-ipv6:', '')) + self.gwv6 = ipv6_interface.ip + self.ipv6_prefix = ipv6_interface._prefixlen + else: + self.gwv6 = ipaddress.IPv6Address(opt.replace('wan-static-ipv6:', '')) + if opt.startswith('wan-static-route:'): + self.static_route = opt.replace('wan-static-route:', '').replace('-', ' via ') + # TODO: remove wan-static-route at some point above + if opt.startswith('static-route:'): + self.static_route = opt.replace('static-route:', '').replace('-', ' via ') + if opt == 'wan-dhcp-client': + self.wan_dhcp = True + if opt == 'wan-no-eth0': + self.wan_no_eth0 = True + if opt == 'wan-no-dhcp-sever': + self.wan_dhcp_server = False + if opt == 'wan-dhcp-client-v6': + self.wan_dhcpv6 = True + if opt.startswith('mgmt-dns:'): + value = opt.replace('mgmt-dns:', '') + self.mgmt_dns = ipaddress.IPv4Address(value.split('/')[0]) + + try: + i = self.expect(["yes/no", "assword:", "Last login", username+".*'s password:"] + self.prompt, timeout=30) + except pexpect.TIMEOUT: + raise Exception("Unable to connect to %s." % name) + except pexpect.EOF: + if hasattr(self, "before"): + print(self.before) + raise Exception("Unable to connect to %s." % name) + if i == 0: + self.sendline("yes") + i = self.expect(["Last login", "assword:"]) + if i == 1 or i == 3: + self.sendline(password) + else: + pass + # if we did initially get a prompt wait for one here + if i < 4: + self.expect(self.prompt) + + # attempts to fix the cli colums size + self.set_cli_size(200) + + + if ipaddr is None: + self.sendline('hostname') + self.expect('hostname') + self.expect(self.prompt) + ipaddr = self.ipaddr = self.before.strip() + + self.sendline('alias mgmt') + idx = self.expect(['alias mgmt=', pexpect.TIMEOUT], timeout=10) + if idx == 0: + self.expect(self.prompt) + self.sendline('alias apt="mgmt apt"; alias apt-get="mgmt apt-get"') + self.expect_exact('alias apt="mgmt apt"; alias apt-get="mgmt apt-get"') + self.expect(self.prompt) + + cmsg = '%s ' % ipaddr + if self.port != 22: + cmsg += '%s port ' % port + cmsg += 'device console = ' + cmsg += colored('%s (%s)' % (color, name), color) + cprint(cmsg, None, attrs=['bold']) + + if post_cmd_host is not None: + sys.stdout.write("\tRunning post_cmd_host.... ") + sys.stdout.flush() + phc = pexpect.spawn(command='bash', args=['-c', post_cmd_host], env=env) + i = phc.expect([pexpect.EOF, pexpect.TIMEOUT, 'password']) + if i > 0: + print("\tpost_cmd_host did not complete, it likely failed\n") + else: + print("\tpost_cmd_host done") + + if post_cmd is not None: + sys.stdout.write("\tRunning post_cmd.... ") + sys.stdout.flush() + env_prefix="" + for k, v in env.iteritems(): + env_prefix += "export %s=%s; " % (k, v) + + self.sendline(env_prefix + post_cmd) + self.expect(self.prompt) + print("\tpost_cmd done") + + if reboot: + self.reset() + + self.logfile_read = output + + def run_cleanup_cmd(self): + sys.stdout.write("Running cleanup_cmd on %s..." % self.name) + sys.stdout.flush() + cc = pexpect.spawn(command='bash', args=['-c', self.cleanup_cmd], env=self.env) + cc.expect(pexpect.EOF, timeout=120) + print("cleanup_cmd done.") + + def reset(self): + self.sendline('reboot') + self.expect(['going down','disconnected']) + try: + self.expect(self.prompt, timeout=10) + except: + pass + time.sleep(15) # Wait for the network to go down. + for i in range(0, 20): + try: + pexpect.spawn('ping -w 1 -c 1 ' + self.name).expect('64 bytes', timeout=1) + except: + print(self.name + " not up yet, after %s seconds." % (i + 15)) + else: + print("%s is back after %s seconds, waiting for network daemons to spawn." % (self.name, i + 14)) + time.sleep(15) + break + self.__init__(self.name, self.color, + self.output, self.username, + self.password, self.port, + reboot=False) + + def install_pkgs(self): + if self.pkgs_installed == True: + return + + self.sendline('echo "Acquire::ForceIPv4 "true";" > /etc/apt/apt.conf.d/99force-ipv4') + self.expect(self.prompt) + + if not self.wan_no_eth0 and not self.wan_dhcp and not self.install_pkgs_after_dhcp and not getattr(self, 'standalone_provisioner', False): + self.sendline('ifconfig %s down' % self.iface_dut) + self.expect(self.prompt) + + pkgs = "isc-dhcp-server xinetd tinyproxy curl apache2-utils nmap psmisc vim-common tftpd-hpa pppoe isc-dhcp-server procps iptables lighttpd psmisc dnsmasq xxd" + + def _install_pkgs(): + self.sendline('apt-get update && apt-get -o DPkg::Options::="--force-confnew" -qy install %s' % pkgs) + if 0 == self.expect(['Reading package', pexpect.TIMEOUT], timeout=60): + self.expect(self.prompt, timeout=300) + else: + print("Failed to download packages, things might not work") + self.sendcontrol('c') + self.expect(self.prompt) + + self.pkgs_installed = True + + # TODO: use netns for all this? + undo_default_route = None + self.sendline('ping -4 -c1 deb.debian.org') + i = self.expect(['ping: unknown host', 'connect: Network is unreachable', pexpect.TIMEOUT] + self.prompt, timeout=10) + if 0 == i: + # TODO: don't reference eth0, but the uplink iface + self.sendline("echo SYNC; ip route list | grep 'via.*dev eth0' | awk '{print $3}'") + self.expect_exact("SYNC\r\n") + if 0 == self.expect(['(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\r\n'] + self.prompt, timeout=5): + possible_default_gw = self.match.group(1) + self.sendline("ip route add default via %s" % possible_default_gw) + self.expect(self.prompt) + self.sendline('ping -c1 deb.debian.org') + self.expect(self.prompt) + undo_default_route = possible_default_gw + self.sendline('apt-get update && apt-get -o DPkg::Options::="--force-confnew" -qy install %s' % pkgs) + if 0 == self.expect(['Reading package', pexpect.TIMEOUT], timeout=60): + self.expect(self.prompt, timeout=300) + else: + print("Failed to download packages, things might not work") + self.sendcontrol('c') + self.expect(self.prompt) + elif 1 == i: + if self.install_pkgs_after_dhcp: + _install_pkgs() + else: + self.install_pkgs_after_dhcp = True + return + elif 2 == i: + self.sendcontrol('c') + self.expect(self.prompt) + else: + _install_pkgs() + + if undo_default_route is not None: + self.sendline("ip route del default via %s" % undo_default_route) + self.expect(self.prompt) + + def turn_on_pppoe(self): + self.sendline('cat > /etc/ppp/pppoe-server-options << EOF') + self.sendline('noauth') + self.sendline('ms-dns 8.8.8.8') + self.sendline('ms-dns 8.8.4.4') + self.sendline('EOF') + self.expect(self.prompt) + self.sendline('pppoe-server -k -I %s -L 192.168.2.1 -R 192.168.2.10 -N 4' % self.iface_dut) + self.expect(self.prompt) + + def turn_off_pppoe(self): + self.sendline("\nkillall pppoe-server pppoe pppd") + self.expect("pppd") + self.expect(self.prompt) + + def start_tftp_server(self): + # we can call this first, before configure so we need to do this here + # as well + self.install_pkgs() + # the entire reason to start tftp is to copy files to devices + # which we do via ssh so let's start that as well + self.start_sshd_server() + + try: + eth1_addr = self.get_interface_ipaddr(self.iface_dut) + except: + eth1_addr = None + + # set WAN ip address, for now this will always be this address for the device side + # TODO: fix gateway for non-WAN tftp_server + if self.gw != eth1_addr: + self.sendline('ifconfig %s %s' % (self.iface_dut, self.gw_ng)) + self.expect(self.prompt) + self.sendline('ifconfig %s up' % self.iface_dut) + self.expect(self.prompt) + + #configure tftp server + self.sendline('/etc/init.d/tftpd-hpa stop') + self.expect('Stopping') + self.expect(self.prompt) + if not self.shared_tftp_server: + self.sendline('rm -rf '+self.tftp_dir) + self.expect(self.prompt) + self.sendline('rm -rf /srv/tftp') + self.expect(self.prompt) + self.sendline('mkdir -p /srv/tftp') + self.expect(self.prompt) + self.sendline('ln -sf /srv/tftp/ '+self.tftp_dir) + self.expect(self.prompt) + self.sendline('mkdir -p '+self.tftp_dir+'/tmp') + self.expect(self.prompt) + self.sendline('chmod a+w '+self.tftp_dir+'/tmp') + self.expect(self.prompt) + self.sendline('mkdir -p '+self.tftp_dir+'/crashdump') + self.expect(self.prompt) + self.sendline('chmod a+w '+self.tftp_dir+'/crashdump') + self.expect(self.prompt) + self.sendline('sed /TFTP_OPTIONS/d -i /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('echo TFTP_OPTIONS=\\"--secure --create\\" >> /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('sed /TFTP_ADDRESS/d -i /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('echo TFTP_ADDRESS=\\":69\\" >> /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('sed /TFTP_DIRECTORY/d -i /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('echo TFTP_DIRECTORY=\\"/srv/tftp\\" >> /etc/default/tftpd-hpa') + self.expect(self.prompt) + self.sendline('/etc/init.d/tftpd-hpa restart') + self.expect(self.prompt) + + def restart_tftp_server(self): + self.sendline('\n/etc/init.d/tftpd-hpa restart') + self.expect('Restarting') + self.expect(self.prompt) + + def start_sshd_server(self): + self.sendline('/etc/init.d/rsyslog start') + self.expect(self.prompt) + self.sendline('/etc/init.d/ssh start') + self.expect(self.prompt) + self.sendline('sed "s/.*PermitRootLogin.*/PermitRootLogin yes/g" -i /etc/ssh/sshd_config') + self.expect(self.prompt) + self.sendline('/etc/init.d/ssh reload') + self.expect(self.prompt) + + def configure(self, kind, config=[]): + # TODO: wan needs to enable on more so we can route out? + self.enable_ipv6(self.iface_dut) + self.install_pkgs() + self.start_sshd_server() + if kind == "wan_device": + self.setup_as_wan_gateway() + elif kind == "lan_device": + self.setup_as_lan_device() + + if self.static_route is not None: + # TODO: add some ppint handle this more robustly + self.send('ip route del %s; ' % self.static_route.split(' via ')[0]) + self.sendline('ip route add %s' % self.static_route) + self.expect(self.prompt) + + def setup_dhcp_server(self): + if not self.wan_dhcp_server: + return + + # configure DHCP server + self.sendline('/etc/init.d/isc-dhcp-server stop') + self.expect(self.prompt) + self.sendline('sed s/INTERFACES=.*/INTERFACES=\\"%s\\"/g -i /etc/default/isc-dhcp-server' % self.iface_dut) + self.expect(self.prompt) + self.sendline('sed s/INTERFACESv4=.*/INTERFACESv4=\\"%s\\"/g -i /etc/default/isc-dhcp-server' % self.iface_dut) + self.expect(self.prompt) + self.sendline('sed s/INTERFACESv6=.*/INTERFACESv6=\\"%s\\"/g -i /etc/default/isc-dhcp-server' % self.iface_dut) + self.expect(self.prompt) + self.sendline('cat > /etc/dhcp/dhcpd.conf << EOF') + self.sendline('ddns-update-style none;') + self.sendline('option domain-name "bigfoot-test";') + self.sendline('option domain-name-servers %s;' % self.gw) + self.sendline('default-lease-time 600;') + self.sendline('max-lease-time 7200;') + # use the same netmask as the lan device + self.sendline('subnet %s netmask %s {' % (self.nw.network_address, self.nw.netmask)) + self.sendline(' range %s %s;' % (self.nw.network_address + 10, self.nw.network_address + 100)) + self.sendline(' option routers %s;' % self.gw) + self.sendline('}') + self.sendline('EOF') + self.expect(self.prompt) + self.sendline('/etc/init.d/isc-dhcp-server start') + self.expect(['Starting ISC DHCP(v4)? server.*dhcpd.', 'Starting isc-dhcp-server.*']) + self.expect(self.prompt) + + def setup_dnsmasq(self): + self.sendline('cat > /etc/dnsmasq.conf << EOF') + self.sendline('server=8.8.4.4') + self.sendline('listen-address=127.0.0.1') + self.sendline('listen-address=%s' % self.gw) + self.sendline('addn-hosts=/etc/dnsmasq.hosts') #all additional hosts will be added to dnsmasq.hosts + self.sendline('EOF') + self.add_hosts() + self.sendline('/etc/init.d/dnsmasq restart') + self.expect(self.prompt) + self.sendline('echo "nameserver 127.0.0.1" > /etc/resolv.conf') + self.expect(self.prompt) + + def add_hosts(self): + #to add extra hosts(dict) to dnsmasq.hosts if dns has to run in wan container + import config + hosts={} + for device in config.board['devices']: + if 'ipaddr' in device: + domain_name=str(getattr(config, device['name']).name)+'.boardfarm.com' + device = getattr(config, device['name']) + if not hasattr(device, 'ipaddr'): + continue + hosts[domain_name] = str(device.ipaddr) + if hosts is not None: + self.sendline('cat > /etc/dnsmasq.hosts << EOF') + for key, value in hosts.iteritems(): + self.sendline(key+" "+ value) + self.sendline('EOF') + + def remove_hosts(self): + self.sendline('rm /etc/dnsmasq.hosts') + self.expect(self.prompt) + self.sendline('/etc/init.d/dnsmasq restart') + self.expect(self.prompt) + + def setup_as_wan_gateway(self): + + self.setup_dnsmasq() + + self.sendline('killall iperf ab hping3') + self.expect(self.prompt) + + # potential cleanup so this wan device works + self.sendline('iptables -t nat -X') + self.expect(self.prompt) + self.sendline('iptables -t nat -F') + self.expect(self.prompt) + + # set WAN ip address + if self.wan_dhcp: + self.sendline('/etc/init.d/isc-dhcp-server stop') + self.expect(self.prompt) + self.sendline('dhclient -r %s; dhclient %s' % (self.iface_dut, self.iface_dut)) + self.expect(self.prompt) + self.gw = self.get_interface_ipaddr(self.iface_dut) + elif not self.wan_no_eth0: + self.sendline('ifconfig %s %s' % (self.iface_dut, self.gw_ng)) + self.expect(self.prompt) + self.sendline('ifconfig %s up' % self.iface_dut) + self.expect(self.prompt) + if self.wan_dhcp_server: + self.setup_dhcp_server() + + if self.wan_dhcpv6 == True: + # we are bypass this for now (see http://patchwork.ozlabs.org/patch/117949/) + self.sendline('sysctl -w net.ipv6.conf.%s.accept_dad=0' % self.iface_dut) + self.expect(self.prompt) + try: + self.gwv6 = self.get_interface_ip6addr(self.iface_dut) + except: + self.sendline('dhclient -6 -i -r %s' % self.iface_dut) + self.expect(self.prompt) + self.sendline('dhclient -6 -i -v %s' % self.iface_dut) + self.expect(self.prompt) + self.sendline('ip -6 addr') + self.expect(self.prompt) + self.gwv6 = self.get_interface_ip6addr(self.iface_dut) + elif self.gwv6 is not None: + # we are bypass this for now (see http://patchwork.ozlabs.org/patch/117949/) + self.sendline('sysctl -w net.ipv6.conf.%s.accept_dad=0' % self.iface_dut) + self.expect(self.prompt) + self.sendline('ip -6 addr add %s/%s dev %s' % (self.gwv6, self.ipv6_prefix, self.iface_dut)) + self.expect(self.prompt) + + + # configure routing + self.sendline('sysctl net.ipv4.ip_forward=1') + self.expect(self.prompt) + self.sendline('sysctl net.ipv6.conf.all.forwarding=0') + self.expect(self.prompt) + + if self.wan_no_eth0 or self.wan_dhcp: + wan_uplink_iface = self.iface_dut + else: + wan_uplink_iface = "eth0" + + wan_ip_uplink = self.get_interface_ipaddr(wan_uplink_iface) + self.sendline('iptables -t nat -A POSTROUTING -o %s -j SNAT --to-source %s' % (wan_uplink_iface, wan_ip_uplink)) + self.expect(self.prompt) + + self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_timestamps') + self.expect(self.prompt) + self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_sack') + self.expect(self.prompt) + + self.sendline('ifconfig %s' % self.iface_dut) + self.expect(self.prompt) + + self.turn_off_pppoe() + + def setup_as_lan_device(self): + # potential cleanup so this wan device works + self.sendline('killall iperf ab hping3') + self.expect(self.prompt) + self.sendline('\niptables -t nat -X') + self.expect('iptables -t') + self.expect(self.prompt) + self.sendline('sysctl net.ipv4.ip_forward=1') + self.expect(self.prompt) + self.sendline('iptables -t nat -F; iptables -t nat -X') + self.expect(self.prompt) + self.sendline('iptables -F; iptables -X') + self.expect(self.prompt) + self.sendline('iptables -t nat -A PREROUTING -p tcp --dport 222 -j DNAT --to-destination %s:22' % self.lan_gateway) + self.expect(self.prompt) + self.sendline('iptables -t nat -A POSTROUTING -o %s -p tcp --dport 22 -j MASQUERADE' % self.iface_dut) + self.expect(self.prompt) + self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_timestamps') + self.expect(self.prompt) + self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_sack') + self.expect(self.prompt) + self.sendline('pkill --signal 9 -f dhclient.*%s' % self.iface_dut) + self.expect(self.prompt) + + def start_lan_client(self, wan_gw=None): + # very casual try for ipv6 addr, if we don't get one don't fail for now + try: + self.enable_ipv6(self.iface_dut) + # TODO: how to wait for stateless config? + self.get_interface_ip6addr(self.iface_dut) + except: + self.sendline('dhclient -6 -i -r %s' % self.iface_dut) + self.expect(self.prompt) + self.sendline('dhclient -6 -i -v %s' % self.iface_dut) + if 0 == self.expect([pexpect.TIMEOUT] + self.prompt, timeout=15): + self.sendcontrol('c') + self.expect(self.prompt) + self.sendline('ip -6 addr') + self.expect(self.prompt) + + # TODO: this should not be required (fix at some point...) + self.sendline('sysctl -w net.ipv6.conf.%s.accept_dad=0' % self.iface_dut) + self.sendline('ip link set down %s && ip link set up %s' % (self.iface_dut, self.iface_dut)) + self.expect(self.prompt) + self.disable_ipv6('eth0') + + self.sendline('\nifconfig %s up' % self.iface_dut) + self.expect('ifconfig %s up' % self.iface_dut) + self.expect(self.prompt) + self.sendline("dhclient -4 -r %s" % self.iface_dut) + self.expect(self.prompt) + self.sendline('\nifconfig %s 0.0.0.0' % self.iface_dut) + self.expect(self.prompt) + self.sendline('rm /var/lib/dhcp/dhclient.leases') + self.expect(self.prompt) + self.sendline("sed -e 's/mv -f $new_resolv_conf $resolv_conf/cat $new_resolv_conf > $resolv_conf/g' -i /sbin/dhclient-script") + self.expect(self.prompt) + + if self.mgmt_dns is not None: + self.sendline("sed '/append domain-name-servers %s/d' -i /etc/dhcp/dhclient.conf" % str(self.mgmt_dns)) + self.expect(self.prompt) + self.sendline('echo "append domain-name-servers %s;" >> /etc/dhcp/dhclient.conf' % str(self.mgmt_dns)) + self.expect(self.prompt) + + # TODO: don't hard code eth0 + self.sendline('ip route del default dev eth0') + self.expect(self.prompt) + for attempt in range(3): + try: + self.sendline('dhclient -4 -v %s' % self.iface_dut) + self.expect('DHCPOFFER', timeout=30) + self.expect(self.prompt) + break + except: + self.sendcontrol('c') + else: + raise Exception("Error: Device on LAN couldn't obtain address via DHCP.") + + self.sendline('cat /etc/resolv.conf') + self.expect(self.prompt) + self.sendline('ip addr show dev %s' % self.iface_dut) + self.expect(self.prompt) + self.sendline('ip route') + # TODO: we should verify this so other way, because the they could be the same subnets + # in theory + i = self.expect(['default via %s dev %s' % (self.lan_gateway, self.iface_dut), pexpect.TIMEOUT], timeout=5) + if i == 1: + # bridged mode + self.is_bridged = True + # update gw + self.sendline("ip route list 0/0 | awk '{print $3}'") + self.expect_exact("ip route list 0/0 | awk '{print $3}'") + self.expect(self.prompt) + self.lan_gateway = ipaddress.IPv4Address(self.before.strip().decode()) + + ip_addr = self.get_interface_ipaddr(self.iface_dut) + self.sendline("ip route | grep %s | awk '{print $1}'" % ip_addr) + self.expect_exact("ip route | grep %s | awk '{print $1}'" % ip_addr) + self.expect(self.prompt) + self.lan_network = ipaddress.IPv4Network(self.before.strip().decode()) + self.sendline('ip -6 route') + self.expect(self.prompt) + + # Setup HTTP proxy, so board webserver is accessible via this device + self.sendline('curl --version') + self.expect_exact('curl --version') + self.expect(self.prompt) + self.sendline('ab -V') + self.expect(self.prompt) + self.sendline('nmap --version') + self.expect(self.prompt) + # TODO: determine which config file is the correct one... but for now just modify both + for f in ['/etc/tinyproxy.conf', '/etc/tinyproxy/tinyproxy.conf']: + self.sendline("sed -i 's/^Port 8888/Port 8080/' %s" % f) + self.expect(self.prompt) + self.sendline("sed 's/#Allow/Allow/g' -i %s" % f) + self.expect(self.prompt) + self.sendline("sed '/Listen/d' -i %s" % f) + self.expect(self.prompt) + self.sendline('echo "Listen 0.0.0.0" >> %s' % f) + self.expect(self.prompt) + self.sendline('echo "Listen ::" >> %s' % f) + self.expect(self.prompt) + self.sendline('/etc/init.d/tinyproxy restart') + self.expect('Restarting') + self.expect(self.prompt) + # Write a useful ssh config for routers + self.sendline('mkdir -p ~/.ssh') + self.sendline('cat > ~/.ssh/config << EOF') + self.sendline('Host %s' % self.lan_gateway) + self.sendline('StrictHostKeyChecking no') + self.sendline('UserKnownHostsFile=/dev/null') + self.sendline('') + self.sendline('Host krouter') + self.sendline('Hostname %s' % self.lan_gateway) + self.sendline('StrictHostKeyChecking no') + self.sendline('UserKnownHostsFile=/dev/null') + self.sendline('EOF') + self.expect(self.prompt) + # Copy an id to the router so people don't have to type a password to ssh or scp + self.sendline('nc %s 22 -w 1 | cut -c1-3' % self.lan_gateway) + self.expect_exact('nc %s 22 -w 1 | cut -c1-3' % self.lan_gateway) + if 0 == self.expect(['SSH'] + self.prompt, timeout=5) and not self.is_bridged: + self.sendcontrol('c') + self.expect(self.prompt) + self.sendline('[ -e /root/.ssh/id_rsa ] || ssh-keygen -N "" -f /root/.ssh/id_rsa') + if 0 != self.expect(['Protocol mismatch.'] + self.prompt): + self.sendline('scp ~/.ssh/id_rsa.pub %s:/etc/dropbear/authorized_keys' % self.lan_gateway) + if 0 == self.expect(['assword:'] + self.prompt): + self.sendline('password') + self.expect(self.prompt) + else: + self.sendcontrol('c') + self.expect(self.prompt) + + if self.install_pkgs_after_dhcp: + self.install_pkgs() + + if wan_gw is not None and 'options' in self.kwargs and \ + 'lan-fixed-route-to-wan' in self.kwargs['options']: + self.sendline('ip route add %s via %s' % (wan_gw, self.lan_gateway)) + self.expect(self.prompt) + + def tftp_server_ip_int(self): + '''Returns the DUT facing side tftp server ip''' + return self.gw + + def tftp_server_ipv6_int(self): + '''Returns the DUT facing side tftp server ipv6''' + return self.gwv6 + +if __name__ == '__main__': + # Example use + try: + ipaddr, port = sys.argv[1].split(':') + except: + raise Exception("First argument should be in form of ipaddr:port") + dev = DebianBox(ipaddr=ipaddr, + color='blue', + username="root", + password="bigfoot1", + port=port) + dev.sendline('echo Hello') + dev.expect('Hello', timeout=4) + dev.expect(dev.prompt) + + if sys.argv[2] == "setup_as_lan_device": + dev.configure("lan_device") + if sys.argv[2] == "setup_as_wan_gateway": + dev.configure("wan_device") + if sys.argv[2] == "test_voip": + sys.path.insert(0, os.getcwd()) + sys.path.insert(0, os.getcwd() + '/tests') + from lib import installers + + installers.install_asterisk(dev) diff --git a/boardfarm/devices/debian_isc.py b/boardfarm/devices/debian_isc.py new file mode 100644 index 00000000..0aaf7359 --- /dev/null +++ b/boardfarm/devices/debian_isc.py @@ -0,0 +1,655 @@ +import ipaddress +import os +import pexpect +from lib.regexlib import ValidIpv4AddressRegex +import re +import glob + +import debian + +class DebianISCProvisioner(debian.DebianBox): + ''' + Linux based provisioner using ISC DHCP server + ''' + + model = ('debian-isc-provisioner') + + wan_cmts_provisioner = False + standalone_provisioner = True + wan_dhcp_server = False + + # default CM specific settings + default_lease_time = 604800 + max_lease_time = 604800; + + def __init__(self, *args, **kwargs): + + self.cm_network = ipaddress.IPv4Network(kwargs.pop('cm_network', u"192.168.200.0/24")) + self.cm_gateway = ipaddress.IPv4Address(kwargs.pop('cm_gateway', u"192.168.200.1")) + self.mta_network = ipaddress.IPv4Network(kwargs.pop('mta_network', u"192.168.201.0/24")) + self.mta_gateway = ipaddress.IPv4Address(kwargs.pop('mta_gateway', u"192.168.201.1")) + self.open_network = ipaddress.IPv4Network(kwargs.pop('open_network', u"192.168.202.0/24")) + self.open_gateway = ipaddress.IPv4Address(kwargs.pop('open_gateway', u"192.168.202.1")) + self.prov_network = ipaddress.IPv4Network(kwargs.pop('prov_network', u"192.168.3.0/24")) + self.prov_gateway = ipaddress.IPv4Address(kwargs.pop('prov_gateway', u"192.168.3.222")) + self.prov_ip = ipaddress.IPv4Address(kwargs.pop('prov_ip', u"192.168.3.1")) + + self.prov_iface = ipaddress.IPv6Interface(kwargs.pop('prov_ipv6', u"2001:dead:beef:1::1/%s" % self.ipv6_prefix)) + self.prov_ipv6, self.prov_nw_ipv6 = self.prov_iface.ip, self.prov_iface.network + + self.cm_gateway_v6_iface = ipaddress.IPv6Interface(kwargs.pop('cm_gateway_v6', u"2001:dead:beef:4::cafe/%s" % self.ipv6_prefix)) + self.cm_gateway_v6, self.cm_network_v6 = self.cm_gateway_v6_iface.ip, self.cm_gateway_v6_iface.network + self.cm_network_v6_start = ipaddress.IPv6Address(kwargs.pop('cm_network_v6_start', u"2001:dead:beef:4::10")) + self.cm_network_v6_end = ipaddress.IPv6Address(kwargs.pop('cm_network_v6_end', u"2001:dead:beef:4::100")) + self.open_gateway_iface = ipaddress.IPv6Interface(kwargs.pop('open_gateway_v6', u"2001:dead:beef:6::cafe/%s" % self.ipv6_prefix)) + self.open_gateway_v6, self.open_network_v6 = self.open_gateway_iface.ip, self.open_gateway_iface.network + self.open_network_v6_start = ipaddress.IPv6Address(kwargs.pop('open_network_v6_start', u"2001:dead:beef:6::10")) + self.open_network_v6_end = ipaddress.IPv6Address(kwargs.pop('open_network_v6_end', u"2001:dead:beef:6::100")) + self.prov_gateway_v6 = ipaddress.IPv6Address(kwargs.pop('prov_gateway_v6', u"2001:dead:beef:1::cafe")) + + # we're storing a list of all /56 subnets possible from erouter_net_iface. + # As per docsis, /56 must be the default pd length + self.erouter_net_iface = ipaddress.IPv6Interface(kwargs.pop('erouter_net', u"2001:dead:beef:e000::/51")) + self.erouter_net = list(self.erouter_net_iface.network.subnets(56-self.erouter_net_iface._prefixlen)) + + self.sip_fqdn = kwargs.pop('sip_fqdn',u"08:54:43:4F:4D:4C:41:42:53:03:43:4F:4D:00") + self.time_server = ipaddress.IPv4Address(kwargs.pop('time_server', self.prov_ip)) + self.timezone = self.get_timzone_offset(kwargs.pop('timezone', u"UTC")) + self.syslog_server = ipaddress.IPv4Address(kwargs.pop('syslog_server', self.prov_ip)) + if 'options' in kwargs: + options = [x.strip() for x in kwargs['options'].split(',')] + for opt in options: + # Not a well supported config, will go away at some point + if opt.startswith('wan-cmts-provisioner'): + self.wan_cmts_provisioner = True + self.shared_tftp_server = True + # This does run one.. but it's handled via the provisioning code path + self.standalone_provisioner = False + + self.gw = self.prov_ip + self.gwv6 = self.prov_ipv6 + self.nw = self.prov_network + return super(DebianISCProvisioner, self).__init__(*args, **kwargs) + + def setup_dhcp6_config(self, board_config): + tftp_server = self.tftp_device.tftp_server_ipv6_int() + + # can't provision without this, so let's ignore v6 if that's the case + if tftp_server is None: + self.sendline('rm /etc/dhcp/dhcpd6.conf-' + board_config['station'] + '.master') + self.expect(self.prompt) + return + + to_send = '''cat > /etc/dhcp/dhcpd6.conf-''' + board_config['station'] + '''.master << EOF +preferred-lifetime 7200; +option dhcp-renewal-time 3600; +option dhcp-rebinding-time 5400; + +allow leasequery; +prefix-length-mode prefer; + +option dhcp6.info-refresh-time 21600; +option dhcp6.ia_pd code 25 = { integer 32, integer 32, integer 32, integer 16, integer 16, integer 32, integer 32, integer 8, ip6-address}; +option dhcp6.gateway code 32003 = ip6-address; +option space docsis code width 2 length width 2; +option docsis.device-type code 2 = text; +option docsis.tftp-servers code 32 = array of ip6-address; +option docsis.configuration-file code 33 = text; +option docsis.syslog-servers code 34 = array of ip6-address; +option docsis.device-id code 36 = string; +option docsis.time-servers code 37 = array of ip6-address; +option docsis.time-offset code 38 = signed integer 32; +option docsis.cm-mac-address code 1026 = string; +option docsis.PKTCBL-CCCV4 code 2170 = { integer 16, integer 16, ip-address, integer 16, integer 16, ip-address }; +option vsio.docsis code 4491 = encapsulate docsis; + +# TODO: move to host section +#option dhcp6.aftr-name ""; +option dhcp6.name-servers ###PROV_IPV6###; +option dhcp6.domain-search "test.example.com","example.com"; + +class "CM" { + match if option docsis.device-type = "ECM"; +} +class "EROUTER" { + match if option docsis.device-type = "EROUTER"; +} + +subnet6 ###PROV_NW_IPV6### { + interface ###IFACE###; + ignore booting; +} + +shared-network boardfarm { + interface ###IFACE###; + subnet6 ###CM_NETWORK_V6### { + pool6 { + range6 ###CM_NETWORK_V6_START### ###CM_NETWORK_V6_END###; + allow members of "CM"; + option docsis.tftp-servers ###PROV_IPV6###; + option docsis.time-servers ###PROV_IPV6###; + option docsis.configuration-file "9_EU_CBN_IPv6_LG.cfg"; + option docsis.syslog-servers ###PROV_IPV6### ; + option docsis.time-offset 5000; + option docsis.PKTCBL-CCCV4 1 4 ###MTA_DHCP_SERVER1### 2 4 ###MTA_DHCP_SERVER2###; + option docsis.time-offset ###TIMEZONE###; + }''' + + if self.cm_network_v6 != self.open_network_v6: + to_send = to_send + ''' + } + subnet6 ###OPEN_NETWORK_V6### {''' + + to_send = to_send + ''' + pool6 { + range6 ###OPEN_NETWORK_V6_START### ###OPEN_NETWORK_V6_END###; + allow members of "EROUTER"; + option dhcp6.solmax-rt 240; + option dhcp6.inf-max-rt 360; + prefix6 ###EROUTER_NET_START### ###EROUTER_NET_END### /###EROUTER_PREFIX###; + } + pool6 { + range6 ###OPEN_NETWORK_HOST_V6_START### ###OPEN_NETWORK_HOST_V6_END###; + allow unknown-clients; + option dhcp6.solmax-rt 240; + option dhcp6.inf-max-rt 360; + } + } +} +EOF''' + + to_send = to_send.replace('###IFACE###', self.iface_dut) + to_send = to_send.replace('###PROV_IPV6###', str(self.prov_ipv6)) + to_send = to_send.replace('###PROV_NW_IPV6###', str(self.prov_nw_ipv6)) + to_send = to_send.replace('###CM_NETWORK_V6###', str(self.cm_network_v6)) + to_send = to_send.replace('###CM_NETWORK_V6_START###', str(self.cm_network_v6_start)) + to_send = to_send.replace('###CM_NETWORK_V6_END###', str(self.cm_network_v6_end)) + to_send = to_send.replace('###OPEN_NETWORK_V6###', str(self.open_network_v6)) + to_send = to_send.replace('###OPEN_NETWORK_V6_START###', str(self.open_network_v6_start)) + to_send = to_send.replace('###OPEN_NETWORK_V6_END###', str(self.open_network_v6_end)) + # Increment IP by 200 hosts + to_send = to_send.replace('###OPEN_NETWORK_HOST_V6_START###', str(self.open_network_v6_start+256*2)) + to_send = to_send.replace('###OPEN_NETWORK_HOST_V6_END###', str(self.open_network_v6_end+256*2)) + + # keep last ten /56 prefix in erouter pool. for unknown hosts + to_send = to_send.replace('###EROUTER_NET_START###', str(self.erouter_net[-10].network_address)) + to_send = to_send.replace('###EROUTER_NET_END###', str(self.erouter_net[-1].network_address)) + to_send = to_send.replace('###EROUTER_PREFIX###', str(self.erouter_net[-1]._prefixlen)) + to_send = to_send.replace('###MTA_DHCP_SERVER1###', str(self.prov_ip)) + to_send = to_send.replace('###MTA_DHCP_SERVER2###', str(self.prov_ip)) + to_send = to_send.replace('###TIMEZONE###', str(self.timezone)) + # TODO: add ranges for subnet's, syslog server per CM + + self.sendline(to_send) + self.expect(self.prompt) + + self.sendline('rm /etc/dhcp/dhcpd6.conf.''' + board_config['station']) + self.expect(self.prompt) + + cfg_file = "/etc/dhcp/dhcpd6.conf-" + board_config['station'] + + # zero out old config + self.sendline('cp /dev/null %s' % cfg_file) + self.expect(self.prompt) + + # insert tftp server, TODO: how to clean up? + if 'options' not in board_config['extra_provisioning_v6']['cm']: + board_config['extra_provisioning_v6']['cm']['options'] = {} + board_config['extra_provisioning_v6']['cm']['options']['docsis.tftp-servers'] = tftp_server + board_config['extra_provisioning_v6']['cm']['options']['docsis.PKTCBL-CCCV4'] = "1 4 %s 1 4 %s" % (self.prov_ip, self.prov_ip) + + # the IPv6 subnet for erouter_net in json, should be large enough + # len(erouter_net) >= no. of boards + 10 + board_config['extra_provisioning_v6']['erouter']['fixed-prefix6'] = str(self.erouter_net[int(board_config['station'].split("-")[-1])%len(self.erouter_net)]) + + # there is probably a better way to construct this file... + for dev, cfg_sec in board_config['extra_provisioning_v6'].iteritems(): + self.sendline("echo 'host %s-%s {' >> %s" % (dev, board_config['station'], cfg_file)) + for key, value in cfg_sec.iteritems(): + if key == "options": + for k2, v2 in value.iteritems(): + self.sendline("echo ' option %s %s;' >> %s" % (k2, v2, cfg_file)) + self.expect(self.prompt) + else: + self.sendline("echo ' %s %s;' >> %s" % (key, value, cfg_file)) + self.expect(self.prompt) + self.sendline("echo '}' >> %s" % cfg_file) + + self.sendline('mv ' + cfg_file + ' /etc/dhcp/dhcpd6.conf.' + board_config['station']) + self.expect(self.prompt) + # combine all configs into one + self.sendline("cat /etc/dhcp/dhcpd6.conf.* >> /etc/dhcp/dhcpd6.conf-" + board_config['station'] + ".master") + self.expect(self.prompt) + self.sendline("mv /etc/dhcp/dhcpd6.conf-" + board_config['station'] + ".master /etc/dhcp/dhcpd6.conf") + self.expect(self.prompt) + + + def setup_dhcp_config(self, board_config): + tftp_server = self.tftp_device.tftp_server_ip_int() + + # TODO: we should work ipv6 only at some point + #if tftp_server is None: + # self.sendline('rm /etc/dhcp/dhcpd.conf-' + board_config['station'] + '.master') + # self.expect(self.prompt) + # return + + to_send = '''cat > /etc/dhcp/dhcpd.conf-''' + board_config['station'] + '''.master << EOF +log-facility local7; +option log-servers ###LOG_SERVER###; +option time-servers ###TIME_SERVER###; +default-lease-time 604800; +max-lease-time 604800; +allow leasequery; + +class "CM" { + match if substring (option vendor-class-identifier, 0, 6) = "docsis"; +} +class "MTA" { + match if substring (option vendor-class-identifier, 0, 4) = "pktc"; +} +class "HOST" { + match if ((substring(option vendor-class-identifier,0,6) != "docsis") and (substring(option vendor-class-identifier,0,4) != "pktc")); +} + +option space docsis-mta; +option docsis-mta.dhcp-server-1 code 1 = ip-address; +option docsis-mta.dhcp-server-2 code 2 = ip-address; +option docsis-mta.provision-server code 3 = { integer 8, string }; +option docsis-mta.kerberos-realm code 6 = string; +option docsis-mta.as-req-as-rep-1 code 4 = { integer 32, integer 32, integer 32 }; +option docsis-mta.as-req-as-rep-2 code 5 = { integer 32, integer 32, integer 32 }; +option docsis-mta.krb-realm-name code 6 = string; +option docsis-mta.tgs-util code 7 = integer 8; +option docsis-mta.timer code 8 = integer 8; +option docsis-mta.ticket-ctrl-mask code 9 = integer 16; +option docsis-mta-pkt code 122 = encapsulate docsis-mta; + +subnet ###PROV_IP### netmask ###PROV_NETMASK### { + interface ###IFACE###; + ignore booting; +} + +shared-network boardfarm { + interface ###IFACE###; + subnet ###CM_IP### netmask ###CM_NETMASK### + { + option routers ###CM_GATEWAY###; + option broadcast-address ###CM_BROADCAST###; + option dhcp-parameter-request-list 43; + option domain-name "local"; + option time-offset ###TIMEZONE###; + option tftp-server-name "###DEFAULT_TFTP_SERVER###"; + option docsis-mta.dhcp-server-1 ###MTA_DHCP_SERVER1###; + option docsis-mta.dhcp-server-2 ###MTA_DHCP_SERVER2###; + filename "UNLIMITCASA.cfg"; + } + subnet ###MTA_IP### netmask ###MTA_NETMASK### + { + option routers ###MTA_GATEWAY###; + option broadcast-address ###MTA_BROADCAST###; + option time-offset ###TIMEZONE###; + option domain-name-servers ###PROV###; + option docsis-mta.kerberos-realm 05:42:41:53:49:43:01:31:00 ; + option docsis-mta.provision-server 0 ###MTA_SIP_FQDN### ; + } + subnet ###OPEN_IP### netmask ###OPEN_NETMASK### + { + option routers ###OPEN_GATEWAY###; + option broadcast-address ###OPEN_BROADCAST###; + option domain-name "local"; + option time-offset ###TIMEZONE###; + option domain-name-servers ###PROV###; + } + pool { + range ###MTA_START_RANGE### ###MTA_END_RANGE###; + allow members of "MTA"; + } + pool { + range ###CM_START_RANGE### ###CM_END_RANGE###; + allow members of "CM"; + } + pool { + range ###OPEN_START_RANGE### ###OPEN_END_RANGE###; + allow members of "HOST"; + } +} +EOF''' + + to_send = to_send.replace('###LOG_SERVER###', str(self.syslog_server)) + to_send = to_send.replace('###TIME_SERVER###', str(self.time_server)) + to_send = to_send.replace('###MTA_SIP_FQDN###', str(self.sip_fqdn)) + to_send = to_send.replace('###NEXT_SERVER###', str(self.prov_ip)) + to_send = to_send.replace('###IFACE###', str(self.iface_dut)) + to_send = to_send.replace('###MTA_DHCP_SERVER1###', str(self.prov_ip)) + to_send = to_send.replace('###MTA_DHCP_SERVER2###', str(self.prov_ip)) + to_send = to_send.replace('###PROV###', str(self.prov_ip)) + to_send = to_send.replace('###PROV_IP###', str(self.prov_network[0])) + to_send = to_send.replace('###PROV_NETMASK###', str(self.prov_network.netmask)) + to_send = to_send.replace('###CM_IP###', str(self.cm_network[0])) + to_send = to_send.replace('###CM_NETMASK###', str(self.cm_network.netmask)) + to_send = to_send.replace('###CM_START_RANGE###', str(self.cm_network[10])) + to_send = to_send.replace('###CM_END_RANGE###', str(self.cm_network[60])) + to_send = to_send.replace('###CM_GATEWAY###', str(self.cm_gateway)) + to_send = to_send.replace('###CM_BROADCAST###', str(self.cm_network[-1])) + to_send = to_send.replace('###DEFAULT_TFTP_SERVER###', str(self.prov_ip)) + to_send = to_send.replace('###MTA_IP###', str(self.mta_network[0])) + to_send = to_send.replace('###MTA_NETMASK###', str(self.mta_network.netmask)) + to_send = to_send.replace('###MTA_START_RANGE###', str(self.mta_network[10])) + to_send = to_send.replace('###MTA_END_RANGE###', str(self.mta_network[60])) + to_send = to_send.replace('###MTA_GATEWAY###', str(self.mta_gateway)) + to_send = to_send.replace('###MTA_BROADCAST###', str(self.mta_network[-1])) + to_send = to_send.replace('###OPEN_IP###', str(self.open_network[0])) + to_send = to_send.replace('###OPEN_NETMASK###', str(self.open_network.netmask)) + to_send = to_send.replace('###OPEN_START_RANGE###', str(self.open_network[10])) + to_send = to_send.replace('###OPEN_END_RANGE###', str(self.open_network[60])) + to_send = to_send.replace('###OPEN_GATEWAY###', str(self.open_gateway)) + to_send = to_send.replace('###OPEN_BROADCAST###', str(self.open_network[-1])) + to_send = to_send.replace('###TIMEZONE###', str(self.timezone)) + + self.sendline(to_send) + self.expect(self.prompt) + + self.sendline('rm /etc/dhcp/dhcpd.conf.''' + board_config['station']) + self.expect(self.prompt) + + cfg_file = "/etc/dhcp/dhcpd.conf-" + board_config['station'] + + # zero out old config + self.sendline('cp /dev/null %s' % cfg_file) + self.expect(self.prompt) + + # insert tftp server, TODO: how to clean up? + board_config['extra_provisioning']['cm']['next-server'] = tftp_server + board_config['extra_provisioning']['mta']['next-server'] = tftp_server + + # there is probably a better way to construct this file... + for dev, cfg_sec in board_config['extra_provisioning'].iteritems(): + self.sendline("echo 'host %s-%s {' >> %s" % (dev, board_config['station'], cfg_file)) + for key, value in cfg_sec.iteritems(): + if key == "options": + for k2, v2 in value.iteritems(): + self.sendline("echo ' option %s %s;' >> %s" % (k2, v2, cfg_file)) + self.expect(self.prompt) + else: + self.sendline("echo ' %s %s;' >> %s" % (key, value, cfg_file)) + self.expect(self.prompt) + self.sendline("echo '}' >> %s" % cfg_file) + + self.sendline('mv ' + cfg_file + ' /etc/dhcp/dhcpd.conf.' + board_config['station']) + self.expect(self.prompt) + # combine all configs into one + self.sendline("cat /etc/dhcp/dhcpd.conf.* >> /etc/dhcp/dhcpd.conf-" + board_config['station'] + ".master") + self.expect(self.prompt) + self.sendline("mv /etc/dhcp/dhcpd.conf-" + board_config['station'] + ".master /etc/dhcp/dhcpd.conf") + self.expect(self.prompt) + + def get_timzone_offset(self,timezone): + if timezone == "UTC": + return 0 + if timezone.startswith("GMT") or timezone.startswith("UTC"): + try: + offset = int(re.search(r"[\W\D\S]?\d{1,2}",timezone).group(0)) + except: + # In case a value was not provided, will throw an Attribute error + return 0 + # offset should be from GMT -11 to GMT 12 + if offset in range(-11,13): + return 3600 * offset + else: + print("Invalid Timezone. Using UTC standard") + return 0 + + def update_cmts_isc_dhcp_config(self, board_config): + if 'extra_provisioning' not in board_config: + # same defaults so we at least set tftp server to WAN + board_config['extra_provisioning'] = {} + if 'extra_provisioning_v6' not in board_config: + board_config['extra_provisioning_v6'] = {} + + # DHCPv4 defaults for when board does not supply defaults + if 'mta_mac' in board_config and not 'mta' in board_config['extra_provisioning']: + board_config['extra_provisioning']["mta"] = \ + { "hardware ethernet": board_config['mta_mac'], + "options": { "domain-name": "\"sipcenter.com\"", + "domain-name-servers": "%s" % self.prov_ip, + "routers": "%s" % self.mta_gateway, + "log-servers": "%s" % self.prov_ip, + "host-name": "\"" + board_config['station'] + "\"" + } + } + if 'cm_mac' in board_config and not 'cm' in board_config['extra_provisioning']: + board_config['extra_provisioning']["cm"] = \ + { "hardware ethernet": board_config['cm_mac'], + "options": { "domain-name-servers": "%s" % self.prov_ip, + "time-offset": "%s" % str(self.timezone) + } + } + + # since it skips the previous condition if extra prov is provided + board_config['extra_provisioning']["cm"]["options"]["time-offset"] = "%s" % str(self.timezone) + + if 'erouter_mac' in board_config and not 'erouter' in board_config['extra_provisioning']: + board_config['extra_provisioning']["erouter"] = \ + { "hardware ethernet": board_config['erouter_mac'], + "default-lease-time" : self.default_lease_time, + "max-lease-time": self.max_lease_time + } + + # DHCPv6 defaults for when board does not supply defaults + if 'cm_mac' in board_config and not 'cm' in board_config['extra_provisioning_v6']: + board_config['extra_provisioning_v6']["cm"] = \ + { "host-identifier option dhcp6.client-id": '00:03:00:01:' + board_config['cm_mac'], + "options": { "docsis.configuration-file": '"%s"' % board_config['cm_cfg'].encoded_fname } } + if 'erouter_mac' in board_config and not 'erouter' in board_config['extra_provisioning_v6']: + board_config['extra_provisioning_v6']["erouter"] = \ + { "hardware ethernet": board_config['erouter_mac'] } + + self.setup_dhcp_config(board_config) + self.setup_dhcp6_config(board_config) + + def copy_cmts_provisioning_files(self, board_config): + # Look in all overlays as well, and PATH as a workaround for standalone + paths = os.environ['PATH'].split(os.pathsep) + paths += [ os.path.realpath(x) for x in os.environ['BFT_OVERLAY'].split(' ') ] + cfg_list = [] + + if 'tftp_cfg_files' in board_config: + for cfg in board_config['tftp_cfg_files']: + from docsis_lib.docsis import cm_cfg + if isinstance(cfg, cm_cfg) or isinstance(cfg, mta_cfg): + cfg_list.append(cfg) + else: + for path in paths: + cfg_list += glob.glob(path + '/devices/cm-cfg/%s' % cfg) + else: + for path in paths: + cfg_list += glob.glob(path + '/devices/cm-cfg/UNLIMITCASA.cfg') + cfg_set = set(cfg_list) + + # Copy binary files to tftp server + for cfg in cfg_set: + from docsis_lib.docsis import docsis + d = docsis(cfg) + ret = d.encode() + self.tftp_device.copy_file_to_server(ret) + + def provision_board(self, board_config): + self.install_pkgs() + + # if we are not a full blown wan+provisoner then offer to route traffic + if not self.wan_cmts_provisioner: + self.setup_as_wan_gateway() + + ''' Setup DHCP and time server etc for CM provisioning''' + self.sendline('echo INTERFACESv4="%s" > /etc/default/isc-dhcp-server' % self.iface_dut) + self.expect(self.prompt) + self.sendline('echo INTERFACESv6="%s" >> /etc/default/isc-dhcp-server' % self.iface_dut) + self.expect(self.prompt) + # we are bypass this for now (see http://patchwork.ozlabs.org/patch/117949/) + self.sendline('sysctl -w net.ipv6.conf.%s.accept_dad=0' % self.iface_dut) + self.expect(self.prompt) + if not self.wan_no_eth0: + self.sendline('ifconfig %s down; ifconfig %s up' % (self.iface_dut, self.iface_dut)) + self.expect(self.prompt) + self.sendline('ifconfig %s %s' % (self.iface_dut, self.gw)) + self.expect(self.prompt) + + # TODO: we need to route via eth0 at some point + # TODO: don't hard code eth0... + self.disable_ipv6('eth0') + self.enable_ipv6(self.iface_dut) + if self.gwv6 is not None: + self.sendline('ip -6 addr add %s/%s dev %s' % (self.gwv6, self.ipv6_prefix, self.iface_dut)) + self.expect(self.prompt) + + if self.static_route is not None: + self.sendline('ip route add %s' % self.static_route) + self.expect(self.prompt) + + for nw in [self.cm_network, self.mta_network, self.open_network]: + self.sendline('ip route add %s via %s' % (nw, self.prov_gateway)) + self.expect(self.prompt) + + for nw in [self.cm_gateway_v6, self.open_gateway_v6]: + self.sendline('ip -6 route add %s/%s via %s dev %s' % (nw, self.ipv6_prefix, self.prov_gateway_v6, self.iface_dut)) + self.expect(self.prompt) + + for nw in self.erouter_net: + self.sendline('ip -6 route add %s via %s' % (nw, self.prov_gateway_v6)) + self.expect(self.prompt) + + self.update_cmts_isc_dhcp_config(board_config) + self.sendline('cat /etc/dhcp/dhcpd.conf') + self.expect(self.prompt) + self.sendline('cat /etc/dhcp/dhcpd6.conf') + self.expect_exact('cat /etc/dhcp/dhcpd6.conf') + self.expect(self.prompt) + + self._restart_dhcp_with_lock() + + # only start tftp server if we are a full blown wan+provisioner + if self.wan_cmts_provisioner: + self.start_tftp_server() + + # errr, this should not need to call into board object + try: + from devices import board + board.update_cfg_for_site() + except: + pass + + self.copy_cmts_provisioning_files(board_config) + + self.sendline("sed 's/disable\\t\\t= yes/disable\\t\\t= no/g' -i /etc/xinetd.d/time") + self.expect(self.prompt) + self.sendline("grep -q flags.*=.*IPv6 /etc/xinetd.d/time || sed '/wait.*=/a\\\\tflags\\t\\t= IPv6' -i /etc/xinetd.d/time") + self.expect(self.prompt) + self.sendline('/etc/init.d/xinetd restart') + self.expect('Starting internet superserver: xinetd.') + self.expect(self.prompt) + + def reprovision_board(self, board_config): + '''New DHCP, cfg files etc for board after it's been provisioned once''' + self.copy_cmts_provisioning_files(board_config) + self.update_cmts_isc_dhcp_config(board_config) + + self._restart_dhcp_with_lock() + + def _restart_dhcp_with_lock(self): + do_ipv6 = True + + try: + chk_ip = self.get_interface_ip6addr(self.iface_dut) + if ipaddress.IPv6Address(unicode(chk_ip)) not in self.prov_nw_ipv6: + do_ipv6 = False + if self.tftp_device.tftp_server_ipv6_int() is None: + do_ipv6 = False + except: + do_ipv6 = False + + self.sendline('(flock -x 9; /etc/init.d/isc-dhcp-server restart; flock -u 9) 9>/etc/init.d/isc-dhcp-server.lock') + matching = ['Starting ISC DHCP(v4)? server.*dhcpd.', 'Starting isc-dhcp-server.*'] + match_num = 1 + if do_ipv6: + matching.append('Starting ISC DHCPv6 server: dhcpd(6)?.\r\n') + match_num += 1 + else: + print("NOTE: not starting IPv6 because this provisioner is not setup properly") + + for not_used in range(match_num): + self.expect(matching) + match_num -= 1 + + assert match_num == 0, "Incorrect number of DHCP servers started, something went wrong!" + self.expect(self.prompt) + self.sendline('rm /etc/init.d/isc-dhcp-server.lock') + self.expect(self.prompt) + + def get_attr_from_dhcp(self, attr, exp_pattern, dev, station, match_group=4): + '''Try getting an attribute from the dhcpd.conf. file''' + val = None + try: + self.sendline('cat /etc/dhcp/dhcpd.conf.%s' % station) + idx = self.expect(['(%s-%s\s\{([^}]+)(%s\s(%s))\;)' % (dev, station, attr, exp_pattern) ] + ['No such file or directory'] + [pexpect.TIMEOUT], timeout=10) + if idx == 0: + # the value should be in group 4 + val = self.match.group(match_group) + except: + pass + return val + + def get_cfgs(self, board_config): + '''Tries to get the cfg out of the dhcpd.conf for the station in question''' + try: + mta_cfg = self.get_attr_from_dhcp('filename', '".*?"', 'mta', board_config['station']) + mta_cfg_srv = self.get_attr_from_dhcp('next-server', ValidIpv4AddressRegex, 'mta', board_config['station']) + + cm_cfg = self.get_attr_from_dhcp('filename', '".*?"', 'cm', board_config['station']) + cm_cfg_srv = self.get_attr_from_dhcp('next-server', ValidIpv4AddressRegex, 'cm', board_config['station']) + if mta_cfg is None or mta_cfg_srv is None or cm_cfg is None or cm_cfg_srv is None: + raise + return [[mta_cfg.replace('"', ''), mta_cfg_srv], [cm_cfg.replace('"',''), cm_cfg_srv]] + except: + pass + + return None + + def get_conf_file_from_tftp(self, _tmpdir, board_config): + '''Retrieve the files in the cfg_list from the tftp sever, puts them in localhost:/tmp/''' + + cfg_list = self.get_cfgs(board_config) + if cfg_list is None: + return False + + for elem in cfg_list: + conf_file = self.tftp_dir+'/'+elem[0] + server = elem[1] + + # this is where the current (to be downloaded from the tftp) + # config is going to be placed + dest_fname = _tmpdir+'/'+os.path.basename(conf_file)+"."+board_config['station']+".current" + try: + os.remove(dest_fname) + except: + pass + + try: + print('Downloading '+server+':'+conf_file+' to '+dest_fname) + from devices.common import scp_from + scp_from(conf_file, server, self.tftp_device.username, self.tftp_device.password, self.tftp_device.port, dest_fname) + + if not os.path.isfile(dest_fname): + # Something has gone wrong as the tftp client has not thrown an + # exception, but the file is not where it should be!! + print("Tftp completed but %s not found in destination dir: "% dest_fname) + return False + print("Downloaded: "+conf_file) + except: + print("Failed to download %s from %s"% (conf_file, self.ipaddr)) + return False + + return True + + def get_ipv4_time_server(self): + return self.time_server + diff --git a/boardfarm/devices/debian_wifi.py b/boardfarm/devices/debian_wifi.py new file mode 100644 index 00000000..e73866e0 --- /dev/null +++ b/boardfarm/devices/debian_wifi.py @@ -0,0 +1,112 @@ +import re +import debian +import pexpect +from countrycode import countrycode +from lib.wifi import wifi_client_stub + +class DebianWifi(debian.DebianBox, wifi_client_stub): + '''Extension of Debian class with wifi functions''' + + model = ('debianwifi') + def __init__(self, *args, **kwargs): + super(DebianWifi,self).__init__( *args, **kwargs) + self.iface_dut = self.iface_wifi = self.kwargs.get('dut_interface', 'wlan1') + + def disable_and_enable_wifi(self): + self.disable_wifi() + self.enable_wifi() + + def disable_wifi(self): + self.set_link_state(self.iface_wifi, "down") + + def enable_wifi(self): + self.set_link_state(self.iface_wifi, "up") + + def wifi_scan(self): + from tests.lib.installers import install_iw + install_iw(self) + + self.sudo_sendline('iw %s scan | grep SSID:' % self.iface_wifi) + self.expect(self.prompt) + return self.before + + def wifi_check_ssid(self, ssid_name): + from tests.lib.installers import install_iw + install_iw(self) + + self.sudo_sendline('iw %s scan | grep "SSID: %s"' % (self.iface_wifi, ssid_name)) + self.expect(self.prompt) + match = re.search("%s\"\s+.*(%s)"%(ssid_name, ssid_name), self.before) + if match: + return True + else: + return False + + def wifi_connect(self, ssid_name, password=None, security_mode=None): + if password == None: + self.sudo_sendline("iwconfig %s essid %s" % (self.iface_wifi,ssid_name)) + else: + '''Generate WPA supplicant file and execute it''' + self.sudo_sendline("rm "+ssid_name+".conf") + self.expect(self.prompt) + self.sudo_sendline("wpa_passphrase "+ssid_name+" "+password+" >> "+ssid_name+".conf") + self.expect(self.prompt) + self.sendline("cat "+ssid_name+".conf") + self.expect(self.prompt) + self.sudo_sendline("wpa_supplicant -B -Dnl80211 -i"+self.iface_wifi+ " -c"+ssid_name+".conf") + self.expect(self.prompt) + match = re.search('Successfully initialized wpa_supplicant', self.before) + if match: + return True + else: + return False + + def wifi_connectivity_verify(self): + '''Connection state verify''' + self.sendline("iw %s link" % self.iface_wifi) + self.expect(self.prompt) + match = re.search('Connected', self.before) + if match: + return True + else: + return False + + def disconnect_wpa(self): + self.sudo_sendline("killall wpa_supplicant") + self.expect(self.prompt) + + def wlan_ssid_disconnect(self): + output = self.sudo_sendline("iw dev %s disconnect" % self.iface_wifi) + self.expect(self.prompt) + + def wifi_disconnect(self): + self.disconnect_wpa() + self.wlan_ssid_disconnect() + + def wifi_change_region(self, country): + country = countrycode(country, origin='country_name', target='iso2c') + self.sudo_sendline("iw reg set %s"%(country)) + self.expect(self.prompt) + self.sendline("iw reg get") + self.expect(self.prompt) + match = re.search(country, self.before) + if match: + return match.group(0) + else: + return None + + def start_lan_client(self): + self.iface_dut = self.iface_wifi + super(DebianWifi, self).start_lan_client() + + def wifi_client_connect(self, ssid_name, password=None, security_mode=None): + '''Scan for SSID and verify connectivity''' + self.disable_and_enable_wifi() + self.expect(pexpect.TIMEOUT, timeout=20) + output = self.wifi_check_ssid(ssid_name) + assert output==True,'SSID value check in WLAN container' + + conn_wifi = self.wifi_connect(ssid_name, password) + self.expect(pexpect.TIMEOUT, timeout=20) + verify_connect = self.wifi_connectivity_verify() + assert verify_connect==True,'Connection establishment in WIFI' diff --git a/boardfarm/devices/dell_switch.py b/boardfarm/devices/dell_switch.py new file mode 100644 index 00000000..485d9ced --- /dev/null +++ b/boardfarm/devices/dell_switch.py @@ -0,0 +1,118 @@ +# Copyright (c) 2018 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +#!/usr/bin/env python + +import pexpect +import sys +import base + + +class DellSwitch(base.BaseDevice): + ''' + Connects to and configures a Dell Switch + ''' + + prompt = ['console>', 'console#', 'console\(config.*\)#'] + + def __init__(self, + conn_cmd, + password=''): + pexpect.spawn.__init__(self, '/bin/bash', args=['-c', conn_cmd]) + self.logfile_read = sys.stdout + self.password = password + + def connect(self): + for i in range(10): + self.sendline('exit') + if 0 == self.expect([pexpect.TIMEOUT] + self.prompt, timeout=5): + self.sendline('enable') + if 0 == self.expect(['Password:'] + self.prompt): + self.sendline(self.password) + self.expect(self.prompt) + self.sendline('config') + self.expect(self.prompt) + return + + raise Exception("Unable to get prompt on Dell switch") + + def create_vlan(self, vlan): + self.sendline('vlan database') + self.expect(self.prompt) + self.sendline('vlan %s' % vlan) + self.expect(self.prompt) + self.sendline('exit') + self.expect(self.prompt) + + def configure_basic_settings(self): + self.create_vlan(4093) + self.sendline('ip address dhcp') + self.expect(self.prompt) + self.sendline('ip address vlan 4093') + self.expect(self.prompt) + + def configure_eth_private_port(self, port, override_vlan=None): + if override_vlan is None: + vlan = 100 + port + else: + vlan = override_vlan + + self.create_vlan(vlan) + self.sendline('interface ethernet 1/g%s' % port) + self.expect(self.prompt) + self.sendline('spanning-tree disable') + self.expect(self.prompt) + self.sendline('switchport mode general') + self.expect(self.prompt) + # NOTE: we can't change the PVID otherwise it breaks other containers + self.sendline('switchport general pvid %s' % (100 + port)) + self.expect(self.prompt) + self.sendline('switchport general ingress-filtering disable') + self.expect(self.prompt) + self.sendline('switchport forbidden vlan add 1,4093') + self.expect(self.prompt) + self.sendline('switchport general allowed vlan add %s' % vlan) + self.expect(self.prompt) + self.sendline('exit') + self.expect(self.prompt) + + def configure_eth_trunk_port(self, port): + self.sendline('interface ethernet 1/g%s' % port) + self.expect(self.prompt) + self.sendline('switchport mode trunk') + self.expect(self.prompt) + self.sendline('switchport forbidden vlan add 1') + self.expect(self.prompt) + # TODO: this secondary range should be configurable + # maybe setting trunk ports on the device class first? + self.sendline('switchport trunk allowed vlan add 101-148,200-210,4093') + self.expect(self.prompt) + self.sendline('exit') + self.expect(self.prompt) + + def save_running_to_startup_config(self): + self.sendline('exit') + self.expect(self.prompt) + self.sendline('copy running-config startup-config') + self.expect(self.prompt) + self.sendline('config') + self.expect(self.prompt) + +if __name__ == '__main__': + dell_switch = DellSwitch(sys.argv[1]) + dell_switch.connect() + + dell_switch.configure_basic_settings() + for i in range(1, 42+1): + dell_switch.configure_eth_private_port(i) + for i in range(43, 48+1): + dell_switch.configure_eth_trunk_port(i) + + print() + print("Press Control-] to exit interact mode") + print("=====================================") + dell_switch.interact() + print() diff --git a/boardfarm/devices/docker_factory.py b/boardfarm/devices/docker_factory.py new file mode 100644 index 00000000..a2c59780 --- /dev/null +++ b/boardfarm/devices/docker_factory.py @@ -0,0 +1,124 @@ +import pexpect +import sys +import os + +import linux + +class DockerFactory(linux.LinuxDevice): + ''' + A docker host that can spawn various types of images + ''' + + model = ('docker-factory') + prompt = ['docker_session>'] + created_docker_network = False + created_docker = False + extra_devices = [] + target_cname = [] + + def __str__(self): + return self.name + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + self.ipaddr = kwargs.pop('ipaddr', None) + self.iface = kwargs.pop('iface', None) + self.docker_network = kwargs.pop('docker_network', None) + self.env = kwargs.pop('env', None) + self.name = kwargs.pop('name') + self.cname = self.name + '-${uniq_id}' + + if self.ipaddr is not None: + # TOOO: we rely on correct username and key and standard port + pexpect.spawn.__init(self, command="ssh", + args=['%s' % (self.ipaddr), + '-o', 'StrictHostKeyChecking=no', + '-o', 'UserKnownHostsFile=/dev/null', + '-o', 'ServerAliveInterval=60', + '-o', 'ServerAliveCountMax=5']) + else: + pexpect.spawn.__init__(self, command='bash', env=self.env) + self.ipaddr = 'localhost' + + if 'BFT_DEBUG' in os.environ: + self.logfile_read = sys.stdout + + self.expect(pexpect.TIMEOUT, timeout=1) + self.sendline('export PS1="docker_session>"') + self.expect(self.prompt) + self.sendline('echo FOO') + self.expect_exact('echo FOO') + self.expect(self.prompt) + + self.set_cli_size(200) + + # if these interfaces are getting created let's give them time to show up + for i in range(10): + self.sendline('ifconfig %s' % self.iface) + self.expect(self.prompt) + if 'error fetching interface information: Device not found' not in self.before: + break + + # iface set, we need to create network + if self.iface is not None: + self.sendline('docker network create -d macvlan -o parent=%s -o macvlan_mode=bridge %s' % (self.iface, self.cname)) + self.expect(self.prompt) + self.sendline('docker network ls') + self.expect(self.prompt) + self.created_docker_network = True + + + from devices import get_device + for target in kwargs.pop('targets'): + target_img = target['img'] + target_type = target['type'] + target_cname = target['name'] + '-${uniq_id}' + + # TODO: check for docker image and build if needed/can + # TODO: move default command into Dockerfile + # TODO: list of ports to forward, http proxy port for example and ssh + self.sendline('docker run --rm --privileged --name=%s -d -p 22 %s /usr/sbin/sshd -D' % (target_cname, target_img)) + self.expect(self.prompt) + self.expect(pexpect.TIMEOUT, timeout=1) + self.sendline('docker network connect %s %s' % (self.cname, target_cname)) + self.expect(self.prompt) + assert 'Error response from daemon' not in self.before, "Failed to connect docker network" + if self.created_docker_network == True: + self.sendline('docker exec %s ip address flush dev eth1' % target_cname) + self.expect(self.prompt) + self.sendline("docker port %s | grep '22/tcp' | sed 's/.*://g'" % target_cname) + self.expect_exact("docker port %s | grep '22/tcp' | sed 's/.*://g'" % target_cname) + self.expect(self.prompt) + target['port'] = self.before.strip() + int(self.before.strip()) + self.created_docker = True + + target['ipaddr'] = self.ipaddr + + new_device = get_device(target_type, **target) + self.extra_devices.append(new_device) + + self.target_cname.append(target_cname) + + + def close(self, *args, **kwargs): + self.clean_docker() + self.clean_docker_network() + return super(DockerFactory, self).close(*args, **kwargs) + + def clean_docker_network(self): + if self.created_docker_network == True: + self.sendline('docker network rm %s' % self.cname) + self.expect(self.prompt) + self.sendline('docker network ls') + self.expect(self.prompt) + + def clean_docker(self): + if self.created_docker == True: + for c in self.target_cname: + self.sendline('docker stop %s' % c) + self.expect(self.prompt) + self.sendline('docker rm %s'% c) + self.expect(self.prompt) diff --git a/devices/error_detect.py b/boardfarm/devices/error_detect.py similarity index 92% rename from devices/error_detect.py rename to boardfarm/devices/error_detect.py index 40fa4d5b..35604191 100644 --- a/devices/error_detect.py +++ b/boardfarm/devices/error_detect.py @@ -5,7 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import pexpect import common import re import inspect @@ -67,6 +66,12 @@ def detect_fatal_error(console): #detect_kernel_panic(console, s) def caller_file_line(i): + #line = 0 + #print "##################### %s" % i + #for s in inspect.stack(): + # print "%s: %s" % (line, s) + # line = line + 1 + #print "##################### %s" % i caller = inspect.stack()[i] # caller of spawn or pexpect frame = caller[0] info = inspect.getframeinfo(frame) diff --git a/boardfarm/devices/kermit_connection.py b/boardfarm/devices/kermit_connection.py new file mode 100644 index 00000000..7c115e7f --- /dev/null +++ b/boardfarm/devices/kermit_connection.py @@ -0,0 +1,45 @@ +import pexpect + + +class KermitConnection(): + """ + Wrapper for the kermit command + kermit can be used as an alternative to telnet. On some + platform telnet can hog the cpu to 100% for no apparent + reason. kermit seems to be more stable, but to work properly + it needs a little setting up. + """ + prompt = "C-Kermit>" + + def __init__(self, device=None, conn_cmd=None, **kwargs): + self.device = device + self.conn_cmd = conn_cmd + + def connect(self): + try: + pexpect.spawn.__init__(self.device, + command='/bin/bash', + args=['-c', "kermit"]) + self.device.sendline() + self.device.expect(self.prompt) + # don't be strict and wait too long for the negotiations + self.device.sendline("SET TELNET WAIT OFF") + self.device.expect(self.prompt) + self.device.sendline("set host %s"% ' '.join(self.conn_cmd.split(' ')[1:])) + self.device.expect(self.prompt) + self.device.sendline('connect') + self.device.expect(['----------------------------------------------------'], timeout=15) + # check if it is a Microsoft Telnet Service + if 0 == self.device.expect(['Welcome to Microsoft Telnet Service', pexpect.TIMEOUT], timeout=10): + # MS telnet server does weird things... this sendline should get the 'login:' prompt + self.device.sendline() + except pexpect.EOF: + raise Exception("Board is in use (connection refused).") + + def close(self): + self.device.sendcontrol('\\') + self.device.sendline('c') + self.device.expect(self.prompt) + self.device.sendline('q') + self.device.expect('OK to exit\?') + self.device.sendline('y') diff --git a/boardfarm/devices/linux.py b/boardfarm/devices/linux.py new file mode 100644 index 00000000..a5669284 --- /dev/null +++ b/boardfarm/devices/linux.py @@ -0,0 +1,297 @@ +import base, binascii +import os, ipaddress, re +from lib.regexlib import ValidIpv4AddressRegex, AllValidIpv6AddressesRegex, LinuxMacFormat +import pexpect + +from common import print_bold + +BFT_DEBUG = "BFT_DEBUG" in os.environ + +class LinuxDevice(base.BaseDevice): + '''Linux implementations ''' + tftp_dir = '/tftpboot' + + def get_interface_ipaddr(self, interface): + '''Get ipv4 address of interface''' + self.sendline("\nifconfig %s" % interface) + regex = ['addr:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(Bcast|P-t-P):', + 'inet (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(broadcast|P-t-P)', + 'inet ('+ValidIpv4AddressRegex+').*netmask ('+ValidIpv4AddressRegex+').*destination '+ValidIpv4AddressRegex] + self.expect(regex, timeout=5) + ipaddr = self.match.group(1) + ipv4address = str(ipaddress.IPv4Address(unicode(ipaddr))) + self.expect(self.prompt) + return ipv4address + + def get_interface_ip6addr(self, interface): + '''Get ipv6 address of interface''' + self.sendline("\nifconfig %s" % interface) + self.expect_exact("ifconfig %s" % interface) + self.expect(self.prompt) + for match in re.findall(AllValidIpv6AddressesRegex, self.before): + ip6address = ipaddress.IPv6Address(unicode(match)) + if not ip6address.is_link_local: + return str(ip6address) + raise Exception("Did not find non-link-local ipv6 address") + + def get_interface_macaddr(self, interface): + '''Get the interface macaddress ''' + self.sendline('cat /sys/class/net/%s/address' % interface) + self.expect_exact('cat /sys/class/net/%s/address' % interface) + self.expect(LinuxMacFormat) + macaddr = self.match.group() + self.expect(self.prompt) + return macaddr + + def get_seconds_uptime(self): + '''Return seconds since last reboot. Stored in /proc/uptime''' + self.sendcontrol('c') + self.expect(self.prompt) + self.sendline('\ncat /proc/uptime') + self.expect('((\d+)\.(\d{2}))(\s)(\d+)\.(\d{2})') + seconds_up = float(self.match.group(1)) + self.expect(self.prompt) + return seconds_up + + def enable_ipv6(self, interface): + '''Enable ipv6 of the interface ''' + self.sendline("sysctl net.ipv6.conf."+interface+".accept_ra=2") + self.expect(self.prompt, timeout=30) + self.sendline("sysctl net.ipv6.conf."+interface+".disable_ipv6=0") + self.expect(self.prompt, timeout=30) + + def disable_ipv6(self, interface): + '''Disable ipv6 of the interface ''' + self.sendline("sysctl net.ipv6.conf."+interface+".disable_ipv6=1") + self.expect(self.prompt, timeout=30) + + def set_printk(self, CUR=1, DEF=1, MIN=1, BTDEF=7): + '''Modifies the log level in kernel''' + try: + self.sendline('echo "%d %d %d %d" > /proc/sys/kernel/printk' % (CUR, DEF, MIN, BTDEF)) + self.expect(self.prompt, timeout=10) + if not BFT_DEBUG: + print_bold("printk set to %d %d %d %d" % (CUR, DEF, MIN, BTDEF)) + except: + pass + + def prefer_ipv4(self, pref=True): + """Edits the /etc/gai.conf file + + This is to give/remove ipv4 preference (by default ipv6 is preferred) + See /etc/gai.conf inline comments for more details + """ + if pref is True: + self.sendline("sed -i 's/^#precedence ::ffff:0:0\/96 100/precedence ::ffff:0:0\/96 100/' /etc/gai.conf") + else: + self.sendline("sed -i 's/^precedence ::ffff:0:0\/96 100/#precedence ::ffff:0:0\/96 100/' /etc/gai.conf") + self.expect(self.prompt) + + def ping(self, ping_ip, source_ip=None, ping_count=4, ping_interface=None): + '''Check ping from any device''' + if source_ip == None and ping_interface == None: + self.sendline('ping -c %s %s' % (ping_count, ping_ip)) + elif ping_interface != None: + self.sendline('ping -I %s -c %s %s' % (ping_interface, ping_count, ping_ip)) + else: + self.sendline("ping -S %s -c %s %s" % (source_ip, ping_count, ping_ip)) + self.expect(self.prompt, timeout=50) + match = re.search("%s packets transmitted, %s received, 0%% packet loss" % + (ping_count, ping_count), self.before) + if match: + return 'True' + else: + return 'False' + + def is_link_up(self, interface): + '''Checking the interface status''' + self.sendline("ip link show %s" % interface) + self.expect(self.prompt) + link_state = self.before + match = re.search('BROADCAST,MULTICAST,UP',link_state) + if match: + return match.group(0) + else: + return None + + def set_link_state(self, interface, state): + '''Setting the interface status''' + self.sudo_sendline("ip link set %s %s" % (interface,state)) + self.expect(self.prompt) + + def add_new_user(self, id, pwd): + '''Create new login ID. But check if already exists''' + self.sendline('\nadduser %s' % id) + try: + self.expect_exact("Enter new UNIX password", timeout=5) + self.sendline('%s' % pwd) + self.expect_exact("Retype new UNIX password") + self.sendline('%s' % pwd) + self.expect_exact("Full Name []") + self.sendline('%s' % id) + self.expect_exact("Room Number []") + self.sendline('1') + self.expect_exact("Work Phone []") + self.sendline('4081234567') + self.expect_exact("Home Phone []") + self.sendline('4081234567') + self.expect_exact("Other []") + self.sendline('4081234567') + self.expect_exact("Is the information correct?") + self.sendline('y') + self.expect(self.prompt) + self.sendline('usermod -aG sudo %s' % id) + self.expect(self.prompt) + # Remove "$" in the login prompt and replace it with "#" + self.sendline('sed -i \'s/\\w\\\$ /\\\w# /g\' //home/%s/.bashrc' % id) + self.expect(self.prompt, timeout=30) + except: + self.expect(self.prompt, timeout=30) + + def copy_file_to_server(self, src, dst=None): + '''Copy the file from source to destination ''' + def gzip_str(string_): + import gzip + import io + out = io.BytesIO() + with gzip.GzipFile(fileobj=out, mode='w') as fo: + fo.write(string_) + return out.getvalue() + + with open(src, mode='rb') as file: + bin_file = binascii.hexlify(gzip_str(file.read())) + if dst is None: + dst = self.tftp_dir + '/' + os.path.basename(src) + print("Copying %s to %s" % (src, dst)) + saved_logfile_read = self.logfile_read + self.logfile_read = None + self.sendline('''cat << EOFEOFEOFEOF | xxd -r -p | gunzip > %s +%s +EOFEOFEOFEOF''' % (dst, bin_file)) + self.expect(self.prompt) + self.sendline('ls %s' % dst) + self.expect_exact('ls %s' % dst) + i = self.expect(['ls: cannot access %s: No such file or directory' % dst] + self.prompt) + if i == 0: + raise Exception("Failed to copy file") + self.logfile_read = saved_logfile_read + + def ip_neigh_flush(self): + '''Removes entries in the neighbour table ''' + self.sendline('\nip -s neigh flush all') + self.expect('flush all') + self.expect(self.prompt) + + def sudo_sendline(self, cmd): + '''Add sudo in the sendline if username is root''' + if self.username != "root": + self.sendline("sudo true") + if 0 == self.expect(["password for .*:"] + self.prompt): + will_prompt_for_password = True + else: + will_prompt_for_password = False + + cmd = "sudo " + cmd + if will_prompt_for_password: + self.sendline(self.password) + self.expect(self.prompt) + super(LinuxDevice, self).sendline(cmd) + + def set_cli_size(self, columns): + '''Set the terminal colums value''' + self.sendline('stty columns %s'%str(columns)) + self.expect(self.prompt) + + def wait_for_linux(self): + '''Verify Linux starts up.''' + i = self.expect(['Reset Button Push down', 'Linux version', 'Booting Linux', 'Starting kernel ...', 'Kernel command line specified:'], timeout=45) + if i == 0: + self.expect('httpd') + self.sendcontrol('c') + self.expect(self.uprompt) + self.sendline('boot') + i = self.expect(['U-Boot', 'login:', 'Please press Enter to activate this console'] + self.prompt, timeout=150) + if i == 0: + raise Exception('U-Boot came back when booting kernel') + elif i == 1: + self.sendline('root') + if 0 == self.expect(['assword:'] + self.prompt): + self.sendline('password') + self.expect(self.prompt) + + def get_dns_server_upstream(self): + '''Get the IP of name server''' + self.sendline('cat /etc/resolv.conf') + self.expect('nameserver (.*)\r\n', timeout=5) + ret = self.match.group(1) + self.expect(self.prompt) + return ret + + def get_nf_conntrack_conn_count(self): + '''Get the total number of connections in the network''' + pp = self.get_pp_dev() + + for not_used in range(5): + try: + pp.sendline('cat /proc/sys/net/netfilter/nf_conntrack_count') + pp.expect_exact('cat /proc/sys/net/netfilter/nf_conntrack_count', timeout=2) + pp.expect(pp.prompt, timeout=15) + ret = int(pp.before.strip()) + + self.touch() + return ret + except: + continue + else: + raise Exception("Unable to extract nf_conntrack_count!") + + def get_proc_vmstat(self, pp=None): + '''Get the virtual machine status ''' + if pp is None: + pp = self.get_pp_dev() + + for not_used in range(5): + try: + pp.sendline('cat /proc/vmstat') + pp.expect_exact('cat /proc/vmstat') + pp.expect(pp.prompt) + results = re.findall('(\w+) (\d+)', pp.before) + ret = {} + for key, value in results: + ret[key] = int(value) + + return ret + except Exception as e: + print(e) + continue + else: + raise Exception("Unable to parse /proc/vmstat!") + + def wait_for_network(self): + '''Wait until network interfaces have IP Addresses.''' + for interface in [self.wan_iface, self.lan_iface]: + for i in range(5): + try: + if interface is not None: + ipaddr = self.get_interface_ipaddr(interface).strip() + if not ipaddr: + continue + self.sendline("route -n") + self.expect(interface, timeout=2) + self.expect(self.prompt) + except pexpect.TIMEOUT: + print("waiting for wan/lan ipaddr") + else: + break + + def get_memfree(self): + '''Return the kB of free memory.''' + # free pagecache, dentries and inodes for higher accuracy + self.sendline('\nsync; echo 3 > /proc/sys/vm/drop_caches') + self.expect('drop_caches') + self.expect(self.prompt) + self.sendline('cat /proc/meminfo | head -2') + self.expect('MemFree:\s+(\d+) kB') + memFree = self.match.group(1) + self.expect(self.prompt) + return int(memFree) diff --git a/boardfarm/devices/local_cmd.py b/boardfarm/devices/local_cmd.py new file mode 100644 index 00000000..3da35bc3 --- /dev/null +++ b/boardfarm/devices/local_cmd.py @@ -0,0 +1,21 @@ +import pexpect + +class LocalCmd(): + ''' + Set connection_type to local_cmd, ignores all output for now + ''' + def __init__(self, device=None, conn_cmd=None, **kwargs): + self.device = device + self.conn_cmd = conn_cmd + + def connect(self): + try: + pexpect.spawn.__init__(self.device, + command='/bin/bash', + args=['-c', self.conn_cmd]) + self.device.expect(pexpect.TIMEOUT, timeout=5) + except pexpect.EOF: + raise Exception("Board is in use (connection refused).") + + def close(self): + self.device.sendcontrol('c') diff --git a/devices/local_serial_connection.py b/boardfarm/devices/local_serial_connection.py similarity index 74% rename from devices/local_serial_connection.py rename to boardfarm/devices/local_serial_connection.py index 1d3a4639..e34da656 100644 --- a/devices/local_serial_connection.py +++ b/boardfarm/devices/local_serial_connection.py @@ -1,4 +1,5 @@ import pexpect +from lib.regexlib import telnet_ipv4_conn class LocalSerialConnection(): ''' @@ -15,9 +16,9 @@ def connect(self): command='/bin/bash', args=['-c', self.conn_cmd]) try: - result = self.device.expect([".*Connected.*", "----------------------------------------------------"]) - except pexpect.EOF as e: + result = self.device.expect([telnet_ipv4_conn, "----------------------------------------------------"]) + except pexpect.EOF: raise Exception("Board is in use (connection refused).") - def close(): + def close(self): self.device.sendline("~.") diff --git a/devices/marvell.py b/boardfarm/devices/marvell.py similarity index 95% rename from devices/marvell.py rename to boardfarm/devices/marvell.py index fb199789..47554521 100644 --- a/devices/marvell.py +++ b/boardfarm/devices/marvell.py @@ -14,6 +14,7 @@ class WRT3200ACM(openwrt_router.OpenWrtRouter): ''' Marvell board ''' + model = ("wrt3200acm") prompt = ['root\\@.*:.*#', ] uprompt = ['Venom>>'] @@ -55,5 +56,5 @@ def flash_linux(self, KERNEL): self.sendline('run update_both_images') self.expect(self.uprompt, timeout=90) - def boot_linux(self, rootfs=None): + def boot_linux(self, rootfs=None, bootargs=""): self.sendline('boot') diff --git a/devices/mysql.py b/boardfarm/devices/mysql.py similarity index 100% rename from devices/mysql.py rename to boardfarm/devices/mysql.py diff --git a/devices/netgear.py b/boardfarm/devices/netgear.py similarity index 99% rename from devices/netgear.py rename to boardfarm/devices/netgear.py index 1e4a2b5a..47e1b704 100644 --- a/devices/netgear.py +++ b/boardfarm/devices/netgear.py @@ -8,12 +8,12 @@ import pexpect import sys -import base +import linux # Netgear Switch Prompt prompt = "\(M4100-50G\) " -class NetgearM4100(base.BaseDevice): +class NetgearM4100(linux.LinuxDevice): ''' A netgear switch allows for changing connections by modifying VLANs on ports. diff --git a/boardfarm/devices/oe.py b/boardfarm/devices/oe.py new file mode 100644 index 00000000..602c5a70 --- /dev/null +++ b/boardfarm/devices/oe.py @@ -0,0 +1,8 @@ +import linux, os, re + +class OpenEmbedded(linux.LinuxDevice): + '''OE core implementation''' + + def install_package(self, pkg): + '''Install packages ''' + raise Exception("Not implemented!") diff --git a/devices/openwrt_router.py b/boardfarm/devices/openwrt_router.py similarity index 61% rename from devices/openwrt_router.py rename to boardfarm/devices/openwrt_router.py index 5ed19569..4d2c735d 100644 --- a/devices/openwrt_router.py +++ b/boardfarm/devices/openwrt_router.py @@ -8,27 +8,22 @@ import atexit import os import os.path -import random import signal import socket import sys import urllib2 import pexpect -import base +import linux from datetime import datetime -import time +import ipaddress +import re -import error_detect import power import common import connection_decider +from common import print_bold - -# To Do: maybe make this config variable -BFT_DEBUG = "BFT_DEBUG" in os.environ - - -class OpenWrtRouter(base.BaseDevice): +class OpenWrtRouter(linux.LinuxDevice): ''' Args: model: Examples include "ap148" and "ap135". @@ -36,6 +31,8 @@ class OpenWrtRouter(base.BaseDevice): power_ip: IP Address of power unit to which this device is connected power_outlet: Outlet # this device is connected ''' + conn_list = None + consoles = [] prompt = ['root\\@.*:.*#', '/ # ', '@R7500:/# '] uprompt = ['ath>', '\(IPQ\) #', 'ar7240>', '\(IPQ40xx\)'] @@ -45,10 +42,17 @@ class OpenWrtRouter(base.BaseDevice): lan_gmac_iface = "eth1" lan_iface = "br-lan" wan_iface = "eth0" + tftp_server_int = None + flash_meta_booted = False + has_cmts = False + cdrouter_config = None - delaybetweenchar = None uboot_net_delay = 30 + routing = True + lan_network = ipaddress.IPv4Network(u"192.168.1.0/24") + lan_gateway = ipaddress.IPv4Address(u"192.168.1.1") + tmpdir = "/tmp" def __init__(self, model, conn_cmd, @@ -64,8 +68,16 @@ def __init__(self, connection_type=None, power_username=None, power_password=None, + config=None, **kwargs): + self.config = config + self.consoles = [self] + self.start = kwargs['start'] + + if type(conn_cmd) is list: + self.conn_list = conn_cmd + conn_cmd = self.conn_list[0] if connection_type is None: print("\nWARNING: Unknown connection type using ser2net\n") @@ -79,76 +91,28 @@ def __init__(self, self.model = model self.web_proxy = web_proxy if tftp_server: - self.tftp_server = socket.gethostbyname(tftp_server) - if tftp_username: - self.tftp_username = tftp_username - if tftp_password: - self.tftp_password = tftp_password - if tftp_port: - self.tftp_port = tftp_port + try: + self.tftp_server = socket.gethostbyname(tftp_server) + if tftp_username: + self.tftp_username = tftp_username + if tftp_password: + self.tftp_password = tftp_password + if tftp_port: + self.tftp_port = tftp_port + except: + pass else: self.tftp_server = None atexit.register(self.kill_console_at_exit) - def reset(self, break_into_uboot=False): - '''Power-cycle this device.''' - if not break_into_uboot: - self.power.reset() - return - for attempt in range(3): - try: - self.power.reset() - self.expect('U-Boot', timeout=30) - self.expect('Hit any key ') - self.sendline('\n\n\n\n\n\n\n') # try really hard - self.expect(self.uprompt, timeout=4) - # Confirm we are in uboot by typing any command. - # If we weren't in uboot, we wouldn't see the command - # that we type. - self.sendline('echo FOO') - self.expect('echo FOO', timeout=4) - self.expect(self.uprompt, timeout=4) - return - except Exception as e: - print(e) - print("\nWe appeared to have failed to break into U-Boot...") - - def get_ip_addr(self, interface): - '''Return IP Address for given interface.''' - self.sendline("\nifconfig %s" % interface) - self.expect('addr:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(Bcast|P-t-P):', timeout=5) - ipaddr = self.match.group(1) - self.expect(self.prompt) - return ipaddr - - def get_seconds_uptime(self): - '''Return seconds since last reboot. Stored in /proc/uptime''' - self.sendline('\ncat /proc/uptime') - self.expect('(\d+).(\d+) (\d+).(\d+)\r\n') - seconds_up = int(self.match.group(1)) - self.expect(self.prompt) - return seconds_up - - def get_memfree(self): - '''Return the kB of free memory.''' - # free pagecache, dentries and inodes for higher accuracy - self.sendline('\nsync; echo 3 > /proc/sys/vm/drop_caches') - self.expect('drop_caches') - self.expect(self.prompt) - self.sendline('cat /proc/meminfo | head -2') - self.expect('MemFree:\s+(\d+) kB') - memFree = self.match.group(1) - self.expect(self.prompt) - return int(memFree) - - def get_file(self, fname): + def get_file(self, fname, lan_ip=lan_gateway): ''' OpenWrt routers have a webserver, so we use that to download the file via a webproxy (e.g. a device on the board's LAN). ''' if not self.web_proxy: raise Exception('No web proxy defined to access board.') - url = 'http://192.168.1.1/TEMP' + url = 'http://%s/TEMP' % lan_ip self.sendline("\nchmod a+r %s" % fname) self.expect('chmod ') self.expect(self.prompt) @@ -175,12 +139,19 @@ def tftp_get_file(self, host, filename, timeout=30): self.expect(self.prompt) return new_fname - def tftp_get_file_uboot(self, loadaddr, filename, timeout=30): + def tftp_get_file_uboot(self, loadaddr, filename, timeout=60): '''Within u-boot, download file from tftp server.''' for attempt in range(3): try: - self.sendline("tftpboot %s %s" % (loadaddr, filename)) - self.expect_exact("tftpboot %s %s" % (loadaddr, filename)) + self.sendline('help') + self.expect_exact('help') + self.expect(self.uprompt) + if 'tftpboot' in self.before: + cmd = 'tftpboot' + else: + cmd = 'tftp' + self.sendline("%s %s %s" % (cmd, loadaddr, filename)) + self.expect_exact("%s %s %s" % (cmd, loadaddr, filename)) i = self.expect(['Bytes transferred = (\d+) (.* hex)'] + self.uprompt, timeout=timeout) if i != 0: continue @@ -193,13 +164,22 @@ def tftp_get_file_uboot(self, loadaddr, filename, timeout=30): self.expect(self.uprompt) raise Exception("TFTP failed, try rebooting the board.") - def prepare_file(self, fname): + def prepare_file(self, fname, tserver=None, tusername=None, tpassword=None, tport=None): '''Copy file to tftp server, so that it it available to tftp to the board itself.''' + if tserver is None: + tserver = self.tftp_server + if tusername is None: + tusername = self.tftp_username + if tpassword is None: + tpassword = self.tftp_password + if tport is None: + tport = self.tftp_port + if fname.startswith("http://") or fname.startswith("https://"): - return common.download_from_web(fname, self.tftp_server, self.tftp_username, self.tftp_password, self.tftp_port) + return common.download_from_web(fname, tserver, tusername, tpassword, tport) else: - return common.scp_to_tftp_server(os.path.abspath(fname), self.tftp_server, self.tftp_username, self.tftp_password, self.tftp_port) + return common.scp_to_tftp_server(os.path.abspath(fname), tserver, tusername, tpassword, tport) def install_package(self, fname): '''Install OpenWrt package (opkg).''' @@ -215,32 +195,6 @@ def install_package(self, fname): self.sendline("rm -f /%s" % target_file) self.expect(self.prompt) - def randomMAC(self): - mac = [0x00, 0x16, 0x3e, - random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff)] - return ':'.join(map(lambda x: "%02x" % x, mac)) - - def check_memory_addresses(self): - '''Check/set memory addresses and size for proper flashing.''' - pass - - def flash_uboot(self, uboot): - raise Exception('Code not written for flash_uboot for this board type, %s' % self.model) - - def flash_rootfs(self, ROOTFS): - raise Exception('Code not written for flash_rootfs for this board type, %s' % self.model) - - def flash_linux(self, KERNEL): - raise Exception('Code not written for flash_linux for this board type, %s.' % self.model) - - def flash_meta(self, META_BUILD): - raise Exception('Code not written for flash_meta for this board type, %s.' % self.model) - - def prepare_nfsroot(self, NFSROOT): - raise Exception('Code not written for prepare_nfsroot for this board type, %s.' % self.model) - def wait_for_boot(self): ''' Break into U-Boot. Check memory locations and sizes, and set @@ -278,25 +232,6 @@ def wait_for_boot(self): self.expect(["Writing to Nand... done", "Protected 1 sectors", "Saving Environment to NAND...", 'Saving Environment to FAT...']) self.expect(self.uprompt) - def kill_console_at_exit(self): - self.kill(signal.SIGKILL) - - def wait_for_network(self): - '''Wait until network interfaces have IP Addresses.''' - for interface in [self.wan_iface, self.lan_iface]: - for i in range(5): - try: - ipaddr = self.get_interface_ipaddr(interface).strip() - if not ipaddr: - continue - self.sendline("route -n") - self.expect(interface) - self.expect(self.prompt) - except pexpect.TIMEOUT: - print("waiting for wan ipaddr") - else: - break - def network_restart(self): '''Restart networking.''' self.sendline('\nifconfig') @@ -333,7 +268,11 @@ def get_wan_proto(self): self.expect("wan.proto='?([a-zA-Z0-9\.-]*)'?\r\n", timeout=5) return self.match.group(1) - def setup_uboot_network(self, TFTP_SERVER="192.168.0.1"): + def setup_uboot_network(self, tftp_server=None): + if self.tftp_server_int is None: + if tftp_server is None: + raise Exception("Error in TFTP server configuration") + self.tftp_server_int = tftp_server '''Within U-boot, request IP Address, set server IP, and other networking tasks.''' # Use standard eth1 address of wan-side computer @@ -348,9 +287,9 @@ def setup_uboot_network(self, TFTP_SERVER="192.168.0.1"): if i == 0: self.sendline('setenv ipaddr 192.168.0.2') self.expect(self.uprompt) - self.sendline('setenv serverip %s' % TFTP_SERVER) + self.sendline('setenv serverip %s' % self.tftp_server_int) self.expect(self.uprompt) - if TFTP_SERVER: + if self.tftp_server_int: #interfaces=['eth1','eth0'] passed = False for attempt in range(5): @@ -359,7 +298,7 @@ def setup_uboot_network(self, TFTP_SERVER="192.168.0.1"): self.expect('') self.expect(self.uprompt) self.sendline("ping $serverip") - self.expect("host %s is alive" % TFTP_SERVER) + self.expect("host %s is alive" % self.tftp_server_int) self.expect(self.uprompt) passed = True break @@ -382,33 +321,6 @@ def setup_uboot_network(self, TFTP_SERVER="192.168.0.1"): self.sendline('saveenv') self.expect(self.uprompt) - def boot_linux(self, rootfs=None): - print("\nWARNING: We don't know how to boot this board to linux " - "please write the code to do so.") - - def wait_for_linux(self): - '''Verify Linux starts up.''' - i = self.expect(['Reset Button Push down', 'Booting Linux', 'Starting kernel ...'], timeout=45) - if i == 0: - self.expect('httpd') - self.sendcontrol('c') - self.expect(self.uprompt) - self.sendline('boot') - i = self.expect(['U-Boot', 'login:', 'Please press Enter to activate this console'] + self.prompt, timeout=150) - if i == 0: - raise Exception('U-Boot came back when booting kernel') - elif i == 1: - self.sendline('root') - - # Give things time to start or crash on their own. - # Some things, like wifi, take a while. - self.expect(pexpect.TIMEOUT, timeout=40) - self.sendline('\r') - self.expect(self.prompt) - self.sendline('uname -a') - self.expect('Linux ') - self.expect(self.prompt) - def config_wan_proto(self, proto): '''Set protocol for WAN interface.''' if "dhcp" in proto: @@ -427,96 +339,165 @@ def config_wan_proto(self, proto): self.network_restart() self.expect(pexpect.TIMEOUT, timeout=10) - def uci_allow_wan_http(self): + def enable_mgmt_gui(self): + '''Allow access to webgui from devices on WAN interface ''' + self.uci_allow_wan_http(self.lan_gateway) + + def enable_ssh(self): + '''Allow ssh on wan interface ''' + self.uci_allow_wan_ssh(self.lan_gateway) + + def uci_allow_wan_http(self, lan_ip="192.168.1.1"): '''Allow access to webgui from devices on WAN interface.''' - self.uci_forward_traffic_redirect("tcp", "80", "192.168.1.1") + self.uci_forward_traffic_redirect("tcp", "80", lan_ip) - def uci_allow_wan_ssh(self): - self.uci_forward_traffic_redirect("tcp", "22", "192.168.1.1") + def uci_allow_wan_ssh(self, lan_ip="192.168.1.1"): + self.uci_forward_traffic_redirect("tcp", "22", lan_ip) + + def uci_allow_wan_https(self): + '''Allow access to webgui from devices on WAN interface.''' + self.uci_forward_traffic_redirect("tcp", "443", "192.168.1.1") def uci_forward_traffic_redirect(self, tcp_udp, port_wan, ip_lan): self.sendline('uci add firewall redirect') + self.expect(self.prompt) self.sendline('uci set firewall.@redirect[-1].src=wan') + self.expect(self.prompt) self.sendline('uci set firewall.@redirect[-1].src_dport=%s' % port_wan) + self.expect(self.prompt) self.sendline('uci set firewall.@redirect[-1].proto=%s' % tcp_udp) + self.expect(self.prompt) + self.sendline('uci set firewall.@redirect[-1].dest=lan') + self.expect(self.prompt) self.sendline('uci set firewall.@redirect[-1].dest_ip=%s' % ip_lan) + self.expect(self.prompt) self.sendline('uci commit firewall') + self.expect(self.prompt) self.firewall_restart() def uci_forward_traffic_rule(self, tcp_udp, port, ip, target="ACCEPT"): self.sendline('uci add firewall rule') + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].src=wan') + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].proto=%s' % tcp_udp) + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].dest=lan') + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].dest_ip=%s' % ip) + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].dest_port=%s' % port) + self.expect(self.prompt) self.sendline('uci set firewall.@rule[-1].target=%s' % target) + self.expect(self.prompt) self.sendline('uci commit firewall') + self.expect(self.prompt) self.firewall_restart() def wait_for_mounts(self): - # wait for overlay to finish mounting - for i in range(5): - try: - board.sendline('mount') - board.expect_exact('overlayfs:/overlay on / type overlay', timeout=15) - board.expect(prompt) - break - except: - pass - else: - print("WARN: Overlay still not mounted") - - # Optional send and expect functions to try and be fancy at catching errors - def send(self, s): - if BFT_DEBUG: - common.print_bold("%s = sending: %s" % - (error_detect.caller_file_line(3), repr(s))) - - if self.delaybetweenchar is not None: - ret = 0 - for char in s: - ret += super(OpenWrtRouter, self).send(char) - time.sleep(self.delaybetweenchar) - return ret - - return super(OpenWrtRouter, self).send(s) - - def expect_helper(self, pattern, wrapper, *args, **kwargs): - if not BFT_DEBUG: - return wrapper(pattern, *args, **kwargs) - - common.print_bold("%s = expecting: %s" % - (error_detect.caller_file_line(2), repr(pattern))) - try: - ret = wrapper(pattern, *args, **kwargs) - if hasattr(self.match, "group"): - common.print_bold("%s = matched: %s" % - (error_detect.caller_file_line(2), repr(self.match.group()))) - else: - common.print_bold("%s = matched: %s" % - (error_detect.caller_file_line(2), repr(pattern))) - return ret - except: - common.print_bold("expired") - raise - - def expect(self, pattern, *args, **kwargs): - wrapper = super(OpenWrtRouter, self).expect - - return self.expect_helper(pattern, wrapper, *args, **kwargs) - - def expect_exact(self, pattern, *args, **kwargs): - wrapper = super(OpenWrtRouter, self).expect_exact - - return self.expect_helper(pattern, wrapper, *args, **kwargs) - - def sendcontrol(self, char): - if BFT_DEBUG: - common.print_bold("%s = sending: control-%s" % - (error_detect.caller_file_line(3), repr(char))) - - return super(OpenWrtRouter, self).sendcontrol(char) + '''wait for overlay to finish mounting''' + for i in range(5): + try: + board.sendline('mount') + board.expect_exact('overlayfs:/overlay on / type overlay', timeout=15) + board.expect(prompt) + break + except: + pass + else: + print("WARN: Overlay still not mounted") + + def get_dns_server(self): + '''Getting dns server ip address ''' + return "%s" %self.lan_gateway + + def get_user_id(self, user_id): + self.sendline('cat /etc/passwd | grep -w ' + user_id) + idx = self.expect([user_id] + self.prompt) + if idx == 0: + self.expect(self.prompt) + return 0 == idx + + def get_pp_dev(self): + return self + + def collect_stats(self, stats=[]): + pp = self.get_pp_dev() + self.stats = [] + self.failed_stats = {} + + for stat in stats: + if 'mpstat' in stat: + for i in range(5): + try: + pp.sendcontrol('c') + pp.sendline("kill `ps | grep mpstat | grep -v grep | awk '{print $1}'`") + pp.expect_exact("kill `ps | grep mpstat | grep -v grep | awk '{print $1}'`") + pp.expect(pp.prompt) + break + except: + pp.sendcontrol('d') + pp = self.get_pp_dev() + if i == 4: + print_bold("FAILED TO KILL MPSTAT!") + pp.sendcontrol('c') + pass + + pp.sendline('mpstat -P ALL 5 > %s/mpstat &' % self.tmpdir) + if 0 == pp.expect(['mpstat: not found'] + pp.prompt): + self.failed_stats['mpstat'] = float('nan') + continue + elif 0 == pp.expect(['mpstat: not found', pexpect.TIMEOUT], timeout=4): + self.failed_stats['mpstat'] = float('nan') + continue + + pp.sendline('ps | grep mpstat') + + self.stats.append(stat) + + def parse_stats(self, dict_to_log={}): + pp = self.get_pp_dev() + + if 'mpstat' in self.stats: + pp.sendline('ps | grep mpstat') + pp.expect_exact('ps | grep mpstat') + if 0 == pp.expect([pexpect.TIMEOUT, 'mpstat -P ALL 5'], timeout=5): + self.failed_stats['mpstat'] = float('nan') + self.stats.remove('mpstat') + + idx = 0 + for not_used in range(len(self.stats)): + pp.sendline('fg') + pp.expect(self.stats) + if 'mpstat' in pp.match.group(): + pp.sendcontrol('c') + pp.expect(pp.prompt) + pp.sendline('cat %s/mpstat' % self.tmpdir) + pp.expect(['cat %s/mpstat' % self.tmpdir, pexpect.TIMEOUT]) + + idle_vals = [] + start = datetime.now() + while 0 == pp.expect(['all(\s+\d+\.\d{2}){9}\r\n', pexpect.TIMEOUT] + pp.prompt): + idle_vals.append(float(pp.match.group().strip().split(' ')[-1])) + if (datetime.now() - start).seconds > 60: + self.touch() + + if len(idle_vals) != 0: + avg_cpu_usage = 100 - sum(idle_vals) / len(idle_vals) + dict_to_log['mpstat'] = avg_cpu_usage + else: + dict_to_log['mpstat'] = 0 + + pp.sendline('rm %s/mpstat' % self.tmpdir) + pp.expect([pexpect.TIMEOUT] + pp.prompt) + + idx += 1 + + # TODO: verify we got 'em all + if idx != len(self.stats): + print("WARN: did not match all stats collected!") + + dict_to_log.update(self.failed_stats) if __name__ == '__main__': # Example use diff --git a/boardfarm/devices/power.py b/boardfarm/devices/power.py new file mode 100644 index 00000000..e626f307 --- /dev/null +++ b/boardfarm/devices/power.py @@ -0,0 +1,467 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +try: + from urllib.request import urlopen + from urllib.error import HTTPError + import urllib as _urllib +except: + from urllib2 import urlopen, HTTPError + import urllib2 as _urllib + +import pexpect +import dlipower +import time +import inspect + +def get_default_for_arg(function, arg): + args, varargs, keywords, defaults = inspect.getargspec(function) + return defaults + +from easysnmp import Session + +try: + from ouimeaux.environment import Environment as WemoEnv + from ouimeaux.device.switch import Switch as WemoSwitch +except: + WemoEnv = None + WemoSwitch = None + +def get_power_device(ip_address, username=None, password=None, outlet=None): + ''' + Try to determine the type of network-controlled power switch + at a given IP address. Return a class that can correctly + interact with that type of switch. + ''' + + login_failed = False + all_login_defaults = [] + for name, obj in globals().iteritems(): + if inspect.isclass(obj) and issubclass(obj, PowerDevice): + defaults = get_default_for_arg(obj.__init__, "username") + if defaults is not None and len(defaults) == 2: + all_login_defaults.append((defaults[0], defaults[1])) + + if ip_address is None: + if outlet is not None: + if "wemo://" in outlet: + if WemoEnv is None: + print("Please install ouimeaux: pip install ouimeaux") + else: + return WemoPowerSwitch(outlet=outlet) + if "serial://" in outlet: + return SimpleSerialPower(outlet=outlet) + if "cmd://" in outlet: + return SimpleCommandPower(outlet=outlet) + if "px2://" in outlet: + return PX2(outlet=outlet) + + return HumanButtonPusher() + + try: + data = urlopen("http://" + ip_address).read().decode() + except UnicodeDecodeError as e: + data = urlopen("http://" + ip_address).read() + except HTTPError as e: + if str(e) == 'HTTP Error 401: Unauthorized': + login_failed = True + + # still try to read data + data = e.read().decode() + except Exception as e: + print(e) + raise Exception("\nError connecting to %s" % ip_address) + + def check_data(data): + if 'Power Controller' in data: + return DLIPowerSwitch(ip_address, outlet=outlet, username=username, password=password) + if 'Sentry Switched CDU' in data: + return SentrySwitchedCDU(ip_address, outlet=outlet) + if '<title>APC ' in data: + return APCPower(ip_address, outlet=outlet) + if '<b>IP9258 Log In</b>' in data: + return Ip9258(ip_address, outlet, username=username, password=password) + if 'Cyber Power Systems' in data: + return CyberPowerPdu(ip_address, outlet=outlet, username=username, password=password) + if 'IP9820' in data: + return Ip9820(ip_address, outlet) + + return None + + ret = check_data(data) + if ret is not None: + return ret + + if login_failed: + # TODO: prioritize Ip9820 since it requires login? + def get_with_username_password(username, password): + # create a password manager + password_mgr = _urllib.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, 'http://' + ip_address, username, password) + handler = _urllib.HTTPBasicAuthHandler(password_mgr) + opener = _urllib.build_opener(handler) + opener.open('http://' + ip_address) + _urllib.install_opener(opener) + + request = _urllib.Request('http://' + ip_address) + response = opener.open(request) + data = response.read() + return data + + # try with passed in info first + ret = check_data(get_with_username_password(username, password)) + if ret is not None: + return ret + + for username, password in all_login_defaults: + try: + ret = check_data(get_with_username_password(username, password)) + except: + continue + else: + break + + ret = check_data(data) + if ret is not None: + return ret + + raise Exception("No code written to handle power device found at %s" % ip_address) + +class PowerDevice(): + ''' + At minimum, power devices let users reset an outlet over a network. + ''' + + def __init__(self, ip_address, username=None, password=None): + self.ip_address = ip_address + self.username = username + self.password = password + # Maybe verify connection is working here + + def reset(self, outlet): + '''Turn an outlet OFF, maybe wait, then back ON.''' + raise Exception('Code not written to reset with this type of power device at %s' % self.ip_address) + + +class SentrySwitchedCDU(PowerDevice): + ''' + Power Unit from Server Technology. + ''' + def __init__(self, + ip_address, + outlet, + username='admn', + password='admn'): + PowerDevice.__init__(self, ip_address, username, password) + self.outlet = outlet + # Verify connection + try: + pcon = self.__connect() + pcon.sendline('status .a%s' % self.outlet) + i = pcon.expect(['Command successful', 'User/outlet -- name not found']) + if i == 1: + raise Exception('\nOutlet %s not found' % self.outlet) + pcon.close() + except Exception as e: + print(e) + print("\nError with power device %s" % ip_address) + raise Exception("Error with power device %s" % ip_address) + + def __connect(self): + pcon = pexpect.spawn('telnet %s' % self.ip_address) + pcon.expect('Sentry Switched CDU Version', timeout=15) + pcon.expect('Username:') + pcon.sendline(self.username) + pcon.expect('Password:') + pcon.sendline(self.password) + i = pcon.expect(['Switched CDU:', 'Critical Alert']) + if i == 0: + return pcon + else: + print("\nCritical failure in %s, skipping PDU\n" % self.power_ip) + raise Exception("critical failure in %s" % self.power_ip) + + def reset(self, retry_attempts=2): + print("\n\nResetting board %s %s" % (self.ip_address, self.outlet)) + for attempt in range(retry_attempts): + try: + pcon = self.__connect() + pcon.sendline('reboot .a%s' % self.outlet) + pcon.expect('Command successful') + pcon.close() + return + except Exception as e: + print(e) + continue + raise Exception("\nProblem resetting outlet %s." % self.outlet) + +class PX2(PowerDevice): + ''' + Power Unit from Raritan. + ''' + def __init__(self, + outlet, + username='admin', + password='scripter99'): + ip_address, self.outlet = outlet.replace("px2://", '').split(';') + PowerDevice.__init__(self, ip_address, username, password) + + pcon = pexpect.spawn('telnet %s' % self.ip_address) + pcon.expect('Login for PX2 CLI') + pcon.expect('Username:') + pcon.sendline(self.username) + pcon.expect('Password:') + pcon.sendline(self.password) + pcon.expect('Welcome to PX2 CLI!') + pcon.expect('# ') + + self.pcon = pcon + + def reset(self): + self.pcon.sendline('power outlets %s cycle /y' % self.outlet) + self.pcon.expect('# ') + + +class HumanButtonPusher(PowerDevice): + ''' + Tell a person to physically reboot the router. + ''' + def __init__(self): + PowerDevice.__init__(self, None) + def reset(self): + print("\n\nUser power-cycle the device now!\n") + +class APCPower(PowerDevice): + '''Resets an APC style power control port''' + def __init__(self, + ip_address, + outlet, + username='apc', + password='apc'): + PowerDevice.__init__(self, ip_address, username, password) + self.outlet = outlet + def reset(self): + pcon = pexpect.spawn('telnet %s' % self.ip_address) + pcon.expect("User Name :") + pcon.send(self.username + "\r\n") + pcon.expect("Password :") + pcon.send(self.password + "\r\n") + pcon.expect("> ") + pcon.send("1" + "\r\n") + pcon.expect("> ") + pcon.send("2" + "\r\n") + pcon.expect("> ") + pcon.send("1" + "\r\n") + pcon.expect("> ") + pcon.send(self.outlet + "\r\n") + pcon.expect("> ") + pcon.send("1" + "\r\n") + pcon.expect("> ") + pcon.send("6" + "\r\n") + pcon.send("YES") + pcon.send("" + "\r\n") + pcon.expect("> ") + +class DLIPowerSwitch(PowerDevice): + '''Resets a DLI based power switch''' + def __init__(self, + ip_address, + outlet, + username, + password): + PowerDevice.__init__(self, ip_address, username, password) + self.switch = dlipower.PowerSwitch(hostname=ip_address, userid=username, password=password) + self.outlet = outlet + + def reset(self, outlet=None): + if outlet is None: + outlet = self.outlet + self.switch.cycle(outlet) + +class WemoPowerSwitch(PowerDevice): + ''' + Controls a wemo switch given an ipaddress. Run the following command to list devices: + + $ python ./devices/power.py + ''' + def __init__(self, outlet): + addr = 'http://' + outlet.replace("wemo://", "") + ":49153/setup.xml" + self.switch = WemoSwitch(addr) + def reset(self): + self.switch.off() + time.sleep(5) + self.switch.on() + +class SimpleCommandPower(PowerDevice): + ''' + Runs a simple command to turn power on/off + ''' + + on_cmd = "true" + off_cmd = "false" + + def __init__(self, outlet): + parsed = outlet.replace("cmd://", '').split(';') + for param in parsed: + for attr in ['on_cmd', 'off_cmd']: + if attr + '=' in param: + setattr(self, attr, param.replace(attr + '=', '').encode()) + + def reset(self): + pexpect.spawn(self.off_cmd).expect(pexpect.EOF) + time.sleep(5) + pexpect.spawn(self.on_cmd).expect(pexpect.EOF) + +class SimpleSerialPower(PowerDevice): + ''' + Simple serial based relay or power on off. Has an on and off string to send + over serial + ''' + serial_dev = '/dev/ttyACM0' + baud = 2400 + off_cmd = b'relay on 0' + delay = 5 + on_cmd = b'relay off 0' + + def __init__(self, outlet): + parsed = outlet.replace("serial://", '').split(';') + self.serial_dev = "/dev/" + parsed[0] + for param in parsed[1:]: + for attr in ['on_cmd', 'off_cmd']: + if attr + '=' in param: + setattr(self, attr, param.replace(attr + '=', '').encode()) + + def reset(self): + import serial + with serial.Serial(self.serial_dev, self.baud) as ser: + if self.off_cmd is not None: + ser.write(self.off_cmd + '\r') + time.sleep(5) + + ser.write(self.on_cmd + '\r') + + ser.close() + +# +# IP Power 9258 networked power switch class +# +# This work is released under the Creative Commons Zero (CC0) license. +# See http://creativecommons.org/publicdomain/zero/1.0/ + +# Example use: +# +# import time +# from ip9258 import Ip9258 +# +# ip9258 = Ip9258('192.168.1.10', 'admin', 'password') +# +# for i in range(4): +# ip9258.on(i) +# time.delay(1) +# +# ip9258.off(i) +# time.delay(1) + +class Ip9258(PowerDevice): + def __init__(self, ip_address, port, username="admin", password="12345678"): + PowerDevice.__init__(self, ip_address, username, password) + self._ip_address = ip_address + self.port = port + + # create a password manager + password_mgr = _urllib.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, 'http://' + ip_address, username, password) + handler = _urllib.HTTPBasicAuthHandler(password_mgr) + opener = _urllib.build_opener(handler) + # Now all calls to urllib2.urlopen use our opener. + _urllib.install_opener(opener) + + def on(self): + print("Power On Port(%s)\n" % self.port) + return _urllib.urlopen('http://' + self._ip_address + '/set.cmd?cmd=setpower+p6' + str(self.port) + '=1') + + def off(self): + print("Power Off Port(%s)\n" % self.port) + return _urllib.urlopen('http://' + self._ip_address + '/set.cmd?cmd=setpower+p6' + str(self.port) + '=0') + + def reset(self): + self.off() + time.sleep(5) + self.on() + +class CyberPowerPdu(PowerDevice): + def __init__(self, + ip_address, + outlet, + username='cyber', + password='cyber'): + PowerDevice.__init__(self, ip_address, username, password) + self.port = outlet + self.ip_address = ip_address + self.oid_Outlet = '1.3.6.1.4.1.3808.1.1.3.3.3.1.1.4' + self.session = Session(hostname=self.ip_address, community="private", version=2) + + def on(self): + oid = self.oid_Outlet + '.' + str(self.port) + self.session.set(oid, 1, 'i') + + def off(self): + oid = self.oid_Outlet + '.' + str(self.port) + self.session.set(oid, 2, 'i') + + def reset(self): + self.off() + time.sleep(5) + self.on() + +class Ip9820(PowerDevice): + def __init__(self, ip_address, port, username="admin", password="12345678"): + PowerDevice.__init__(self, ip_address, username, password) + self._ip_address = ip_address + self.port = port + + # create a password manager + password_mgr = _urllib.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, 'http://' + ip_address, username, password) + handler = _urllib.HTTPBasicAuthHandler(password_mgr) + opener = _urllib.build_opener(handler) + # Now all calls to _urllib.urlopen use our opener. + _urllib.install_opener(opener) + + def on(self): + print("Power On Port(%s)\n" % self.port) + return _urllib.urlopen('http://' + self._ip_address + '/set.cmd?cmd=setpower+p6' + str(self.port) + '=1') + + def off(self): + print("Power Off Port(%s)\n" % self.port) + return _urllib.urlopen('http://' + self._ip_address + '/set.cmd?cmd=setpower+p6' + str(self.port) + '=0') + + def reset(self): + self.off() + time.sleep(5) + self.on() + +if __name__ == "__main__": + print("Gathering info about power outlets...") + + if WemoEnv is not None: + env = WemoEnv() + env.start() + scan_time = 10 + print("Scanning for WeMo switches for %s seconds..." % scan_time) + env.discover(scan_time) + if len(env.list_switches()) > 0: + print("Found the following switches:"); + for switch_name in env.list_switches(): + switch = env.get_switch(switch_name) + print("%s ip address is %s" % (switch_name, switch.host)) + print("The switches above can be added by ip address" + " for example use the") + print("following to use %s" % switch_name) + print("\twemo://%s" % switch.host) + else: + print("No WeMo switches found") diff --git a/devices/qcom_akronite_nand.py b/boardfarm/devices/qcom_akronite_nand.py similarity index 93% rename from devices/qcom_akronite_nand.py rename to boardfarm/devices/qcom_akronite_nand.py index 5eec5813..3d6db0fc 100644 --- a/devices/qcom_akronite_nand.py +++ b/boardfarm/devices/qcom_akronite_nand.py @@ -13,6 +13,8 @@ class QcomAkroniteRouterNAND(qcom_arm_base.QcomArmBase): ''' Board with an Akronite processor. ''' + model = ("ipq8066", "db149", "ap145", "ap148", "ap148-osprey", + "ap148-beeliner", "ap160-1", "ap160-2", "ap161") machid_table = {"db149": "125b", "ap145": "12ca", "ap148": "1260", "ap148-beeliner": "1260", @@ -53,7 +55,7 @@ def flash_linux(self, KERNEL): raise Exception("Kernel is in UBI rootfs, not separate") - def boot_linux(self, rootfs=None): + def boot_linux(self, rootfs=None, bootargs=""): common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) self.reset() self.wait_for_boot() diff --git a/devices/qcom_akronite_nor.py b/boardfarm/devices/qcom_akronite_nor.py similarity index 96% rename from devices/qcom_akronite_nor.py rename to boardfarm/devices/qcom_akronite_nor.py index 120be820..280cb80a 100644 --- a/devices/qcom_akronite_nor.py +++ b/boardfarm/devices/qcom_akronite_nor.py @@ -12,6 +12,7 @@ class QcomAkroniteRouterNOR(qcom_arm_base.QcomArmBase): ''' Board with an Akronite processor. ''' + model = ("ap148-nor") def __init__(self, *args, **kwargs): super(QcomAkroniteRouterNOR, self).__init__(*args, **kwargs) @@ -36,7 +37,7 @@ def flash_linux(self, KERNEL): size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) self.spi_flash_bin("0x0062b0000", size, self.uboot_ddr_addr, "0x400000") - def boot_linux(self, rootfs=None): + def boot_linux(self, rootfs=None, bootargs=""): common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) self.reset() self.wait_for_boot() diff --git a/devices/qcom_arm_base.py b/boardfarm/devices/qcom_arm_base.py similarity index 94% rename from devices/qcom_arm_base.py rename to boardfarm/devices/qcom_arm_base.py index 3baab460..9e25aaef 100644 --- a/devices/qcom_arm_base.py +++ b/boardfarm/devices/qcom_arm_base.py @@ -8,6 +8,7 @@ import common import openwrt_router +from lib.randomMAC import randomMAC class QcomArmBase(openwrt_router.OpenWrtRouter): @@ -44,10 +45,10 @@ def check_memory_addresses(self): self.sendline('env default -f') self.expect('Resetting to default environment') self.expect(self.uprompt) - self.sendline('setenv ethaddr %s' % self.randomMAC()) + self.sendline('setenv ethaddr %s' % randomMAC()) self.expect(self.uprompt) - def flash_meta(self, META_BUILD): + def flash_meta(self, META_BUILD, wan, lan): ''' A meta image contains several components wrapped up into one file. Here we flash a meta image onto the board. @@ -81,6 +82,9 @@ def nand_flash_bin(self, addr, size, src): # make sure we round writes up to the next sector size hsize = hex((((int(size, 0) - 1) / self.flash_block_size) + 1) * self.flash_block_size) + if addr == None or addr == "0x0": + raise Exception("Refusing to flash 0x0 or None values for addr") + self.sendline("nand erase %s %s" % (addr, size)) self.expect("OK", timeout=90) self.expect(self.uprompt) @@ -97,6 +101,9 @@ def nand_flash_bin(self, addr, size, src): self.expect(self.uprompt) def spi_flash_bin(self, addr, size, src, esize=None): + if addr == None or addr == "0x0": + raise Exception("Refusing to flash 0x0 or None values for addr") + if esize == None: esize = size diff --git a/devices/qcom_dakota_nand.py b/boardfarm/devices/qcom_dakota_nand.py similarity index 96% rename from devices/qcom_dakota_nand.py rename to boardfarm/devices/qcom_dakota_nand.py index 3b33968f..d3c6c8de 100644 --- a/devices/qcom_dakota_nand.py +++ b/boardfarm/devices/qcom_dakota_nand.py @@ -13,6 +13,7 @@ class QcomDakotaRouterNAND(qcom_akronite_nand.QcomAkroniteRouterNAND): ''' Board with a Dakota processor. ''' + model = ("dk07-nand", "dk04-nand") uboot_ddr_addr = "0x88000000" machid_table = {"dk03": "8010100", "dk04-nand": "8010001", "dk06-nand": "8010005", "dk07-nand": "8010006", "ea8300": "8010006"} diff --git a/devices/qcom_dakota_nor.py b/boardfarm/devices/qcom_dakota_nor.py similarity index 95% rename from devices/qcom_dakota_nor.py rename to boardfarm/devices/qcom_dakota_nor.py index 4cd228ef..4718ae97 100644 --- a/devices/qcom_dakota_nor.py +++ b/boardfarm/devices/qcom_dakota_nor.py @@ -12,6 +12,7 @@ class QcomDakotaRouterNOR(qcom_arm_base.QcomArmBase): ''' Board with an Dakota processor. ''' + model = ("dk01-nor", "dk04-nor") uboot_ddr_addr = "0x88000000" machid_table = {"dk01-nor": "8010000", "dk04-nor": "8010001"} @@ -37,7 +38,7 @@ def flash_linux(self, KERNEL): size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) self.spi_flash_bin(self.kernel_addr, size, self.uboot_ddr_addr, self.kernel_size) - def boot_linux(self, rootfs=None): + def boot_linux(self, rootfs=None, bootargs=""): common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) self.reset() self.wait_for_boot() diff --git a/devices/qcom_mips.py b/boardfarm/devices/qcom_mips.py similarity index 96% rename from devices/qcom_mips.py rename to boardfarm/devices/qcom_mips.py index 02ef1a76..a8e1f48e 100644 --- a/devices/qcom_mips.py +++ b/boardfarm/devices/qcom_mips.py @@ -13,6 +13,8 @@ class QcomMipsRouter(openwrt_router.OpenWrtRouter): ''' Board with a MIPS processor. ''' + model = ("db120", "ap135", "ap143", "ap147", "ap152", "ap151", + "ap151-16M", "ap143", "ap152-8M", "tew-823dru") prompt = ['root\\@.*:.*#', ] uprompt = ['ath>', 'ar7240>'] @@ -90,7 +92,7 @@ def flash_linux(self, KERNEL): self.expect('Total of .* bytes were the same') self.expect(self.uprompt) - def boot_linux(self, rootfs=None): + def boot_linux(self, rootfs=None, bootargs=""): common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) if self.model == "ap135-nand": self.sendline('setenv bootcmd nboot 0x81000000 0 0x100000') diff --git a/boardfarm/devices/qemu.py b/boardfarm/devices/qemu.py new file mode 100644 index 00000000..a4ccae75 --- /dev/null +++ b/boardfarm/devices/qemu.py @@ -0,0 +1,171 @@ +# Copyright (c) 2017 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import openwrt_router +import signal +import sys +import pexpect +import atexit +import os +import ipaddress + +class Qemu(openwrt_router.OpenWrtRouter): + ''' + Emulated QEMU board + ''' + model = ("qemux86") + + wan_iface = "eth0" + lan_iface = "brlan0" + + lan_network = ipaddress.IPv4Network(u"10.0.0.0/24") + lan_gateway = ipaddress.IPv4Address(u"10.0.0.1") + + # allowed open ports (starting point, dns is on wan?) + wan_open_ports = ['22', '53'] + + cleanup_files = [] + kvm = False + + def __init__(self, + model, + conn_cmd, + power_ip, + power_outlet, + output=sys.stdout, + password='bigfoot1', + web_proxy=None, + tftp_server=None, + tftp_username=None, + tftp_password=None, + tftp_port=None, + connection_type=None, + power_username=None, + power_password=None, + rootfs=None, + kernel=None, + env=None, + **kwargs): + self.consoles = [self] + + if rootfs is None: + raise Exception("The QEMU device type requires specifying a rootfs") + + def temp_download(url): + dl_console = pexpect.spawn("bash --noprofile --norc") + dl_console.sendline('export PS1="prompt>>"') + dl_console.expect_exact("prompt>>") + dl_console.sendline('mktemp') + dl_console.expect('/tmp/tmp.*') + fname = dl_console.match.group(0).strip() + dl_console.expect_exact("prompt>>") + self.cleanup_files.append(fname) + atexit.register(self.run_cleanup_cmd) + dl_console.logfile_read = sys.stdout + print("Temp downloaded file = %s" % url) + dl_console.sendline("curl -n -L -k '%s' > %s" % (url, fname)) + dl_console.expect_exact("prompt>>", timeout=500) + dl_console.logfile_read = None + dl_console.sendline('exit') + dl_console.expect(pexpect.EOF) + return fname + + if rootfs.startswith("http://") or rootfs.startswith("https://"): + rootfs = temp_download(rootfs) + + cmd = "%s %s" % (conn_cmd, rootfs) + + if kernel is not None: + if kernel.startswith("http://") or kernel.startswith("https://"): + kernel = temp_download(kernel) + cmd += " -kernel %s --append root=/dev/hda2" % kernel + + # check if we can run kvm + kvm_chk = pexpect.spawn('sudo kvm-ok') + if 0 != kvm_chk.expect(['KVM acceleration can be used', pexpect.EOF]): + cmd = cmd.replace('--enable-kvm ', '') + self.kvm = False + + try: + pexpect.spawn.__init__(self, command='/bin/bash', + args=["-c", cmd], env=env) + self.expect(pexpect.TIMEOUT, timeout=1) + except pexpect.EOF: + self.pid = None + if 'failed to initialize KVM: Device or resource busy' in self.before or \ + 'failed to initialize KVM: Cannot allocate memory' in self.before: + cmd = cmd.replace('--enable-kvm ', '') + self.kvm = False + pexpect.spawn.__init__(self, command='/bin/bash', + args=["-c", cmd], env=env) + else: + raise + + self.cmd = cmd + if kernel is None: + self.expect(["SYSLINUX", "GNU GRUB"]) + self.logfile_read = output + + atexit.register(self.kill_console_at_exit) + + def run_cleanup_cmd(self): + for f in self.cleanup_files: + if os.path.isfile(f): + os.remove(f) + + def close(self, *args, **kwargs): + self.kill_console_at_exit() + return super(Qemu, self).close(*args, **kwargs) + + def kill_console_at_exit(self): + try: + self.sendcontrol('a') + self.send('c') + self.sendline('q') + self.kill(signal.SIGKILL) + except: + pass + + def wait_for_boot(self): + pass + + def setup_uboot_network(self, tftp_server=None): + pass + + def flash_rootfs(self, ROOTFS): + pass + + def flash_linux(self, KERNEL): + pass + + def wait_for_linux(self): + if self.kvm: + tout = 60 + else: + tout = 180 + + for t in range(0, tout, 10): + self.sendline() + i = self.expect([pexpect.TIMEOUT, 'login:'] + self.prompt, timeout=10) + if i == 1: + self.sendline('root') + self.expect(self.prompt, timeout=tout) + if i >= 1: + break + + def boot_linux(self, rootfs=None, bootargs=None): + pass + + def reset(self): + self.sendcontrol('a') + self.send('c') + self.sendline('system_reset') + self.expect_exact(['system_reset', 'Linux version']) + if '-kernel' not in self.cmd: + self.expect(['SYSLINUX', 'GNU GRUB']) + self.sendcontrol('a') + self.send('c') diff --git a/boardfarm/devices/qemu_openwrt.py b/boardfarm/devices/qemu_openwrt.py new file mode 100644 index 00000000..3ded86bc --- /dev/null +++ b/boardfarm/devices/qemu_openwrt.py @@ -0,0 +1,21 @@ +# Copyright (c) 2017 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import qemu +import ipaddress + +class QemuOpenWrt(qemu.Qemu): + ''' + Emulated QEMU board + ''' + model = ("qemux86-openwrt") + + wan_iface = "eth1" + lan_iface = "br-lan" + + lan_network = ipaddress.IPv4Network(u"192.168.1.0/24") + lan_gateway = ipaddress.IPv4Address(u"192.168.1.1") diff --git a/devices/rpi.py b/boardfarm/devices/rpi.py similarity index 56% rename from devices/rpi.py rename to boardfarm/devices/rpi.py index f843efae..da834e7f 100644 --- a/devices/rpi.py +++ b/boardfarm/devices/rpi.py @@ -7,16 +7,22 @@ import common import openwrt_router - +import os +import ipaddress +import pexpect class RPI(openwrt_router.OpenWrtRouter): ''' Raspberry pi board ''' + model = ("rpi3") wan_iface = "erouter0" lan_iface = "brlan0" + lan_network = ipaddress.IPv4Network(u"10.0.0.0/24") + lan_gateway = ipaddress.IPv4Address(u"10.0.0.1") + uprompt = ["U-Boot>"] uboot_eth = "sms0" uboot_ddr_addr = "0x1000000" @@ -28,6 +34,13 @@ class RPI(openwrt_router.OpenWrtRouter): # can't get u-boot to work without a delay delaybetweenchar = 0.05 + # allowed open ports (starting point) + wan_open_ports = ['22', '8080', '8087', '8088', '8090'] + + flash_meta_booted = True + + cdrouter_config = "configs/cdrouter-rdkb.conf" + def flash_uboot(self, uboot): '''In this case it's flashing the vfat partition of the bootload. Need to have that image u-boot and serial turned on via dtoverlay @@ -93,7 +106,7 @@ def flash_rootfs(self, ROOTFS): self.expect(self.uprompt) self.sendline('mmc write %s %s %s' % (self.uboot_ddr_addr, start, count)) self.expect_exact('mmc write %s %s %s' % (self.uboot_ddr_addr, start, count)) - self.expect(self.uprompt, timeout=120) + self.expect(self.uprompt, timeout=480) def flash_linux(self, KERNEL): common.print_bold("\n===== Flashing linux =====\n") @@ -101,16 +114,75 @@ def flash_linux(self, KERNEL): filename = self.prepare_file(KERNEL) size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) - self.sendline('fatwrite mmc 0 %s uImage $filesize' % self.uboot_ddr_addr) + self.kernel_file = os.path.basename(KERNEL) + self.sendline('fatwrite mmc 0 %s %s $filesize' % (self.kernel_file, self.uboot_ddr_addr)) self.expect(self.uprompt) - def boot_linux(self, rootfs=None): + def flash_meta(self, META, wan, lan): + '''Flashes a combine signed image over TFTP''' + print("\n===== Updating entire SD card image =====\n") + # must start before we copy as it erases files + wan.start_tftp_server() + + filename = self.prepare_file(META, tserver=wan.config['ipaddr'], tport=wan.config.get('port', '22')) + + wan_ip = wan.get_interface_ipaddr('eth1') + self.sendline('ping -c1 %s' % wan_ip) + self.expect_exact('1 packets transmitted, 1 packets received, 0% packet loss') + self.expect(self.prompt) + + self.sendline('cd /tmp') + self.expect(self.prompt) + self.sendline(' tftp -g -r %s 10.0.1.1' % filename) + self.expect(self.prompt, timeout=500) + + self.sendline('systemctl isolate rescue.target') + if 0 == self.expect(['Give root password for maintenance', 'Welcome Press Enter for maintenance', 'Press Enter for maintenance']): + self.sendline('password') + else: + self.sendline() + self.expect_exact('sh-3.2# ') + self.sendline('cd /tmp') + self.expect_exact('sh-3.2# ') + self.sendline('mount -no remount,ro /') + self.expect_exact('sh-3.2# ') + self.sendline('dd if=$(basename %s) of=/dev/mmcblk0 && sync' % filename) + self.expect(pexpect.TIMEOUT, timeout=120) + self.reset() + self.wait_for_boot() + # we need to update bootargs, should be doing this at build time + self.boot_linux() + self.wait_for_linux() + + def wait_for_linux(self): + super(RPI, self).wait_for_linux() + + self.sendline('cat /etc/issue') + if 0 == self.expect(['OpenEmbedded'] + self.prompt): + self.routing = False + self.wan_iface = "eth0" + self.lan_iface = None + self.expect(self.prompt) + + self.sendline('dmcli eRT getv Device.DeviceInfo.X_RDKCENTRAL-COM_CaptivePortalEnable') + if self.expect([' type: bool, value: false', 'dmcli: not found'] + self.prompt) > 1: + self.sendline('dmcli eRT setv Device.DeviceInfo.X_RDKCENTRAL-COM_CaptivePortalEnable bool false') + self.expect(self.prompt) + self.sendline('reboot') + super(RPI, self).wait_for_linux() + + def boot_linux(self, rootfs=None, bootargs=""): common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) - #self.sendline('setenv bootargs "8250.nr_uarts=1 bcm2708_fb.fbwidth=1824 bcm2708_fb.fbheight=984 bcm2708_fb.fbswap=1 dma.dmachans=0x7f35 bcm2709.boardrev=0xa02082 bcm2709.serial=0xc07187c2 bcm2709.uart_clock=48000000 smsc95xx.macaddr=B8:27:EB:71:87:C2 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 root=mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait"') - #self.expect(self.uprompt) + self.sendline('fdt addr $fdt_addr') + self.expect(self.uprompt) + self.sendline('fdt get value bcm_bootargs /chosen bootargs') + self.expect(self.uprompt) + + self.sendline('setenv bootargs "$bcm_bootargs %s"' % bootargs) + self.expect(self.uprompt) - self.sendline("setenv bootcmd 'fatload mmc 0 ${kernel_addr_r} uImage; bootm ${kernel_addr_r} - ${fdt_addr}'") + self.sendline("setenv bootcmd 'fatload mmc 0 ${kernel_addr_r} %s; bootm ${kernel_addr_r} - ${fdt_addr}; booti ${kernel_addr_r} - ${fdt_addr}'" % getattr(self, 'kernel_file', 'uImage')) self.expect(self.uprompt) self.sendline('saveenv') self.expect(self.uprompt) diff --git a/devices/ser2net_connection.py b/boardfarm/devices/ser2net_connection.py similarity index 84% rename from devices/ser2net_connection.py rename to boardfarm/devices/ser2net_connection.py index aa6ca5d4..c7e9ccf6 100644 --- a/devices/ser2net_connection.py +++ b/boardfarm/devices/ser2net_connection.py @@ -11,11 +11,11 @@ def connect(self): args=['-c', self.conn_cmd]) try: - result = self.device.expect(["assword:", "ser2net.*\r\n", "OpenGear Serial Server"]) - except pexpect.EOF as e: + result = self.device.expect(["assword:", "ser2net.*\r\n", "OpenGear Serial Server", "to access the port escape menu"]) + except pexpect.EOF: raise Exception("Board is in use (connection refused).") if result == 0: raise Exception("Password required and not supported") - def close(): + def close(self): self.device.sendline("~.") diff --git a/devices/ssh_connection.py b/boardfarm/devices/ssh_connection.py similarity index 81% rename from devices/ssh_connection.py rename to boardfarm/devices/ssh_connection.py index ca329576..4d2e79cc 100644 --- a/devices/ssh_connection.py +++ b/boardfarm/devices/ssh_connection.py @@ -18,8 +18,11 @@ def connect(self): args=['-c', self.conn_cmd]) try: - result = self.device.expect(["assword:", "passphrase"] + self.device.prompt) - except pexpect.EOF as e: + result = self.device.expect(["assword:", "passphrase", "yes/no"] + self.device.prompt) + if result == 2: + self.device.sendline("yes") + result = self.device.expect(["assword:", "passphrase"] + self.device.prompt) + except pexpect.EOF: raise Exception("Board is in use (connection refused).") if result == 0 or result == 1: assert self.ssh_password is not None, "Please add ssh_password in your json configuration file." diff --git a/boardfarm/devices/windows_telnet.py b/boardfarm/devices/windows_telnet.py new file mode 100755 index 00000000..894358ed --- /dev/null +++ b/boardfarm/devices/windows_telnet.py @@ -0,0 +1,95 @@ +import ipaddress +import re +import sys +import base +import connection_decider +from lib.regexlib import AllValidIpv6AddressesRegex + +class WindowsTelnet(base.BaseDevice): + + model = ('windows-telnet') + # This prompt regex could use more work + prompt = ['[a-zA-Z]:\\\\.*>$'] + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + self.ip = self.kwargs['ipaddr'] + self.username = self.kwargs.get('username', 'Administrator') + self.password = self.kwargs.get('password', 'bigfoot1') + + conn_cmd = "telnet %s" % self.ip + + self.connection = connection_decider.connection("local_cmd", device=self, conn_cmd=conn_cmd) + self.connection.connect() + self.linesep = '\r' + + self.expect('login: ') + self.sendline(self.username) + self.expect('password: ') + self.sendline(self.password) + self.expect(self.prompt) + + # Hide login prints, resume after that's done + self.logfile_read = sys.stdout + + def get_ip(self, wifi_interface): + + self.sendline('netsh interface ip show config '+wifi_interface) + + self.expect("(.+)>",timeout=30) + Wifi_log = self.match.group(1) + + match = re.search('IP Address:\s+([\d.]+)' , str(Wifi_log)) + if match: + return match.group(1) + else: + return None + + def ping(self, ping_ip ,source_ip=None, ping_count=4, ping_interface=None): + + if source_ip == None : + self.sendline('ping -n %s %s'%(ping_count, ping_ip)) + else: + self.sendline("ping -S %s -n %s %s"%(source_ip, ping_count, ping_ip)) + + self.expect("(.+)>",timeout=30) + Wifi_log = self.match.group(1) + + match = re.search('Reply from .+: bytes=.+ TTL=' , str(Wifi_log)) + if match: + return 'True' + else: + return 'False' + + def set_dhcp(self , wifi_interface): + self.sendline('netsh interface ip set address '+wifi_interface+" dhcp") + self.expect(self.prompt) + + def set_static_ip(self , wifi_interface, fix_ip, fix_mark, fix_gateway): + self.sendline('netsh interface ip set address '+wifi_interface+" static "+fix_ip+" "+fix_mark+" "+fix_gateway+" 1") + self.expect(self.prompt) + + def get_default_gateway(self, wifi_interface): + self.sendline('netsh interface ip show config '+wifi_interface) + + self.expect("(.+)>",timeout=30) + Wifi_log = self.match.group(1) + + match = re.search('Default Gateway:\s+([\d.]+)' , str(Wifi_log)) + if match: + return match.group(1) + else: + return None + + def get_interface_ipaddr(self, interface): + self.get_ip(interface) + + def get_interface_ip6addr(self, interface): + self.sendline("netsh interface ipv6 show addresses %s" %interface) + self.expect(self.prompt) + for match in re.findall(AllValidIpv6AddressesRegex, self.before): + ipv6addr = ipaddress.IPv6Address(unicode(match)) + if not ipv6addr.is_link_local: + return ipv6addr diff --git a/boardfarm/devices/windows_wsl.py b/boardfarm/devices/windows_wsl.py new file mode 100644 index 00000000..e5bdc2fd --- /dev/null +++ b/boardfarm/devices/windows_wsl.py @@ -0,0 +1,5 @@ +import debian + +class WindowsWSL(debian.DebianBox): + + model = ('windows_wsl') diff --git a/docs/BOARD_FARM.md b/boardfarm/docs/BOARD_FARM.md similarity index 65% rename from docs/BOARD_FARM.md rename to boardfarm/docs/BOARD_FARM.md index 88788cc0..61de9f7c 100644 --- a/docs/BOARD_FARM.md +++ b/boardfarm/docs/BOARD_FARM.md @@ -257,3 +257,123 @@ If running openSUSE, you would run: ``` zypper in uucp ``` + +Starting with an Unknown board +---------------------- + +In a perfect world, the device you are using is already supported but that's +not always the case. This section will highlight some examples for running on +a new unsupported board. + +We've added a board called 'new_device' to the boardfarm example configuration, +with this configuration you can get started easily. This example also makes use of +docker containers for the LAN and WAN virtual devices - so it's also an example of +how that works + +You need to add the user to the docker group as well so the boardfarm scripts can +run docker commands. Also for the time being you need to have password-less sudo +so we can run certain networking related commands + +The default configuration also assumes that you have no device to power cycle the, +so you need to watch for messages: + +``` +User power-cycle the device now! +``` + +And that would be when you manually go and power cycle the board. After that if +everything works properly, the device will come up and it will run an example test. +Since you are using an unknown board type, you will possibly see some messages +where the tool is probing network interfaces but you can ignore those for the +most part + +To get started run you need to update the boardfarm config with the actual ethernet +devices you have connected to the WAN and LAN ports of the device under test. In the +example it uses enxc05627904e8 and enx0050b61bfde4 - these devices can be any device +as long as the packets arrive at the device on the right ports (e.g. you can use VLAN +devices here as well). Also, you need to update the conn_cmd attribute to be a command +that connects to the serial console of your device. This can be telnet, cu, etc as +long as it's a normal console. + +After that is done you can start a basic shell that just connects to all your devices: + +``` +$ ./bft -n new_device -x connect +[ .. snip .. ] +==================== Begin Interact ==================== +Press Ctrl-] to stop interaction and return to menu +``` + +If you got here, a lot went right. The scripts spawned the docker containers and +connected to the console. Go ahead and enter the menu by following the instructions: + +``` +Current station + Board console: cu -l /dev/ttyUSB1 -s 115200 + wan device: ssh root@29ea56ffcf76 + lan device: ssh root@64386c00d161 +Pro-tip: Increase kernel message verbosity with + echo "7 7 7 7" > /proc/sys/kernel/printk +Menu + 1: Enter console + 2: List all tests + 3: Run test + 4: Reset board + 5: Enter interactive python shell + Type a device name to connect: [u'wan', u'lan'] + x: Exit +Please select: +``` + +From here you can type lan, and press enter.. or wan and press enter. This will +change the shell from the device under test to the WAN and LAN docker containers. +You should verfiy that the eth0 and eth1 devices are present in each containter. +eth1 is the interface name of the device you specified in the boardfarm config. +If something went wrong you will need each command one-by-one outside of the +boardfarm scripts and see if there are errors present. + +Assuming no errors, you can exit the menu and try to run another test but this +time we will let the boardfarm scripts do more than connect, they will bring up +the WAN and LAN containers where the WAN container is hosting DHCP and routing +traffic (like an ISP would) and the LAN container is grabbing an IP via DHCP from +the device under test. Also, after that basic configuration is done, it will then +run a simple test (note: you can run tests from the Interact menu as well). + +``` +$ ./bft -n new_device -e iPerf3Test +[ .. snip .. ] +Test suite "flash" has been specified, will attempt to run tests: + 1 RootFSBootTest from /home/mattsm/git/boardfarm/tests/rootfs_boot.pyc +Extra tests specified on command line: + <class 'tests.iperf3_test.iPerf3Test'> + <class 'tests.interact.Interact'> + +[ .. snip .. ] +==================== Begin RootFSBootTest ==================== +[ .. snip .. ] +==================== End RootFSBootTest ====================== + +==================== Begin iPerf3Test ==================== +[ .. snip .. ] +==================== End iPerf3Test ====================== + +==================== Begin Interact ==================== +``` + +The above output is severely truncated to save space, but in each section see +the output from the device under test, WAN, and LAN containers. During the run, +you should see the output from the iperf3 command showing the results of the +test, something like this: + +``` +[SUM] 0.00-60.05 sec 498 MBytes 69.6 Mbits/sec 322 sender +[SUM] 0.00-60.05 sec 498 MBytes 69.5 Mbits/sec receiver +``` + +NOTE: since we are running new containers each time keep in mind that the WAN +and LAN devices will be completely reset each time you start the scripts. You +could actual modify the startup and make the containers persistant across +invocations of the script. Each way has it's pros and cons + +Next step is to add proper support for your new board so you can take advantage of +automated flashing and remote power and reset to completly automate your CI flow diff --git a/docs/BoardFarm.png b/boardfarm/docs/BoardFarm.png similarity index 100% rename from docs/BoardFarm.png rename to boardfarm/docs/BoardFarm.png diff --git a/docs/ELK_SETUP.md b/boardfarm/docs/ELK_SETUP.md similarity index 100% rename from docs/ELK_SETUP.md rename to boardfarm/docs/ELK_SETUP.md diff --git a/docs/Simple_Board_Farm.jpg b/boardfarm/docs/Simple_Board_Farm.jpg similarity index 100% rename from docs/Simple_Board_Farm.jpg rename to boardfarm/docs/Simple_Board_Farm.jpg diff --git a/html/template_results.html b/boardfarm/html/template_results.html similarity index 92% rename from html/template_results.html rename to boardfarm/html/template_results.html index c180ab15..a676f367 100644 --- a/html/template_results.html +++ b/boardfarm/html/template_results.html @@ -56,6 +56,7 @@ <h2 style="margin: 0px;">${summary_title}</h2> <th>Result</th> <th>Name</th> <th>Description</th> + <th>Time</th> </tr> ${table_results} </table> @@ -67,11 +68,11 @@ <h2 style="margin: 0px;">${summary_title}</h2> <br> Board access:<br> <ul> + <li>Report completion time: ${report_time}</li> <li>Name: ${station}</li> <li>Router console: ${conn_cmd}</li> - <li>Lan Device: ssh root@${lan_device}</li> - <li>Wan Device: ssh root@${wan_device}</li> - <li>WebGUI: use HTTP Proxy "${lan_device}", Port "8080"</li> + <li>Location: ${location}</li> +${misc} </ul> Changes: ${changes} <div style="width:640px;border-top: thin solid;font-size: 10pt"> diff --git a/html/template_results_basic.html b/boardfarm/html/template_results_basic.html similarity index 98% rename from html/template_results_basic.html rename to boardfarm/html/template_results_basic.html index 46b0479f..89bde97e 100644 --- a/html/template_results_basic.html +++ b/boardfarm/html/template_results_basic.html @@ -49,6 +49,7 @@ <h2 style="margin: 0px;">${summary_title}</h2> <th>Result</th> <th>Name</th> <th>Description</th> + <th>Time</th> </tr> ${table_results} </table> diff --git a/boardfarm/library.py b/boardfarm/library.py new file mode 100644 index 00000000..bc2fedbb --- /dev/null +++ b/boardfarm/library.py @@ -0,0 +1,131 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import datetime +import ipaddress +import json +import os + +from termcolor import cprint + +_version = (1, 0, 0) +version = '.'.join(str(x) for x in _version) + + +class HelperEncoder(json.JSONEncoder): + '''Turn some objects into a form that can be stored in JSON.''' + def default(self, obj): + if isinstance(obj, ipaddress.IPv4Network) or \ + isinstance(obj, ipaddress.IPv4Address): + return str(obj) + elif isinstance(obj, datetime.datetime): + return str(obj) + elif hasattr(obj, 'shortname'): + return obj.shortname() + else: + try: + return json.JSONEncoder.default(self, obj) + except: + print("WARNING: HelperEncoder doesn't know how to convert %s to a string or number" % type(obj)) + return "" + +def clean_for_json(data): + ''' + Given a python dictionary, walk the structure and convert values to + types that are valid for JSON. Return a python dictionary. + ''' + return json.loads(json.dumps(data, cls=HelperEncoder)) + +def printd(data): + '''Pretty-print as a JSON data object.''' + print(json.dumps(data, sort_keys=True, indent=4, cls=HelperEncoder)) + +def print_bold(msg): + cprint(msg, None, attrs=['bold']) + +def process_test_results(raw_test_results, golden={}): + full_results = {'test_results': [], + 'tests_pass': 0, + 'tests_fail': 0, + 'tests_skip': 0, + 'tests_total': 0, + 'unexpected_fail': 0, + 'unexpected_pass': 0, + } + for i, x in enumerate(raw_test_results): + def parse_and_add_results(cls, prefix=""): + name = prefix + getattr(cls, 'name', cls.__class__.__name__) + grade = getattr(cls, 'result_grade', None) + try: + if hasattr(cls, 'elapsed_time'): + elapsed_time = getattr(cls, 'elapsed_time') + else: + start_time = getattr(cls, 'start_time') + stop_time = getattr(cls, 'stop_time') + elapsed_time = stop_time - start_time + except: + elapsed_time = 0 + + unexpected = False + if '_source' in golden: + if name + "-result" in golden['_source']: + if golden['_source'][name + "-result"] != grade: + unexpected = True + + if grade == "Unexp OK" or (grade == "OK" and unexpected): + grade = "Unexp OK" + full_results['unexpected_pass'] += 1 + elif grade == "Exp FAIL" or (grade == "FAIL" and unexpected): + grade = "Exp FAIL" + full_results['unexpected_fail'] += 1 + elif grade == "OK": + full_results['tests_pass'] += 1 + elif grade == "FAIL": + full_results['tests_fail'] += 1 + elif grade == "SKIP" or grade is None: + full_results['tests_skip'] += 1 + + message = getattr(cls, 'result_message', None) + + if message is None: + try: + message = cls.__doc__.split('\n')[0] + except: + message = "Missing description of class (no docstring)" + print_bold("WARN: Please add docstring to %s." % cls) + pass + + long_message = getattr(cls, 'long_result_message', "") + + full_results['test_results'].append({"name": name, "message": message, "long_message": long_message, "grade": grade, "elapsed_time": elapsed_time}) + + try: + parse_and_add_results(x) + + for subtest in x.subtests: + parse_and_add_results(subtest, prefix=x.__class__.__name__ + "-") + except Exception as e: + print("Failed to parse test result: %s" % e) + pass + + full_results['tests_total'] = len(raw_test_results) + return full_results + +def send_results_to_myqsl(testsuite, output_dir): + ''' + Send url of results to a MySQL database. Only do this if we are on + a build server (use the build environment variables). + ''' + dir = output_dir.replace(os.getcwd(), '').strip(os.sep) + build_id = os.environ.get('image_build_id', '') + build_url = os.environ.get('BUILD_URL', '') + if '' not in (build_id, testsuite, build_url): + from devices import mysql + build_url = build_url.replace("https://", "") + "artifact/openwrt/%s/results.html" % dir + title = 'Board Farm Results (suite: %s)' % testsuite + reporter = mysql.MySqlReporter() + reporter.insert_data(build_id, build_url, title) diff --git a/make_human_readable.py b/boardfarm/make_human_readable.py similarity index 71% rename from make_human_readable.py rename to boardfarm/make_human_readable.py index 9a70bdd0..eca1d5f2 100644 --- a/make_human_readable.py +++ b/boardfarm/make_human_readable.py @@ -11,7 +11,7 @@ import os import re import sys - +import time from string import Template try: from collections import Counter @@ -27,8 +27,21 @@ def pick_template_filename(): Decide which HTML file to use as template for results. This allows for different format for different audiences. ''' - templates = {'basic': owrt_tests_dir+"/html/template_results_basic.html", - 'full': owrt_tests_dir+"/html/template_results.html"} + + basic = owrt_tests_dir+"/html/template_results_basic.html" + full = owrt_tests_dir+"/html/template_results.html" + if 'BFT_OVERLAY' in os.environ: + for overlay in os.environ['BFT_OVERLAY'].split(' '): + overlay = os.path.realpath(overlay) + if os.path.isfile(overlay + "/html/template_results_basic.html"): + basic = overlay + "/html/template_results_basic.html" + break + if os.path.isfile(overlay + "/html/template_results.html"): + full = overlay + "/html/template_results.html" + break + + templates = {'basic': basic, + 'full': full} if os.environ.get('test_suite') == 'daily_au': return templates['basic'] else: @@ -43,9 +56,19 @@ def changes_to_html(changes): ''' if not changes: return None - if not config.code_change_server: - return changes + + # TODO: compare server to GERRIT_HOST and pick the right server... + # but for now take the first one we find base_url = config.code_change_server + if not base_url: + for ovrly_name, ovrly in config.layerconfs: + if hasattr(ovrly, 'code_change_server'): + base_url = ovrly.code_change_server + break + + if not base_url: + return changes + list_changes = re.findall('\d+,\d+', changes) if not list_changes: return None @@ -60,6 +83,17 @@ def changes_to_html(changes): continue return ", ".join(result) +def build_station_info(board_info): + ret = "" + + for device in board_info[u'devices']: + conn = device.get('conn_cmd', None) + if not conn: + conn = ":".join([device.get('ipaddr',''), device.get('port','')]) + ret += " <li>%s %s %s</li>\n" % (device['name'], device['type'], conn) + + return ret + def xmlresults_to_html(test_results, output_name=owrt_tests_dir+"/results/results.html", title=None, @@ -69,11 +103,11 @@ def xmlresults_to_html(test_results, 'summary_title' : title, 'changes': changes_to_html(os.environ.get('change_list')), "board_type": "unknown", - "lan_device": "unknown", - "wan_device": "unknown", - "conn_cmd" : "unknown"} + "location": "unknown", + "report_time" : "unknown"} try: parameters.update(board_info) + parameters['misc'] = build_station_info(board_info) except Exception as e: print(e) @@ -97,7 +131,7 @@ def xmlresults_to_html(test_results, grade_counter[t['grade']] += 1 if 'FAIL' == t['grade']: results_fail_table_lines.append('<tr class="%(row_style)s"><td>%(num)s</td><td class="%(style)s">%(grade)s</td><td>%(name)s</td></tr>' % t) - results_table_lines.append('<tr class="%(row_style)s"><td>%(num)s</td><td class="%(style)s">%(grade)s</td><td>%(name)s</td><td>%(message)s</td></tr>' % t) + results_table_lines.append('<tr class="%(row_style)s"><td>%(num)s</td><td class="%(style)s">%(grade)s</td><td>%(name)s</td><td>%(message)s</td><td>%(elapsed_time).2fs</td></tr>' % t) if t['long_message'] != "": results_table_lines.append('<tr class="%(row_style)s"><td colspan=4><pre align="left">' % t) results_table_lines.append("%(long_message)s" % t) @@ -123,6 +157,16 @@ def xmlresults_to_html(test_results, parameters['total_test_time'] = "%s minutes" % (test_seconds/60) except: pass + + # Report completion time + try: + end_timestamp = int(os.environ.get('TEST_END_TIME')) + struct_time = time.localtime(end_timestamp) + format_time = time.strftime("%Y-%m-%d %H:%M:%S", struct_time) + parameters['report_time'] = "%s" % (format_time) + except: + pass + # Substitute parameters into template html to create new html file template_filename = pick_template_filename() f = open(template_filename, "r").read() diff --git a/boardfarm/resources/mibs/SNMPv2-CONF.txt b/boardfarm/resources/mibs/SNMPv2-CONF.txt new file mode 100755 index 00000000..24a1eed9 --- /dev/null +++ b/boardfarm/resources/mibs/SNMPv2-CONF.txt @@ -0,0 +1,322 @@ +SNMPv2-CONF DEFINITIONS ::= BEGIN + +IMPORTS ObjectName, NotificationName, ObjectSyntax + FROM SNMPv2-SMI; + +-- definitions for conformance groups + +OBJECT-GROUP MACRO ::= +BEGIN + TYPE NOTATION ::= + ObjectsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + ObjectsPart ::= + "OBJECTS" "{" Objects "}" + Objects ::= + Object + | Objects "," Object + Object ::= + + value(ObjectName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- more definitions for conformance groups + +NOTIFICATION-GROUP MACRO ::= +BEGIN + TYPE NOTATION ::= + NotificationsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + NotificationsPart ::= + "NOTIFICATIONS" "{" Notifications "}" + Notifications ::= + Notification + | Notifications "," Notification + Notification ::= + value(NotificationName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- definitions for compliance statements + +MODULE-COMPLIANCE MACRO ::= +BEGIN + TYPE NOTATION ::= + "STATUS" Status + "DESCRIPTION" Text + ReferPart + ModulePart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + ModulePart ::= + Modules + Modules ::= + Module + | Modules Module + Module ::= + -- name of module -- + "MODULE" ModuleName + MandatoryPart + CompliancePart + + ModuleName ::= + -- identifier must start with uppercase letter + identifier ModuleIdentifier + -- must not be empty unless contained + -- in MIB Module + | empty + ModuleIdentifier ::= + value(OBJECT IDENTIFIER) + | empty + + MandatoryPart ::= + "MANDATORY-GROUPS" "{" Groups "}" + | empty + + Groups ::= + + Group + | Groups "," Group + Group ::= + value(OBJECT IDENTIFIER) + + CompliancePart ::= + Compliances + | empty + + Compliances ::= + Compliance + | Compliances Compliance + Compliance ::= + ComplianceGroup + | Object + + ComplianceGroup ::= + "GROUP" value(OBJECT IDENTIFIER) + "DESCRIPTION" Text + + Object ::= + "OBJECT" value(ObjectName) + SyntaxPart + WriteSyntaxPart + AccessPart + "DESCRIPTION" Text + + -- must be a refinement for object's SYNTAX clause + SyntaxPart ::= "SYNTAX" Syntax + | empty + + -- must be a refinement for object's SYNTAX clause + WriteSyntaxPart ::= "WRITE-SYNTAX" Syntax + | empty + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + AccessPart ::= + "MIN-ACCESS" Access + | empty + Access ::= + "not-accessible" + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +-- definitions for capabilities statements + +AGENT-CAPABILITIES MACRO ::= +BEGIN + TYPE NOTATION ::= + "PRODUCT-RELEASE" Text + "STATUS" Status + "DESCRIPTION" Text + ReferPart + ModulePart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + ModulePart ::= + Modules + | empty + Modules ::= + Module + | Modules Module + Module ::= + -- name of module -- + "SUPPORTS" ModuleName + "INCLUDES" "{" Groups "}" + VariationPart + + ModuleName ::= + + -- identifier must start with uppercase letter + identifier ModuleIdentifier + ModuleIdentifier ::= + value(OBJECT IDENTIFIER) + | empty + + Groups ::= + Group + | Groups "," Group + Group ::= + value(OBJECT IDENTIFIER) + + VariationPart ::= + Variations + | empty + Variations ::= + Variation + | Variations Variation + + Variation ::= + ObjectVariation + | NotificationVariation + + NotificationVariation ::= + "VARIATION" value(NotificationName) + AccessPart + "DESCRIPTION" Text + + ObjectVariation ::= + "VARIATION" value(ObjectName) + SyntaxPart + WriteSyntaxPart + AccessPart + CreationPart + DefValPart + "DESCRIPTION" Text + + -- must be a refinement for object's SYNTAX clause + SyntaxPart ::= "SYNTAX" Syntax + | empty + + WriteSyntaxPart ::= "WRITE-SYNTAX" Syntax + | empty + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + AccessPart ::= + "ACCESS" Access + | empty + + Access ::= + "not-implemented" + -- only "not-implemented" for notifications + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + -- following is for backward-compatibility only + | "write-only" + + CreationPart ::= + "CREATION-REQUIRES" "{" Cells "}" + | empty + Cells ::= + Cell + | Cells "," Cell + Cell ::= + value(ObjectName) + + DefValPart ::= "DEFVAL" "{" Defvalue "}" + | empty + + Defvalue ::= -- must be valid for the object's syntax + -- in this macro's SYNTAX clause, if present, + -- or if not, in object's OBJECT-TYPE macro + value(ObjectSyntax) + | "{" BitsValue "}" + + BitsValue ::= BitNames + | empty + + BitNames ::= BitName + | BitNames "," BitName + + BitName ::= identifier + + -- a character string as defined in [2] + Text ::= value(IA5String) +END + +END diff --git a/boardfarm/resources/mibs/SNMPv2-MIB.txt b/boardfarm/resources/mibs/SNMPv2-MIB.txt new file mode 100755 index 00000000..8c828305 --- /dev/null +++ b/boardfarm/resources/mibs/SNMPv2-MIB.txt @@ -0,0 +1,854 @@ +SNMPv2-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + TimeTicks, Counter32, snmpModules, mib-2 + FROM SNMPv2-SMI + DisplayString, TestAndIncr, TimeStamp + + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF; + +snmpMIB MODULE-IDENTITY + LAST-UPDATED "200210160000Z" + ORGANIZATION "IETF SNMPv3 Working Group" + CONTACT-INFO + "WG-EMail: snmpv3@lists.tislabs.com + Subscribe: snmpv3-request@lists.tislabs.com + + Co-Chair: Russ Mundy + Network Associates Laboratories + postal: 15204 Omega Drive, Suite 300 + Rockville, MD 20850-4601 + USA + EMail: mundy@tislabs.com + phone: +1 301 947-7107 + + Co-Chair: David Harrington + Enterasys Networks + postal: 35 Industrial Way + P. O. Box 5005 + Rochester, NH 03866-5005 + USA + EMail: dbh@enterasys.com + phone: +1 603 337-2614 + + Editor: Randy Presuhn + BMC Software, Inc. + postal: 2141 North First Street + San Jose, CA 95131 + USA + EMail: randy_presuhn@bmc.com + phone: +1 408 546-1006" + DESCRIPTION + "The MIB module for SNMP entities. + + Copyright (C) The Internet Society (2002). This + version of this MIB module is part of RFC 3418; + see the RFC itself for full legal notices. + " + REVISION "200210160000Z" + DESCRIPTION + "This revision of this MIB module was published as + RFC 3418." + REVISION "199511090000Z" + DESCRIPTION + "This revision of this MIB module was published as + RFC 1907." + REVISION "199304010000Z" + DESCRIPTION + "The initial revision of this MIB module was published + as RFC 1450." + ::= { snmpModules 1 } + +snmpMIBObjects OBJECT IDENTIFIER ::= { snmpMIB 1 } + +-- ::= { snmpMIBObjects 1 } this OID is obsolete +-- ::= { snmpMIBObjects 2 } this OID is obsolete +-- ::= { snmpMIBObjects 3 } this OID is obsolete + +-- the System group +-- +-- a collection of objects common to all managed systems. + +system OBJECT IDENTIFIER ::= { mib-2 1 } + +sysDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the entity. This value should + include the full name and version identification of + the system's hardware type, software operating-system, + and networking software." + ::= { system 1 } + +sysObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The vendor's authoritative identification of the + network management subsystem contained in the entity. + This value is allocated within the SMI enterprises + subtree (1.3.6.1.4.1) and provides an easy and + unambiguous means for determining `what kind of box' is + being managed. For example, if vendor `Flintstones, + Inc.' was assigned the subtree 1.3.6.1.4.1.424242, + it could assign the identifier 1.3.6.1.4.1.424242.1.1 + to its `Fred Router'." + ::= { system 2 } + +sysUpTime OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The time (in hundredths of a second) since the + network management portion of the system was last + re-initialized." + ::= { system 3 } + +sysContact OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The textual identification of the contact person for + this managed node, together with information on how + to contact this person. If no contact information is + known, the value is the zero-length string." + ::= { system 4 } + +sysName OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An administratively-assigned name for this managed + node. By convention, this is the node's fully-qualified + domain name. If the name is unknown, the value is + the zero-length string." + ::= { system 5 } + +sysLocation OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "The physical location of this node (e.g., 'telephone + closet, 3rd floor'). If the location is unknown, the + value is the zero-length string." + ::= { system 6 } + +sysServices OBJECT-TYPE + SYNTAX INTEGER (0..127) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A value which indicates the set of services that this + entity may potentially offer. The value is a sum. + + This sum initially takes the value zero. Then, for + each layer, L, in the range 1 through 7, that this node + performs transactions for, 2 raised to (L - 1) is added + to the sum. For example, a node which performs only + routing functions would have a value of 4 (2^(3-1)). + In contrast, a node which is a host offering application + services would have a value of 72 (2^(4-1) + 2^(7-1)). + Note that in the context of the Internet suite of + protocols, values should be calculated accordingly: + + layer functionality + 1 physical (e.g., repeaters) + 2 datalink/subnetwork (e.g., bridges) + 3 internet (e.g., supports the IP) + 4 end-to-end (e.g., supports the TCP) + 7 applications (e.g., supports the SMTP) + + For systems including OSI protocols, layers 5 and 6 + may also be counted." + ::= { system 7 } + +-- object resource information +-- +-- a collection of objects which describe the SNMP entity's +-- (statically and dynamically configurable) support of +-- various MIB modules. + +sysORLastChange OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time of the most recent + change in state or value of any instance of sysORID." + ::= { system 8 } + +sysORTable OBJECT-TYPE + SYNTAX SEQUENCE OF SysOREntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The (conceptual) table listing the capabilities of + the local SNMP application acting as a command + responder with respect to various MIB modules. + SNMP entities having dynamically-configurable support + of MIB modules will have a dynamically-varying number + of conceptual rows." + ::= { system 9 } + +sysOREntry OBJECT-TYPE + SYNTAX SysOREntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry (conceptual row) in the sysORTable." + INDEX { sysORIndex } + ::= { sysORTable 1 } + +SysOREntry ::= SEQUENCE { + sysORIndex INTEGER, + sysORID OBJECT IDENTIFIER, + sysORDescr DisplayString, + sysORUpTime TimeStamp +} + +sysORIndex OBJECT-TYPE + SYNTAX INTEGER (1..2147483647) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The auxiliary variable used for identifying instances + of the columnar objects in the sysORTable." + ::= { sysOREntry 1 } + +sysORID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "An authoritative identification of a capabilities + statement with respect to various MIB modules supported + by the local SNMP application acting as a command + responder." + ::= { sysOREntry 2 } + +sysORDescr OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A textual description of the capabilities identified + by the corresponding instance of sysORID." + ::= { sysOREntry 3 } + +sysORUpTime OBJECT-TYPE + SYNTAX TimeStamp + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The value of sysUpTime at the time this conceptual + row was last instantiated." + ::= { sysOREntry 4 } + +-- the SNMP group +-- +-- a collection of objects providing basic instrumentation and +-- control of an SNMP entity. + +snmp OBJECT IDENTIFIER ::= { mib-2 11 } + +snmpInPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of messages delivered to the SNMP + entity from the transport service." + ::= { snmp 1 } + +snmpInBadVersions OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of SNMP messages which were delivered + to the SNMP entity and were for an unsupported SNMP + version." + ::= { snmp 3 } + +snmpInBadCommunityNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of community-based SNMP messages (for + example, SNMPv1) delivered to the SNMP entity which + used an SNMP community name not known to said entity. + Also, implementations which authenticate community-based + SNMP messages using check(s) in addition to matching + the community name (for example, by also checking + whether the message originated from a transport address + allowed to use a specified community name) MAY include + in this value the number of messages which failed the + additional check(s). It is strongly RECOMMENDED that + + the documentation for any security model which is used + to authenticate community-based SNMP messages specify + the precise conditions that contribute to this value." + ::= { snmp 4 } + +snmpInBadCommunityUses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of community-based SNMP messages (for + example, SNMPv1) delivered to the SNMP entity which + represented an SNMP operation that was not allowed for + the SNMP community named in the message. The precise + conditions under which this counter is incremented + (if at all) depend on how the SNMP entity implements + its access control mechanism and how its applications + interact with that access control mechanism. It is + strongly RECOMMENDED that the documentation for any + access control mechanism which is used to control access + to and visibility of MIB instrumentation specify the + precise conditions that contribute to this value." + ::= { snmp 5 } + +snmpInASNParseErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of ASN.1 or BER errors encountered by + the SNMP entity when decoding received SNMP messages." + ::= { snmp 6 } + +snmpEnableAuthenTraps OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Indicates whether the SNMP entity is permitted to + generate authenticationFailure traps. The value of this + object overrides any configuration information; as such, + it provides a means whereby all authenticationFailure + traps may be disabled. + + Note that it is strongly recommended that this object + be stored in non-volatile memory so that it remains + constant across re-initializations of the network + management system." + ::= { snmp 30 } + +snmpSilentDrops OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Confirmed Class PDUs (such as + GetRequest-PDUs, GetNextRequest-PDUs, + GetBulkRequest-PDUs, SetRequest-PDUs, and + InformRequest-PDUs) delivered to the SNMP entity which + were silently dropped because the size of a reply + containing an alternate Response Class PDU (such as a + Response-PDU) with an empty variable-bindings field + was greater than either a local constraint or the + maximum message size associated with the originator of + the request." + ::= { snmp 31 } + +snmpProxyDrops OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The total number of Confirmed Class PDUs + (such as GetRequest-PDUs, GetNextRequest-PDUs, + GetBulkRequest-PDUs, SetRequest-PDUs, and + InformRequest-PDUs) delivered to the SNMP entity which + were silently dropped because the transmission of + the (possibly translated) message to a proxy target + failed in a manner (other than a time-out) such that + no Response Class PDU (such as a Response-PDU) could + be returned." + ::= { snmp 32 } + +-- information for notifications +-- +-- a collection of objects which allow the SNMP entity, when +-- supporting a notification originator application, +-- to be configured to generate SNMPv2-Trap-PDUs. + +snmpTrap OBJECT IDENTIFIER ::= { snmpMIBObjects 4 } + +snmpTrapOID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The authoritative identification of the notification + currently being sent. This variable occurs as + the second varbind in every SNMPv2-Trap-PDU and + InformRequest-PDU." + ::= { snmpTrap 1 } + +-- ::= { snmpTrap 2 } this OID is obsolete + +snmpTrapEnterprise OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The authoritative identification of the enterprise + associated with the trap currently being sent. When an + SNMP proxy agent is mapping an RFC1157 Trap-PDU + into a SNMPv2-Trap-PDU, this variable occurs as the + last varbind." + ::= { snmpTrap 3 } + +-- ::= { snmpTrap 4 } this OID is obsolete + +-- well-known traps + +snmpTraps OBJECT IDENTIFIER ::= { snmpMIBObjects 5 } + +coldStart NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "A coldStart trap signifies that the SNMP entity, + supporting a notification originator application, is + reinitializing itself and that its configuration may + have been altered." + ::= { snmpTraps 1 } + +warmStart NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "A warmStart trap signifies that the SNMP entity, + supporting a notification originator application, + is reinitializing itself such that its configuration + is unaltered." + ::= { snmpTraps 2 } + +-- Note the linkDown NOTIFICATION-TYPE ::= { snmpTraps 3 } +-- and the linkUp NOTIFICATION-TYPE ::= { snmpTraps 4 } +-- are defined in RFC 2863 [RFC2863] + +authenticationFailure NOTIFICATION-TYPE + STATUS current + DESCRIPTION + "An authenticationFailure trap signifies that the SNMP + entity has received a protocol message that is not + properly authenticated. While all implementations + of SNMP entities MAY be capable of generating this + trap, the snmpEnableAuthenTraps object indicates + whether this trap will be generated." + ::= { snmpTraps 5 } + +-- Note the egpNeighborLoss notification is defined +-- as { snmpTraps 6 } in RFC 1213 + +-- the set group +-- +-- a collection of objects which allow several cooperating +-- command generator applications to coordinate their use of the +-- set operation. + +snmpSet OBJECT IDENTIFIER ::= { snmpMIBObjects 6 } + +snmpSetSerialNo OBJECT-TYPE + SYNTAX TestAndIncr + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "An advisory lock used to allow several cooperating + command generator applications to coordinate their + use of the SNMP set operation. + + This object is used for coarse-grain coordination. + To achieve fine-grain coordination, one or more similar + objects might be defined within each MIB group, as + appropriate." + ::= { snmpSet 1 } + +-- conformance information + +snmpMIBConformance + OBJECT IDENTIFIER ::= { snmpMIB 2 } + +snmpMIBCompliances + OBJECT IDENTIFIER ::= { snmpMIBConformance 1 } +snmpMIBGroups OBJECT IDENTIFIER ::= { snmpMIBConformance 2 } + +-- compliance statements + +-- ::= { snmpMIBCompliances 1 } this OID is obsolete +snmpBasicCompliance MODULE-COMPLIANCE + STATUS deprecated + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement the SNMPv2 MIB. + + This compliance statement is replaced by + snmpBasicComplianceRev2." + MODULE -- this module + MANDATORY-GROUPS { snmpGroup, snmpSetGroup, systemGroup, + snmpBasicNotificationsGroup } + + GROUP snmpCommunityGroup + DESCRIPTION + "This group is mandatory for SNMPv2 entities which + support community-based authentication." + ::= { snmpMIBCompliances 2 } + +snmpBasicComplianceRev2 MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMP entities which + implement this MIB module." + MODULE -- this module + MANDATORY-GROUPS { snmpGroup, snmpSetGroup, systemGroup, + snmpBasicNotificationsGroup } + + GROUP snmpCommunityGroup + DESCRIPTION + "This group is mandatory for SNMP entities which + support community-based authentication." + + GROUP snmpWarmStartNotificationGroup + DESCRIPTION + "This group is mandatory for an SNMP entity which + supports command responder applications, and is + able to reinitialize itself such that its + configuration is unaltered." + ::= { snmpMIBCompliances 3 } + +-- units of conformance + +-- ::= { snmpMIBGroups 1 } this OID is obsolete +-- ::= { snmpMIBGroups 2 } this OID is obsolete +-- ::= { snmpMIBGroups 3 } this OID is obsolete + +-- ::= { snmpMIBGroups 4 } this OID is obsolete + +snmpGroup OBJECT-GROUP + OBJECTS { snmpInPkts, + snmpInBadVersions, + snmpInASNParseErrs, + snmpSilentDrops, + snmpProxyDrops, + snmpEnableAuthenTraps } + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + and control of an SNMP entity." + ::= { snmpMIBGroups 8 } + +snmpCommunityGroup OBJECT-GROUP + OBJECTS { snmpInBadCommunityNames, + snmpInBadCommunityUses } + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + of a SNMP entity which supports community-based + authentication." + ::= { snmpMIBGroups 9 } + +snmpSetGroup OBJECT-GROUP + OBJECTS { snmpSetSerialNo } + STATUS current + DESCRIPTION + "A collection of objects which allow several cooperating + command generator applications to coordinate their + use of the set operation." + ::= { snmpMIBGroups 5 } + +systemGroup OBJECT-GROUP + OBJECTS { sysDescr, sysObjectID, sysUpTime, + sysContact, sysName, sysLocation, + sysServices, + sysORLastChange, sysORID, + sysORUpTime, sysORDescr } + STATUS current + DESCRIPTION + "The system group defines objects which are common to all + managed systems." + ::= { snmpMIBGroups 6 } + +snmpBasicNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { coldStart, authenticationFailure } + STATUS current + DESCRIPTION + "The basic notifications implemented by an SNMP entity + supporting command responder applications." + ::= { snmpMIBGroups 7 } + +snmpWarmStartNotificationGroup NOTIFICATION-GROUP + NOTIFICATIONS { warmStart } + STATUS current + DESCRIPTION + "An additional notification for an SNMP entity supporting + command responder applications, if it is able to reinitialize + itself such that its configuration is unaltered." + ::= { snmpMIBGroups 11 } + +snmpNotificationGroup OBJECT-GROUP + OBJECTS { snmpTrapOID, snmpTrapEnterprise } + STATUS current + DESCRIPTION + "These objects are required for entities + which support notification originator applications." + ::= { snmpMIBGroups 12 } + +-- definitions in RFC 1213 made obsolete by the inclusion of a +-- subset of the snmp group in this MIB + +snmpOutPkts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Messages which were + passed from the SNMP protocol entity to the + transport service." + ::= { snmp 2 } + +-- { snmp 7 } is not used + +snmpInTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `tooBig'." + ::= { snmp 8 } + +snmpInNoSuchNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `noSuchName'." + ::= { snmp 9 } + +snmpInBadValues OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field was + `badValue'." + ::= { snmp 10 } + +snmpInReadOnlys OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number valid SNMP PDUs which were delivered + to the SNMP protocol entity and for which the value + of the error-status field was `readOnly'. It should + be noted that it is a protocol error to generate an + SNMP PDU which contains the value `readOnly' in the + error-status field, as such this object is provided + as a means of detecting incorrect implementations of + the SNMP." + ::= { snmp 11 } + +snmpInGenErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were delivered + to the SNMP protocol entity and for which the value + of the error-status field was `genErr'." + ::= { snmp 12 } + +snmpInTotalReqVars OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of MIB objects which have been + retrieved successfully by the SNMP protocol entity + as the result of receiving valid SNMP Get-Request + and Get-Next PDUs." + ::= { snmp 13 } + +snmpInTotalSetVars OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of MIB objects which have been + altered successfully by the SNMP protocol entity as + the result of receiving valid SNMP Set-Request PDUs." + ::= { snmp 14 } + +snmpInGetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 15 } + +snmpInGetNexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have been + accepted and processed by the SNMP protocol entity." + ::= { snmp 16 } + +snmpInSetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been accepted and processed by the SNMP protocol + entity." + ::= { snmp 17 } + +snmpInGetResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been accepted and processed by the SNMP protocol + entity." + ::= { snmp 18 } + +snmpInTraps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Trap PDUs which have been + accepted and processed by the SNMP protocol entity." + ::= { snmp 19 } + +snmpOutTooBigs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `tooBig.'" + ::= { snmp 20 } + +snmpOutNoSuchNames OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status was `noSuchName'." + ::= { snmp 21 } + +snmpOutBadValues OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `badValue'." + ::= { snmp 22 } + +-- { snmp 23 } is not used + +snmpOutGenErrs OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP PDUs which were generated + by the SNMP protocol entity and for which the value + of the error-status field was `genErr'." + ::= { snmp 24 } + +snmpOutGetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 25 } + +snmpOutGetNexts OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 26 } + +snmpOutSetRequests OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 27 } + +snmpOutGetResponses OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 28 } + +snmpOutTraps OBJECT-TYPE + SYNTAX Counter32 + MAX-ACCESS read-only + STATUS obsolete + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 29 } + +snmpObsoleteGroup OBJECT-GROUP + OBJECTS { snmpOutPkts, snmpInTooBigs, snmpInNoSuchNames, + snmpInBadValues, snmpInReadOnlys, snmpInGenErrs, + snmpInTotalReqVars, snmpInTotalSetVars, + snmpInGetRequests, snmpInGetNexts, snmpInSetRequests, + snmpInGetResponses, snmpInTraps, snmpOutTooBigs, + snmpOutNoSuchNames, snmpOutBadValues, + snmpOutGenErrs, snmpOutGetRequests, snmpOutGetNexts, + snmpOutSetRequests, snmpOutGetResponses, snmpOutTraps + } + STATUS obsolete + DESCRIPTION + "A collection of objects from RFC 1213 made obsolete + by this MIB module." + ::= { snmpMIBGroups 10 } + +END diff --git a/boardfarm/resources/mibs/SNMPv2-SMI.txt b/boardfarm/resources/mibs/SNMPv2-SMI.txt new file mode 100755 index 00000000..1c01e1df --- /dev/null +++ b/boardfarm/resources/mibs/SNMPv2-SMI.txt @@ -0,0 +1,344 @@ +SNMPv2-SMI DEFINITIONS ::= BEGIN + +-- the path to the root + +org OBJECT IDENTIFIER ::= { iso 3 } -- "iso" = 1 +dod OBJECT IDENTIFIER ::= { org 6 } +internet OBJECT IDENTIFIER ::= { dod 1 } + +directory OBJECT IDENTIFIER ::= { internet 1 } + +mgmt OBJECT IDENTIFIER ::= { internet 2 } +mib-2 OBJECT IDENTIFIER ::= { mgmt 1 } +transmission OBJECT IDENTIFIER ::= { mib-2 10 } + +experimental OBJECT IDENTIFIER ::= { internet 3 } + +private OBJECT IDENTIFIER ::= { internet 4 } +enterprises OBJECT IDENTIFIER ::= { private 1 } + +security OBJECT IDENTIFIER ::= { internet 5 } + +snmpV2 OBJECT IDENTIFIER ::= { internet 6 } + +-- transport domains +snmpDomains OBJECT IDENTIFIER ::= { snmpV2 1 } + +-- transport proxies +snmpProxys OBJECT IDENTIFIER ::= { snmpV2 2 } + +-- module identities +snmpModules OBJECT IDENTIFIER ::= { snmpV2 3 } + +-- Extended UTCTime, to allow dates with four-digit years +-- (Note that this definition of ExtUTCTime is not to be IMPORTed +-- by MIB modules.) +ExtUTCTime ::= OCTET STRING(SIZE(11 | 13)) + -- format is YYMMDDHHMMZ or YYYYMMDDHHMMZ + + -- where: YY - last two digits of year (only years + -- between 1900-1999) + -- YYYY - last four digits of the year (any year) + -- MM - month (01 through 12) + -- DD - day of month (01 through 31) + -- HH - hours (00 through 23) + -- MM - minutes (00 through 59) + -- Z - denotes GMT (the ASCII character Z) + -- + -- For example, "9502192015Z" and "199502192015Z" represent + -- 8:15pm GMT on 19 February 1995. Years after 1999 must use + -- the four digit year format. Years 1900-1999 may use the + -- two or four digit format. + +-- definitions for information modules + +MODULE-IDENTITY MACRO ::= +BEGIN + TYPE NOTATION ::= + "LAST-UPDATED" value(Update ExtUTCTime) + "ORGANIZATION" Text + "CONTACT-INFO" Text + "DESCRIPTION" Text + RevisionPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + RevisionPart ::= + Revisions + | empty + Revisions ::= + Revision + | Revisions Revision + Revision ::= + "REVISION" value(Update ExtUTCTime) + "DESCRIPTION" Text + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +OBJECT-IDENTITY MACRO ::= +BEGIN + TYPE NOTATION ::= + "STATUS" Status + "DESCRIPTION" Text + + ReferPart + + VALUE NOTATION ::= + value(VALUE OBJECT IDENTIFIER) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- names of objects +-- (Note that these definitions of ObjectName and NotificationName +-- are not to be IMPORTed by MIB modules.) + +ObjectName ::= + OBJECT IDENTIFIER + +NotificationName ::= + OBJECT IDENTIFIER + +-- syntax of objects + +-- the "base types" defined here are: +-- 3 built-in ASN.1 types: INTEGER, OCTET STRING, OBJECT IDENTIFIER +-- 8 application-defined types: Integer32, IpAddress, Counter32, +-- Gauge32, Unsigned32, TimeTicks, Opaque, and Counter64 + +ObjectSyntax ::= + CHOICE { + simple + SimpleSyntax, + -- note that SEQUENCEs for conceptual tables and + -- rows are not mentioned here... + + application-wide + ApplicationSyntax + } + +-- built-in ASN.1 types + +SimpleSyntax ::= + CHOICE { + -- INTEGERs with a more restrictive range + -- may also be used + integer-value -- includes Integer32 + INTEGER (-2147483648..2147483647), + -- OCTET STRINGs with a more restrictive size + -- may also be used + string-value + OCTET STRING (SIZE (0..65535)), + objectID-value + OBJECT IDENTIFIER + } + +-- indistinguishable from INTEGER, but never needs more than +-- 32-bits for a two's complement representation +Integer32 ::= + INTEGER (-2147483648..2147483647) + +-- application-wide types + +ApplicationSyntax ::= + CHOICE { + ipAddress-value + IpAddress, + counter-value + Counter32, + timeticks-value + TimeTicks, + arbitrary-value + Opaque, + big-counter-value + Counter64, + unsigned-integer-value -- includes Gauge32 + Unsigned32 + } + +-- in network-byte order + +-- (this is a tagged type for historical reasons) +IpAddress ::= + [APPLICATION 0] + IMPLICIT OCTET STRING (SIZE (4)) + +-- this wraps +Counter32 ::= + [APPLICATION 1] + IMPLICIT INTEGER (0..4294967295) + +-- this doesn't wrap +Gauge32 ::= + [APPLICATION 2] + IMPLICIT INTEGER (0..4294967295) + +-- an unsigned 32-bit quantity +-- indistinguishable from Gauge32 +Unsigned32 ::= + [APPLICATION 2] + IMPLICIT INTEGER (0..4294967295) + +-- hundredths of seconds since an epoch +TimeTicks ::= + [APPLICATION 3] + IMPLICIT INTEGER (0..4294967295) + +-- for backward-compatibility only +Opaque ::= + [APPLICATION 4] + IMPLICIT OCTET STRING + +-- for counters that wrap in less than one hour with only 32 bits +Counter64 ::= + [APPLICATION 6] + IMPLICIT INTEGER (0..18446744073709551615) + +-- definition for objects + +OBJECT-TYPE MACRO ::= +BEGIN + TYPE NOTATION ::= + "SYNTAX" Syntax + UnitsPart + "MAX-ACCESS" Access + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + IndexPart + DefValPart + + VALUE NOTATION ::= + value(VALUE ObjectName) + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), + -- a textual convention (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + + UnitsPart ::= + "UNITS" Text + | empty + + Access ::= + "not-accessible" + | "accessible-for-notify" + | "read-only" + | "read-write" + | "read-create" + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + IndexPart ::= + "INDEX" "{" IndexTypes "}" + | "AUGMENTS" "{" Entry "}" + | empty + IndexTypes ::= + IndexType + | IndexTypes "," IndexType + IndexType ::= + "IMPLIED" Index + | Index + + Index ::= + -- use the SYNTAX value of the + -- correspondent OBJECT-TYPE invocation + value(ObjectName) + Entry ::= + -- use the INDEX value of the + -- correspondent OBJECT-TYPE invocation + value(ObjectName) + + DefValPart ::= "DEFVAL" "{" Defvalue "}" + | empty + + Defvalue ::= -- must be valid for the type specified in + -- SYNTAX clause of same OBJECT-TYPE macro + value(ObjectSyntax) + | "{" BitsValue "}" + + BitsValue ::= BitNames + | empty + + BitNames ::= BitName + | BitNames "," BitName + + BitName ::= identifier + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- definitions for notifications + +NOTIFICATION-TYPE MACRO ::= +BEGIN + TYPE NOTATION ::= + ObjectsPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + + VALUE NOTATION ::= + value(VALUE NotificationName) + + ObjectsPart ::= + "OBJECTS" "{" Objects "}" + | empty + Objects ::= + Object + + | Objects "," Object + Object ::= + value(ObjectName) + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in section 3.1.1 + Text ::= value(IA5String) +END + +-- definitions of administrative identifiers + +zeroDotZero OBJECT-IDENTITY + STATUS current + DESCRIPTION + "A value used for null identifiers." + ::= { 0 0 } + +END diff --git a/boardfarm/resources/mibs/SNMPv2-TC.txt b/boardfarm/resources/mibs/SNMPv2-TC.txt new file mode 100755 index 00000000..860bf71e --- /dev/null +++ b/boardfarm/resources/mibs/SNMPv2-TC.txt @@ -0,0 +1,772 @@ +SNMPv2-TC DEFINITIONS ::= BEGIN + +IMPORTS + TimeTicks FROM SNMPv2-SMI; + +-- definition of textual conventions + +TEXTUAL-CONVENTION MACRO ::= + +BEGIN + TYPE NOTATION ::= + DisplayPart + "STATUS" Status + "DESCRIPTION" Text + ReferPart + "SYNTAX" Syntax + + VALUE NOTATION ::= + value(VALUE Syntax) -- adapted ASN.1 + + DisplayPart ::= + "DISPLAY-HINT" Text + | empty + + Status ::= + "current" + | "deprecated" + | "obsolete" + + ReferPart ::= + "REFERENCE" Text + | empty + + -- a character string as defined in [2] + Text ::= value(IA5String) + + Syntax ::= -- Must be one of the following: + -- a base type (or its refinement), or + -- a BITS pseudo-type + type + | "BITS" "{" NamedBits "}" + + NamedBits ::= NamedBit + | NamedBits "," NamedBit + + NamedBit ::= identifier "(" number ")" -- number is nonnegative + +END + +DisplayString ::= TEXTUAL-CONVENTION + DISPLAY-HINT "255a" + STATUS current + DESCRIPTION + "Represents textual information taken from the NVT ASCII + + character set, as defined in pages 4, 10-11 of RFC 854. + + To summarize RFC 854, the NVT ASCII repertoire specifies: + + - the use of character codes 0-127 (decimal) + + - the graphics characters (32-126) are interpreted as + US ASCII + + - NUL, LF, CR, BEL, BS, HT, VT and FF have the special + meanings specified in RFC 854 + + - the other 25 codes have no standard interpretation + + - the sequence 'CR LF' means newline + + - the sequence 'CR NUL' means carriage-return + + - an 'LF' not preceded by a 'CR' means moving to the + same column on the next line. + + - the sequence 'CR x' for any x other than LF or NUL is + illegal. (Note that this also means that a string may + end with either 'CR LF' or 'CR NUL', but not with CR.) + + Any object defined using this syntax may not exceed 255 + characters in length." + SYNTAX OCTET STRING (SIZE (0..255)) + +PhysAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1x:" + STATUS current + DESCRIPTION + "Represents media- or physical-level addresses." + SYNTAX OCTET STRING + +MacAddress ::= TEXTUAL-CONVENTION + DISPLAY-HINT "1x:" + STATUS current + DESCRIPTION + "Represents an 802 MAC address represented in the + `canonical' order defined by IEEE 802.1a, i.e., as if it + were transmitted least significant bit first, even though + 802.5 (in contrast to other 802.x protocols) requires MAC + addresses to be transmitted most significant bit first." + SYNTAX OCTET STRING (SIZE (6)) + +TruthValue ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents a boolean value." + SYNTAX INTEGER { true(1), false(2) } + +TestAndIncr ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents integer-valued information used for atomic + operations. When the management protocol is used to specify + that an object instance having this syntax is to be + modified, the new value supplied via the management protocol + must precisely match the value presently held by the + instance. If not, the management protocol set operation + fails with an error of `inconsistentValue'. Otherwise, if + the current value is the maximum value of 2^31-1 (2147483647 + decimal), then the value held by the instance is wrapped to + zero; otherwise, the value held by the instance is + incremented by one. (Note that regardless of whether the + management protocol set operation succeeds, the variable- + binding in the request and response PDUs are identical.) + + The value of the ACCESS clause for objects having this + syntax is either `read-write' or `read-create'. When an + instance of a columnar object having this syntax is created, + any value may be supplied via the management protocol. + + When the network management portion of the system is re- + initialized, the value of every object instance having this + syntax must either be incremented from its value prior to + the re-initialization, or (if the value prior to the re- + initialization is unknown) be set to a pseudo-randomly + generated value." + SYNTAX INTEGER (0..2147483647) + +AutonomousType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents an independently extensible type identification + value. It may, for example, indicate a particular sub-tree + with further MIB definitions, or define a particular type of + protocol or hardware." + SYNTAX OBJECT IDENTIFIER + +InstancePointer ::= TEXTUAL-CONVENTION + STATUS obsolete + DESCRIPTION + "A pointer to either a specific instance of a MIB object or + a conceptual row of a MIB table in the managed device. In + the latter case, by convention, it is the name of the + particular instance of the first accessible columnar object + in the conceptual row. + + The two uses of this textual convention are replaced by + VariablePointer and RowPointer, respectively." + SYNTAX OBJECT IDENTIFIER + +VariablePointer ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A pointer to a specific object instance. For example, + sysContact.0 or ifInOctets.3." + SYNTAX OBJECT IDENTIFIER + +RowPointer ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Represents a pointer to a conceptual row. The value is the + name of the instance of the first accessible columnar object + in the conceptual row. + + For example, ifIndex.3 would point to the 3rd row in the + ifTable (note that if ifIndex were not-accessible, then + ifDescr.3 would be used instead)." + SYNTAX OBJECT IDENTIFIER + +RowStatus ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The RowStatus textual convention is used to manage the + creation and deletion of conceptual rows, and is used as the + value of the SYNTAX clause for the status column of a + conceptual row (as described in Section 7.7.1 of [2].) + + The status column has six defined values: + + - `active', which indicates that the conceptual row is + available for use by the managed device; + + - `notInService', which indicates that the conceptual + row exists in the agent, but is unavailable for use by + the managed device (see NOTE below); 'notInService' has + no implication regarding the internal consistency of + the row, availability of resources, or consistency with + the current state of the managed device; + + - `notReady', which indicates that the conceptual row + exists in the agent, but is missing information + necessary in order to be available for use by the + managed device (i.e., one or more required columns in + the conceptual row have not been instanciated); + + - `createAndGo', which is supplied by a management + station wishing to create a new instance of a + conceptual row and to have its status automatically set + to active, making it available for use by the managed + device; + + - `createAndWait', which is supplied by a management + station wishing to create a new instance of a + conceptual row (but not make it available for use by + the managed device); and, + - `destroy', which is supplied by a management station + wishing to delete all of the instances associated with + an existing conceptual row. + + Whereas five of the six values (all except `notReady') may + be specified in a management protocol set operation, only + three values will be returned in response to a management + protocol retrieval operation: `notReady', `notInService' or + `active'. That is, when queried, an existing conceptual row + has only three states: it is either available for use by + the managed device (the status column has value `active'); + it is not available for use by the managed device, though + the agent has sufficient information to attempt to make it + so (the status column has value `notInService'); or, it is + not available for use by the managed device, and an attempt + to make it so would fail because the agent has insufficient + information (the state column has value `notReady'). + + NOTE WELL + + This textual convention may be used for a MIB table, + irrespective of whether the values of that table's + conceptual rows are able to be modified while it is + active, or whether its conceptual rows must be taken + out of service in order to be modified. That is, it is + the responsibility of the DESCRIPTION clause of the + status column to specify whether the status column must + not be `active' in order for the value of some other + column of the same conceptual row to be modified. If + such a specification is made, affected columns may be + changed by an SNMP set PDU if the RowStatus would not + be equal to `active' either immediately before or after + processing the PDU. In other words, if the PDU also + contained a varbind that would change the RowStatus + value, the column in question may be changed if the + RowStatus was not equal to `active' as the PDU was + received, or if the varbind sets the status to a value + other than 'active'. + + Also note that whenever any elements of a row exist, the + RowStatus column must also exist. + + To summarize the effect of having a conceptual row with a + status column having a SYNTAX clause value of RowStatus, + consider the following state diagram: + + STATE + +--------------+-----------+-------------+------------- + | A | B | C | D + | |status col.|status column| + |status column | is | is |status column + ACTION |does not exist| notReady | notInService| is active +--------------+--------------+-----------+-------------+------------- +set status |noError ->D|inconsist- |inconsistent-|inconsistent- +column to | or | entValue| Value| Value +createAndGo |inconsistent- | | | + | Value| | | +--------------+--------------+-----------+-------------+------------- +set status |noError see 1|inconsist- |inconsistent-|inconsistent- +column to | or | entValue| Value| Value +createAndWait |wrongValue | | | +--------------+--------------+-----------+-------------+------------- +set status |inconsistent- |inconsist- |noError |noError +column to | Value| entValue| | +active | | | | + | | or | | + | | | | + | |see 2 ->D|see 8 ->D| ->D +--------------+--------------+-----------+-------------+------------- +set status |inconsistent- |inconsist- |noError |noError ->C +column to | Value| entValue| | +notInService | | | | + | | or | | or + | | | | + | |see 3 ->C| ->C|see 6 +--------------+--------------+-----------+-------------+------------- +set status |noError |noError |noError |noError ->A +column to | | | | or +destroy | ->A| ->A| ->A|see 7 +--------------+--------------+-----------+-------------+------------- +set any other |see 4 |noError |noError |see 5 +column to some| | | | +value | | see 1| ->C| ->D +--------------+--------------+-----------+-------------+------------- + + (1) goto B or C, depending on information available to the + agent. + + (2) if other variable bindings included in the same PDU, + provide values for all columns which are missing but + required, and all columns have acceptable values, then + return noError and goto D. + + (3) if other variable bindings included in the same PDU, + provide legal values for all columns which are missing but + required, then return noError and goto C. + + (4) at the discretion of the agent, the return value may be + either: + + inconsistentName: because the agent does not choose to + create such an instance when the corresponding + RowStatus instance does not exist, or + + inconsistentValue: if the supplied value is + inconsistent with the state of some other MIB object's + value, or + + noError: because the agent chooses to create the + instance. + + If noError is returned, then the instance of the status + column must also be created, and the new state is B or C, + depending on the information available to the agent. If + inconsistentName or inconsistentValue is returned, the row + remains in state A. + + (5) depending on the MIB definition for the column/table, + either noError or inconsistentValue may be returned. + + (6) the return value can indicate one of the following + errors: + + wrongValue: because the agent does not support + notInService (e.g., an agent which does not support + createAndWait), or + + inconsistentValue: because the agent is unable to take + the row out of service at this time, perhaps because it + is in use and cannot be de-activated. + + (7) the return value can indicate the following error: + + inconsistentValue: because the agent is unable to + remove the row at this time, perhaps because it is in + use and cannot be de-activated. + + (8) the transition to D can fail, e.g., if the values of the + conceptual row are inconsistent, then the error code would + be inconsistentValue. + + NOTE: Other processing of (this and other varbinds of) the + set request may result in a response other than noError + being returned, e.g., wrongValue, noCreation, etc. + + Conceptual Row Creation + + There are four potential interactions when creating a + conceptual row: selecting an instance-identifier which is + not in use; creating the conceptual row; initializing any + objects for which the agent does not supply a default; and, + making the conceptual row available for use by the managed + device. + + Interaction 1: Selecting an Instance-Identifier + + The algorithm used to select an instance-identifier varies + for each conceptual row. In some cases, the instance- + identifier is semantically significant, e.g., the + destination address of a route, and a management station + selects the instance-identifier according to the semantics. + + In other cases, the instance-identifier is used solely to + distinguish conceptual rows, and a management station + without specific knowledge of the conceptual row might + examine the instances present in order to determine an + unused instance-identifier. (This approach may be used, but + it is often highly sub-optimal; however, it is also a + questionable practice for a naive management station to + attempt conceptual row creation.) + + Alternately, the MIB module which defines the conceptual row + might provide one or more objects which provide assistance + in determining an unused instance-identifier. For example, + if the conceptual row is indexed by an integer-value, then + an object having an integer-valued SYNTAX clause might be + defined for such a purpose, allowing a management station to + issue a management protocol retrieval operation. In order + to avoid unnecessary collisions between competing management + stations, `adjacent' retrievals of this object should be + different. + + Finally, the management station could select a pseudo-random + number to use as the index. In the event that this index + + was already in use and an inconsistentValue was returned in + response to the management protocol set operation, the + management station should simply select a new pseudo-random + number and retry the operation. + + A MIB designer should choose between the two latter + algorithms based on the size of the table (and therefore the + efficiency of each algorithm). For tables in which a large + number of entries are expected, it is recommended that a MIB + object be defined that returns an acceptable index for + creation. For tables with small numbers of entries, it is + recommended that the latter pseudo-random index mechanism be + used. + + Interaction 2: Creating the Conceptual Row + + Once an unused instance-identifier has been selected, the + management station determines if it wishes to create and + activate the conceptual row in one transaction or in a + negotiated set of interactions. + + Interaction 2a: Creating and Activating the Conceptual Row + + The management station must first determine the column + requirements, i.e., it must determine those columns for + which it must or must not provide values. Depending on the + complexity of the table and the management station's + knowledge of the agent's capabilities, this determination + can be made locally by the management station. Alternately, + the management station issues a management protocol get + operation to examine all columns in the conceptual row that + it wishes to create. In response, for each column, there + are three possible outcomes: + + - a value is returned, indicating that some other + management station has already created this conceptual + row. We return to interaction 1. + + - the exception `noSuchInstance' is returned, + indicating that the agent implements the object-type + associated with this column, and that this column in at + least one conceptual row would be accessible in the MIB + view used by the retrieval were it to exist. For those + columns to which the agent provides read-create access, + the `noSuchInstance' exception tells the management + station that it should supply a value for this column + when the conceptual row is to be created. + + - the exception `noSuchObject' is returned, indicating + that the agent does not implement the object-type + associated with this column or that there is no + conceptual row for which this column would be + accessible in the MIB view used by the retrieval. As + such, the management station can not issue any + management protocol set operations to create an + instance of this column. + + Once the column requirements have been determined, a + management protocol set operation is accordingly issued. + This operation also sets the new instance of the status + column to `createAndGo'. + + When the agent processes the set operation, it verifies that + it has sufficient information to make the conceptual row + available for use by the managed device. The information + available to the agent is provided by two sources: the + management protocol set operation which creates the + conceptual row, and, implementation-specific defaults + supplied by the agent (note that an agent must provide + implementation-specific defaults for at least those objects + which it implements as read-only). If there is sufficient + information available, then the conceptual row is created, a + `noError' response is returned, the status column is set to + `active', and no further interactions are necessary (i.e., + interactions 3 and 4 are skipped). If there is insufficient + information, then the conceptual row is not created, and the + set operation fails with an error of `inconsistentValue'. + On this error, the management station can issue a management + protocol retrieval operation to determine if this was + because it failed to specify a value for a required column, + or, because the selected instance of the status column + already existed. In the latter case, we return to + interaction 1. In the former case, the management station + can re-issue the set operation with the additional + information, or begin interaction 2 again using + `createAndWait' in order to negotiate creation of the + conceptual row. + + NOTE WELL + + Regardless of the method used to determine the column + requirements, it is possible that the management + station might deem a column necessary when, in fact, + the agent will not allow that particular columnar + instance to be created or written. In this case, the + management protocol set operation will fail with an + error such as `noCreation' or `notWritable'. In this + case, the management station decides whether it needs + to be able to set a value for that particular columnar + instance. If not, the management station re-issues the + management protocol set operation, but without setting + a value for that particular columnar instance; + otherwise, the management station aborts the row + creation algorithm. + + Interaction 2b: Negotiating the Creation of the Conceptual + Row + + The management station issues a management protocol set + operation which sets the desired instance of the status + column to `createAndWait'. If the agent is unwilling to + process a request of this sort, the set operation fails with + an error of `wrongValue'. (As a consequence, such an agent + must be prepared to accept a single management protocol set + operation, i.e., interaction 2a above, containing all of the + columns indicated by its column requirements.) Otherwise, + the conceptual row is created, a `noError' response is + returned, and the status column is immediately set to either + `notInService' or `notReady', depending on whether it has + sufficient information to (attempt to) make the conceptual + row available for use by the managed device. If there is + sufficient information available, then the status column is + set to `notInService'; otherwise, if there is insufficient + information, then the status column is set to `notReady'. + Regardless, we proceed to interaction 3. + + Interaction 3: Initializing non-defaulted Objects + + The management station must now determine the column + requirements. It issues a management protocol get operation + to examine all columns in the created conceptual row. In + the response, for each column, there are three possible + outcomes: + + - a value is returned, indicating that the agent + implements the object-type associated with this column + and had sufficient information to provide a value. For + those columns to which the agent provides read-create + access (and for which the agent allows their values to + be changed after their creation), a value return tells + the management station that it may issue additional + management protocol set operations, if it desires, in + order to change the value associated with this column. + + - the exception `noSuchInstance' is returned, + indicating that the agent implements the object-type + associated with this column, and that this column in at + least one conceptual row would be accessible in the MIB + view used by the retrieval were it to exist. However, + the agent does not have sufficient information to + provide a value, and until a value is provided, the + conceptual row may not be made available for use by the + managed device. For those columns to which the agent + provides read-create access, the `noSuchInstance' + exception tells the management station that it must + issue additional management protocol set operations, in + order to provide a value associated with this column. + + - the exception `noSuchObject' is returned, indicating + that the agent does not implement the object-type + associated with this column or that there is no + conceptual row for which this column would be + accessible in the MIB view used by the retrieval. As + such, the management station can not issue any + management protocol set operations to create an + instance of this column. + + If the value associated with the status column is + `notReady', then the management station must first deal with + all `noSuchInstance' columns, if any. Having done so, the + value of the status column becomes `notInService', and we + proceed to interaction 4. + + Interaction 4: Making the Conceptual Row Available + + Once the management station is satisfied with the values + associated with the columns of the conceptual row, it issues + a management protocol set operation to set the status column + to `active'. If the agent has sufficient information to + make the conceptual row available for use by the managed + device, the management protocol set operation succeeds (a + `noError' response is returned). Otherwise, the management + protocol set operation fails with an error of + `inconsistentValue'. + + NOTE WELL + + A conceptual row having a status column with value + `notInService' or `notReady' is unavailable to the + managed device. As such, it is possible for the + managed device to create its own instances during the + time between the management protocol set operation + which sets the status column to `createAndWait' and the + management protocol set operation which sets the status + column to `active'. In this case, when the management + protocol set operation is issued to set the status + column to `active', the values held in the agent + supersede those used by the managed device. + + If the management station is prevented from setting the + status column to `active' (e.g., due to management station + or network failure) the conceptual row will be left in the + `notInService' or `notReady' state, consuming resources + indefinitely. The agent must detect conceptual rows that + have been in either state for an abnormally long period of + time and remove them. It is the responsibility of the + DESCRIPTION clause of the status column to indicate what an + abnormally long period of time would be. This period of + time should be long enough to allow for human response time + (including `think time') between the creation of the + conceptual row and the setting of the status to `active'. + In the absence of such information in the DESCRIPTION + clause, it is suggested that this period be approximately 5 + minutes in length. This removal action applies not only to + newly-created rows, but also to previously active rows which + are set to, and left in, the notInService state for a + prolonged period exceeding that which is considered normal + for such a conceptual row. + + Conceptual Row Suspension + + When a conceptual row is `active', the management station + may issue a management protocol set operation which sets the + instance of the status column to `notInService'. If the + agent is unwilling to do so, the set operation fails with an + error of `wrongValue' or `inconsistentValue'. Otherwise, + the conceptual row is taken out of service, and a `noError' + response is returned. It is the responsibility of the + DESCRIPTION clause of the status column to indicate under + what circumstances the status column should be taken out of + service (e.g., in order for the value of some other column + of the same conceptual row to be modified). + + Conceptual Row Deletion + + For deletion of conceptual rows, a management protocol set + operation is issued which sets the instance of the status + column to `destroy'. This request may be made regardless of + the current value of the status column (e.g., it is possible + to delete conceptual rows which are either `notReady', + `notInService' or `active'.) If the operation succeeds, + then all instances associated with the conceptual row are + immediately removed." + SYNTAX INTEGER { + -- the following two values are states: + -- these values may be read or written + active(1), + notInService(2), + -- the following value is a state: + -- this value may be read, but not written + notReady(3), + -- the following three values are + -- actions: these values may be written, + -- but are never read + createAndGo(4), + createAndWait(5), + destroy(6) + } + +TimeStamp ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The value of the sysUpTime object at which a specific + occurrence happened. The specific occurrence must be + + defined in the description of any object defined using this + type. + + If sysUpTime is reset to zero as a result of a re- + initialization of the network management (sub)system, then + the values of all TimeStamp objects are also reset. + However, after approximately 497 days without a re- + initialization, the sysUpTime object will reach 2^^32-1 and + then increment around to zero; in this case, existing values + of TimeStamp objects do not change. This can lead to + ambiguities in the value of TimeStamp objects." + SYNTAX TimeTicks + +TimeInterval ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "A period of time, measured in units of 0.01 seconds." + SYNTAX INTEGER (0..2147483647) + +DateAndTime ::= TEXTUAL-CONVENTION + DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d" + STATUS current + DESCRIPTION + "A date-time specification. + + field octets contents range + ----- ------ -------- ----- + 1 1-2 year* 0..65536 + 2 3 month 1..12 + 3 4 day 1..31 + 4 5 hour 0..23 + 5 6 minutes 0..59 + 6 7 seconds 0..60 + (use 60 for leap-second) + 7 8 deci-seconds 0..9 + 8 9 direction from UTC '+' / '-' + 9 10 hours from UTC* 0..13 + 10 11 minutes from UTC 0..59 + + * Notes: + - the value of year is in network-byte order + - daylight saving time in New Zealand is +13 + + For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be + displayed as: + + 1992-5-26,13:30:15.0,-4:0 + + Note that if only local time is known, then timezone + information (fields 8-10) is not present." + SYNTAX OCTET STRING (SIZE (8 | 11)) + +StorageType ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Describes the memory realization of a conceptual row. A + row which is volatile(2) is lost upon reboot. A row which + is either nonVolatile(3), permanent(4) or readOnly(5), is + backed up by stable storage. A row which is permanent(4) + can be changed but not deleted. A row which is readOnly(5) + cannot be changed nor deleted. + + If the value of an object with this syntax is either + permanent(4) or readOnly(5), it cannot be written. + Conversely, if the value is either other(1), volatile(2) or + nonVolatile(3), it cannot be modified to be permanent(4) or + readOnly(5). (All illegal modifications result in a + 'wrongValue' error.) + + Every usage of this textual convention is required to + specify the columnar objects which a permanent(4) row must + at a minimum allow to be writable." + SYNTAX INTEGER { + other(1), -- eh? + volatile(2), -- e.g., in RAM + nonVolatile(3), -- e.g., in NVRAM + permanent(4), -- e.g., partially in ROM + readOnly(5) -- e.g., completely in ROM + } + +TDomain ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a kind of transport service. + + Some possible values, such as snmpUDPDomain, are defined in + the SNMPv2-TM MIB module. Other possible values are defined + in other MIB modules." + REFERENCE "The SNMPv2-TM MIB module is defined in RFC 1906." + SYNTAX OBJECT IDENTIFIER + +TAddress ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "Denotes a transport service address. + + A TAddress value is always interpreted within the context of a + TDomain value. Thus, each definition of a TDomain value must + be accompanied by a definition of a textual convention for use + with that TDomain. Some possible textual conventions, such as + SnmpUDPAddress for snmpUDPDomain, are defined in the SNMPv2-TM + MIB module. Other possible textual conventions are defined in + other MIB modules." + REFERENCE "The SNMPv2-TM MIB module is defined in RFC 1906." + SYNTAX OCTET STRING (SIZE (1..255)) + +END diff --git a/boardfarm/resources/mibs/rfc1213-mib2.asn1.txt b/boardfarm/resources/mibs/rfc1213-mib2.asn1.txt new file mode 100644 index 00000000..3f3522ed --- /dev/null +++ b/boardfarm/resources/mibs/rfc1213-mib2.asn1.txt @@ -0,0 +1,2657 @@ +-- file: asn1specs/1213_mib2.asn1 +-- +-- this file is used in ../c{,++}-examples/snmp/ +-- +-- $Header: /home/rainbow/rj/cvs/snacc/asn1specs/rfc1213-mib2.asn1,v 1.3 1995/07/27 08:29:19 rj Exp $ +-- $Log: rfc1213-mib2.asn1,v $ +-- Revision 1.3 1995/07/27 08:29:19 rj +-- rfc1155-smi.asn1, rfc1157-snmp.asn1 and rfc1213-mib2.asn1 renamed from 1155-smi.asn1, 1157-snmp.asn1 and 1213-mib2.asn1 to accomodate to snacc's new file name generation scheme. +-- +-- Revision 1.2 1995/07/25 19:53:14 rj +-- changed `_' to `-' in file names. +-- +-- Revision 1.1 1994/08/31 23:08:28 rj +-- first check-in. +-- + +RFC1213-MIB DEFINITIONS ::= BEGIN + + IMPORTS + mgmt, NetworkAddress, IpAddress, Counter, Gauge, + TimeTicks + FROM RFC1155-SMI + +-- OBJECT-TYPE +-- FROM RFC-1212 + + ; + + -- This MIB module uses the extended OBJECT-TYPE macro as + -- defined in [14]; + + + -- MIB-II (same prefix as MIB-I) + + mib-2 OBJECT IDENTIFIER ::= { mgmt 1 } + + -- textual conventions + + DisplayString ::= + OCTET STRING + -- This data type is used to model textual information taken + -- from the NVT ASCII character set. By convention, objects + -- with this syntax are declared as having + -- SIZE (0..255) + + PhysAddress ::= + OCTET STRING + -- This data type is used to model media addresses. For many + -- types of media, this will be in a binary representation. + -- For example, an ethernet address would be represented as + -- a string of 6 octets. + + + -- groups in MIB-II + + system OBJECT IDENTIFIER ::= { mib-2 1 } + + interfaces OBJECT IDENTIFIER ::= { mib-2 2 } + + at OBJECT IDENTIFIER ::= { mib-2 3 } + + ip OBJECT IDENTIFIER ::= { mib-2 4 } + + icmp OBJECT IDENTIFIER ::= { mib-2 5 } + + tcp OBJECT IDENTIFIER ::= { mib-2 6 } + + udp OBJECT IDENTIFIER ::= { mib-2 7 } + + egp OBJECT IDENTIFIER ::= { mib-2 8 } + + -- historical (some say hysterical) + -- cmot OBJECT IDENTIFIER ::= { mib-2 9 } + + transmission OBJECT IDENTIFIER ::= { mib-2 10 } + + snmp OBJECT IDENTIFIER ::= { mib-2 11 } + + + -- the System group + + -- Implementation of the System group is mandatory for all + -- systems. If an agent is not configured to have a value + -- for any of these variables, a string of length 0 is + -- returned. + + sysDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A textual description of the entity. This value + should include the full name and version + identification of the system's hardware type, + software operating-system, and networking + software. It is mandatory that this only contain + printable ASCII characters." + ::= { system 1 } + + sysObjectID OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The vendor's authoritative identification of the + network management subsystem contained in the + entity. This value is allocated within the SMI + enterprises subtree (1.3.6.1.4.1) and provides an + easy and unambiguous means for determining `what + kind of box' is being managed. For example, if + vendor `Flintstones, Inc.' was assigned the + subtree 1.3.6.1.4.1.4242, it could assign the + identifier 1.3.6.1.4.1.4242.1.1 to its `Fred + Router'." + ::= { system 2 } + + sysUpTime OBJECT-TYPE + SYNTAX TimeTicks + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The time (in hundredths of a second) since the + network management portion of the system was last + re-initialized." + ::= { system 3 } + + sysContact OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The textual identification of the contact person + for this managed node, together with information + on how to contact this person." + ::= { system 4 } + + sysName OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An administratively-assigned name for this + managed node. By convention, this is the node's + fully-qualified domain name." + ::= { system 5 } + + sysLocation OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The physical location of this node (e.g., + `telephone closet, 3rd floor')." + ::= { system 6 } + + sysServices OBJECT-TYPE + SYNTAX INTEGER (0..127) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A value which indicates the set of services that + this entity primarily offers. + + The value is a sum. This sum initially takes the + value zero, Then, for each layer, L, in the range + 1 through 7, that this node performs transactions + for, 2 raised to (L - 1) is added to the sum. For + example, a node which performs primarily routing + functions would have a value of 4 (2^(3-1)). In + contrast, a node which is a host offering + application services would have a value of 72 + (2^(4-1) + 2^(7-1)). Note that in the context of + the Internet suite of protocols, values should be + calculated accordingly: + + layer functionality + 1 physical (e.g., repeaters) + 2 datalink/subnetwork (e.g., bridges) + 3 internet (e.g., IP gateways) + 4 end-to-end (e.g., IP hosts) + 7 applications (e.g., mail relays) + + For systems including OSI protocols, layers 5 and + 6 may also be counted." + ::= { system 7 } + + + + -- the Interfaces group + + -- Implementation of the Interfaces group is mandatory for + -- all systems. + + ifNumber OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of network interfaces (regardless of + their current state) present on this system." + ::= { interfaces 1 } + + + -- the Interfaces table + + -- The Interfaces table contains information on the entity's + -- interfaces. Each interface is thought of as being + -- attached to a `subnetwork'. Note that this term should + -- not be confused with `subnet' which refers to an + -- addressing partitioning scheme used in the Internet suite + -- of protocols. + + ifTable OBJECT-TYPE + SYNTAX SEQUENCE OF IfEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A list of interface entries. The number of + entries is given by the value of ifNumber." + ::= { interfaces 2 } + + ifEntry OBJECT-TYPE + SYNTAX IfEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "An interface entry containing objects at the + subnetwork layer and below for a particular + interface." + INDEX { ifIndex } + ::= { ifTable 1 } + + IfEntry ::= + SEQUENCE { + ifIndex + INTEGER, + ifDescr + DisplayString, + ifType + INTEGER, + ifMtu + INTEGER, + ifSpeed + Gauge, + ifPhysAddress + PhysAddress, + ifAdminStatus + INTEGER, + ifOperStatus + INTEGER, + ifLastChange + TimeTicks, + ifInOctets + Counter, + ifInUcastPkts + Counter, + ifInNUcastPkts + Counter, + ifInDiscards + Counter, + ifInErrors + Counter, + ifInUnknownProtos + Counter, + ifOutOctets + Counter, + ifOutUcastPkts + Counter, + ifOutNUcastPkts + Counter, + ifOutDiscards + Counter, + ifOutErrors + Counter, + ifOutQLen + Gauge, + ifSpecific + OBJECT IDENTIFIER + } + + ifIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A unique value for each interface. Its value + ranges between 1 and the value of ifNumber. The + value for each interface must remain constant at + least from one re-initialization of the entity's + network management system to the next re- + initialization." + ::= { ifEntry 1 } + + ifDescr OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A textual string containing information about the + interface. This string should include the name of + the manufacturer, the product name and the version + of the hardware interface." + ::= { ifEntry 2 } + + ifType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + regular1822(2), + hdh1822(3), + ddn-x25(4), + rfc877-x25(5), + ethernet-csmacd(6), + iso88023-csmacd(7), + iso88024-tokenBus(8), + iso88025-tokenRing(9), + iso88026-man(10), + starLan(11), + proteon-10Mbit(12), + proteon-80Mbit(13), + hyperchannel(14), + fddi(15), + lapb(16), + sdlc(17), + ds1(18), -- T-1 + e1(19), -- european equiv. of T-1 + basicISDN(20), + primaryISDN(21), -- proprietary serial + propPointToPointSerial(22), + ppp(23), + softwareLoopback(24), + eon(25), -- CLNP over IP [11] + ethernet-3Mbit(26), + nsip(27), -- XNS over IP + slip(28), -- generic SLIP + ultra(29), -- ULTRA technologies + ds3(30), -- T-3 + sip(31), -- SMDS + frame-relay(32) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The type of interface, distinguished according to + the physical/link protocol(s) immediately `below' + the network layer in the protocol stack." + ::= { ifEntry 3 } + + ifMtu OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The size of the largest datagram which can be + sent/received on the interface, specified in + octets. For interfaces that are used for + transmitting network datagrams, this is the size + of the largest network datagram that can be sent + on the interface." + ::= { ifEntry 4 } + + ifSpeed OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "An estimate of the interface's current bandwidth + in bits per second. For interfaces which do not + vary in bandwidth or for those where no accurate + estimation can be made, this object should contain + the nominal bandwidth." + ::= { ifEntry 5 } + + ifPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interface's address at the protocol layer + immediately `below' the network layer in the + protocol stack. For interfaces which do not have + such an address (e.g., a serial line), this object + should contain an octet string of zero length." + ::= { ifEntry 6 } + + ifAdminStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3) -- in some test mode + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The desired state of the interface. The + testing(3) state indicates that no operational + packets can be passed." + ::= { ifEntry 7 } + + ifOperStatus OBJECT-TYPE + SYNTAX INTEGER { + up(1), -- ready to pass packets + down(2), + testing(3) -- in some test mode + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The current operational state of the interface. + The testing(3) state indicates that no operational + packets can be passed." + ::= { ifEntry 8 } + + ifLastChange OBJECT-TYPE + SYNTAX TimeTicks + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The value of sysUpTime at the time the interface + entered its current operational state. If the + current state was entered prior to the last re- + initialization of the local network management + subsystem, then this object contains a zero + value." + ::= { ifEntry 9 } + + ifInOctets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of octets received on the + interface, including framing characters." + ::= { ifEntry 10 } + + ifInUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of subnetwork-unicast packets + delivered to a higher-layer protocol." + ::= { ifEntry 11 } + + ifInNUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of non-unicast (i.e., subnetwork- + broadcast or subnetwork-multicast) packets + delivered to a higher-layer protocol." + ::= { ifEntry 12 } + + ifInDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of inbound packets which were chosen + to be discarded even though no errors had been + detected to prevent their being deliverable to a + higher-layer protocol. One possible reason for + discarding such a packet could be to free up + buffer space." + ::= { ifEntry 13 } + + ifInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of inbound packets that contained + errors preventing them from being deliverable to a + higher-layer protocol." + ::= { ifEntry 14 } + + + ifInUnknownProtos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of packets received via the interface + which were discarded because of an unknown or + unsupported protocol." + ::= { ifEntry 15 } + + ifOutOctets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of octets transmitted out of the + interface, including framing characters." + ::= { ifEntry 16 } + + ifOutUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of packets that higher-level + protocols requested be transmitted to a + subnetwork-unicast address, including those that + were discarded or not sent." + ::= { ifEntry 17 } + + ifOutNUcastPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of packets that higher-level + protocols requested be transmitted to a non- + unicast (i.e., a subnetwork-broadcast or + subnetwork-multicast) address, including those + that were discarded or not sent." + ::= { ifEntry 18 } + + ifOutDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of outbound packets which were chosen + to be discarded even though no errors had been + detected to prevent their being transmitted. One + possible reason for discarding such a packet could + be to free up buffer space." + ::= { ifEntry 19 } + + ifOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of outbound packets that could not be + transmitted because of errors." + ::= { ifEntry 20 } + + ifOutQLen OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The length of the output packet queue (in + packets)." + ::= { ifEntry 21 } + + ifSpecific OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A reference to MIB definitions specific to the + particular media being used to realize the + interface. For example, if the interface is + realized by an ethernet, then the value of this + object refers to a document defining objects + specific to ethernet. If this information is not + present, its value should be set to the OBJECT + IDENTIFIER { 0 0 }, which is a syntatically valid + object identifier, and any conformant + implementation of ASN.1 and BER must be able to + generate and recognize this value." + ::= { ifEntry 22 } + + + -- the Address Translation group + + -- Implementation of the Address Translation group is + -- mandatory for all systems. Note however that this group + -- is deprecated by MIB-II. That is, it is being included + -- solely for compatibility with MIB-I nodes, and will most + -- likely be excluded from MIB-III nodes. From MIB-II and + -- onwards, each network protocol group contains its own + -- address translation tables. + + -- The Address Translation group contains one table which is + -- the union across all interfaces of the translation tables + -- for converting a NetworkAddress (e.g., an IP address) into + -- a subnetwork-specific address. For lack of a better term, + -- this document refers to such a subnetwork-specific address + -- as a `physical' address. + + -- Examples of such translation tables are: for broadcast + -- media where ARP is in use, the translation table is + -- equivalent to the ARP cache; or, on an X.25 network where + -- non-algorithmic translation to X.121 addresses is + -- required, the translation table contains the + -- NetworkAddress to X.121 address equivalences. + + atTable OBJECT-TYPE + SYNTAX SEQUENCE OF AtEntry + ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "The Address Translation tables contain the + NetworkAddress to `physical' address equivalences. + Some interfaces do not use translation tables for + determining address equivalences (e.g., DDN-X.25 + has an algorithmic method); if all interfaces are + of this type, then the Address Translation table + is empty, i.e., has zero entries." + ::= { at 1 } + + atEntry OBJECT-TYPE + SYNTAX AtEntry + ACCESS not-accessible + STATUS deprecated + DESCRIPTION + "Each entry contains one NetworkAddress to + `physical' address equivalence." + INDEX { atIfIndex, + atNetAddress } + ::= { atTable 1 } + + AtEntry ::= + SEQUENCE { + atIfIndex + INTEGER, + atPhysAddress + PhysAddress, + atNetAddress + NetworkAddress + } + + atIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The interface on which this entry's equivalence + is effective. The interface identified by a + particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { atEntry 1 } + + atPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The media-dependent `physical' address. + + Setting this object to a null string (one of zero + length) has the effect of invaliding the + corresponding entry in the atTable object. That + is, it effectively dissasociates the interface + identified with said entry from the mapping + identified with said entry. It is an + implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared + to receive tabular information from agents that + corresponds to entries not currently in use. + Proper interpretation of such entries requires + examination of the relevant atPhysAddress object." + ::= { atEntry 2 } + + atNetAddress OBJECT-TYPE + SYNTAX NetworkAddress + ACCESS read-write + STATUS deprecated + DESCRIPTION + "The NetworkAddress (e.g., the IP address) + corresponding to the media-dependent `physical' + address." + ::= { atEntry 3 } + + + -- the IP group + + -- Implementation of the IP group is mandatory for all + -- systems. + + ipForwarding OBJECT-TYPE + SYNTAX INTEGER { + forwarding(1), -- acting as a gateway + not-forwarding(2) -- NOT acting as a gateway + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The indication of whether this entity is acting + as an IP gateway in respect to the forwarding of + datagrams received by, but not addressed to, this + entity. IP gateways forward datagrams. IP hosts + do not (except those source-routed via the host). + + Note that for some managed nodes, this object may + take on only a subset of the values possible. + Accordingly, it is appropriate for an agent to + return a `badValue' response if a management + station attempts to change this object to an + inappropriate value." + ::= { ip 1 } + + ipDefaultTTL OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The default value inserted into the Time-To-Live + field of the IP header of datagrams originated at + this entity, whenever a TTL value is not supplied + by the transport layer protocol." + ::= { ip 2 } + + ipInReceives OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of input datagrams received from + interfaces, including those received in error." + ::= { ip 3 } + + ipInHdrErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams discarded due to + errors in their IP headers, including bad + checksums, version number mismatch, other format + errors, time-to-live exceeded, errors discovered + in processing their IP options, etc." + ::= { ip 4 } + + ipInAddrErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams discarded because + the IP address in their IP header's destination + field was not a valid address to be received at + this entity. This count includes invalid + addresses (e.g., 0.0.0.0) and addresses of + unsupported Classes (e.g., Class E). For entities + which are not IP Gateways and therefore do not + forward datagrams, this counter includes datagrams + discarded because the destination address was not + a local address." + ::= { ip 5 } + + ipForwDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input datagrams for which this + entity was not their final IP destination, as a + result of which an attempt was made to find a + route to forward them to that final destination. + In entities which do not act as IP Gateways, this + counter will include only those packets which were + Source-Routed via this entity, and the Source- + Route option processing was successful." + ::= { ip 6 } + + ipInUnknownProtos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally-addressed datagrams + received successfully but discarded because of an + unknown or unsupported protocol." + ::= { ip 7 } + + ipInDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of input IP datagrams for which no + problems were encountered to prevent their + continued processing, but which were discarded + (e.g., for lack of buffer space). Note that this + counter does not include any datagrams discarded + while awaiting re-assembly." + ::= { ip 8 } + + ipInDelivers OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of input datagrams successfully + delivered to IP user-protocols (including ICMP)." + ::= { ip 9 } + + ipOutRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of IP datagrams which local IP + user-protocols (including ICMP) supplied to IP in + requests for transmission. Note that this counter + does not include any datagrams counted in + ipForwDatagrams." + ::= { ip 10 } + + ipOutDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of output IP datagrams for which no + problem was encountered to prevent their + transmission to their destination, but which were + discarded (e.g., for lack of buffer space). Note + that this counter would include datagrams counted + in ipForwDatagrams if any such packets met this + (discretionary) discard criterion." + ::= { ip 11 } + + ipOutNoRoutes OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams discarded because no + route could be found to transmit them to their + destination. Note that this counter includes any + packets counted in ipForwDatagrams which meet this + `no-route' criterion. Note that this includes any + datagarms which a host cannot route because all of + its default gateways are down." + ::= { ip 12 } + + ipReasmTimeout OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The maximum number of seconds which received + fragments are held while they are awaiting + reassembly at this entity." + ::= { ip 13 } + + ipReasmReqds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP fragments received which needed + to be reassembled at this entity." + ::= { ip 14 } + + ipReasmOKs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams successfully re- + assembled." + ::= { ip 15 } + + ipReasmFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of failures detected by the IP re- + assembly algorithm (for whatever reason: timed + out, errors, etc). Note that this is not + necessarily a count of discarded IP fragments + since some algorithms (notably the algorithm in + RFC 815) can lose track of the number of fragments + by combining them as they are received." + ::= { ip 16 } + + ipFragOKs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams that have been + successfully fragmented at this entity." + ::= { ip 17 } + + ipFragFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagrams that have been + discarded because they needed to be fragmented at + this entity but could not be, e.g., because their + Don't Fragment flag was set." + ::= { ip 18 } + + ipFragCreates OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of IP datagram fragments that have + been generated as a result of fragmentation at + this entity." + ::= { ip 19 } + + + + -- the IP address table + + -- The IP address table contains this entity's IP addressing + -- information. + + ipAddrTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpAddrEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The table of addressing information relevant to + this entity's IP addresses." + ::= { ip 20 } + + ipAddrEntry OBJECT-TYPE + SYNTAX IpAddrEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The addressing information for one of this + entity's IP addresses." + INDEX { ipAdEntAddr } + ::= { ipAddrTable 1 } + + IpAddrEntry ::= + SEQUENCE { + ipAdEntAddr + IpAddress, + ipAdEntIfIndex + INTEGER, + ipAdEntNetMask + IpAddress, + ipAdEntBcastAddr + INTEGER, + ipAdEntReasmMaxSize + INTEGER (0..65535) + } + + ipAdEntAddr OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The IP address to which this entry's addressing + information pertains." + ::= { ipAddrEntry 1 } + + + ipAdEntIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The index value which uniquely identifies the + interface to which this entry is applicable. The + interface identified by a particular value of this + index is the same interface as identified by the + same value of ifIndex." + ::= { ipAddrEntry 2 } + + ipAdEntNetMask OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The subnet mask associated with the IP address of + this entry. The value of the mask is an IP + address with all the network bits set to 1 and all + the hosts bits set to 0." + ::= { ipAddrEntry 3 } + + ipAdEntBcastAddr OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The value of the least-significant bit in the IP + broadcast address used for sending datagrams on + the (logical) interface associated with the IP + address of this entry. For example, when the + Internet standard all-ones broadcast address is + used, the value will be 1. This value applies to + both the subnet and network broadcasts addresses + used by the entity on this (logical) interface." + ::= { ipAddrEntry 4 } + + ipAdEntReasmMaxSize OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The size of the largest IP datagram which this + entity can re-assemble from incoming IP fragmented + datagrams received on this interface." + ::= { ipAddrEntry 5 } + + + + -- the IP routing table + + -- The IP routing table contains an entry for each route + -- presently known to this entity. + + ipRouteTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpRouteEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "This entity's IP Routing table." + ::= { ip 21 } + + ipRouteEntry OBJECT-TYPE + SYNTAX IpRouteEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A route to a particular destination." + INDEX { ipRouteDest } + ::= { ipRouteTable 1 } + + IpRouteEntry ::= + SEQUENCE { + ipRouteDest + IpAddress, + ipRouteIfIndex + INTEGER, + ipRouteMetric1 + INTEGER, + ipRouteMetric2 + INTEGER, + ipRouteMetric3 + INTEGER, + ipRouteMetric4 + INTEGER, + ipRouteNextHop + IpAddress, + ipRouteType + INTEGER, + ipRouteProto + INTEGER, + ipRouteAge + INTEGER, + ipRouteMask + IpAddress, + ipRouteMetric5 + INTEGER, + ipRouteInfo + OBJECT IDENTIFIER + } + + ipRouteDest OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The destination IP address of this route. An + entry with a value of 0.0.0.0 is considered a + default route. Multiple routes to a single + destination can appear in the table, but access to + such multiple entries is dependent on the table- + access mechanisms defined by the network + management protocol in use." + ::= { ipRouteEntry 1 } + + ipRouteIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The index value which uniquely identifies the + local interface through which the next hop of this + route should be reached. The interface identified + by a particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { ipRouteEntry 2 } + + ipRouteMetric1 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The primary routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 3 } + + ipRouteMetric2 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 4 } + + ipRouteMetric3 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 5 } + + ipRouteMetric4 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 6 } + + ipRouteNextHop OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The IP address of the next hop of this route. + (In the case of a route bound to an interface + which is realized via a broadcast media, the value + of this field is the agent's IP address on that + interface.)" + ::= { ipRouteEntry 7 } + + ipRouteType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + invalid(2), -- an invalidated route + -- route to directly + direct(3), -- connected (sub-)network + + -- route to a non-local + indirect(4) -- host/network/sub-network + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of route. Note that the values + direct(3) and indirect(4) refer to the notion of + direct and indirect routing in the IP + architecture. + + Setting this object to the value invalid(2) has + the effect of invalidating the corresponding entry + in the ipRouteTable object. That is, it + effectively dissasociates the destination + identified with said entry from the route + identified with said entry. It is an + implementation-specific matter as to whether the + agent removes an invalidated entry from the table. + Accordingly, management stations must be prepared + to receive tabular information from agents that + corresponds to entries not currently in use. + Proper interpretation of such entries requires + examination of the relevant ipRouteType object." + ::= { ipRouteEntry 8 } + + ipRouteProto OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + -- non-protocol information, + -- e.g., manually configured + local(2), -- entries + + -- set via a network + netmgmt(3), -- management protocol + + -- obtained via ICMP, + icmp(4), -- e.g., Redirect + + -- the remaining values are + -- all gateway routing + -- protocols + egp(5), + ggp(6), + hello(7), + rip(8), + is-is(9), + es-is(10), + ciscoIgrp(11), + bbnSpfIgp(12), + ospf(13), + bgp(14) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The routing mechanism via which this route was + learned. Inclusion of values for gateway routing + protocols is not intended to imply that hosts + should support those protocols." + ::= { ipRouteEntry 9 } + + ipRouteAge OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The number of seconds since this route was last + updated or otherwise determined to be correct. + Note that no semantics of `too old' can be implied + except through knowledge of the routing protocol + by which the route was learned." + ::= { ipRouteEntry 10 } + + ipRouteMask OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Indicate the mask to be logical-ANDed with the + destination address before being compared to the + value in the ipRouteDest field. For those systems + that do not support arbitrary subnet masks, an + agent constructs the value of the ipRouteMask by + determining whether the value of the correspondent + ipRouteDest field belong to a class-A, B, or C + network, and then using one of: + + mask network + 255.0.0.0 class-A + 255.255.0.0 class-B + 255.255.255.0 class-C + + If the value of the ipRouteDest is 0.0.0.0 (a + default route), then the mask value is also + 0.0.0.0. It should be noted that all IP routing + subsystems implicitly use this mechanism." + ::= { ipRouteEntry 11 } + + ipRouteMetric5 OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An alternate routing metric for this route. The + semantics of this metric are determined by the + routing-protocol specified in the route's + ipRouteProto value. If this metric is not used, + its value should be set to -1." + ::= { ipRouteEntry 12 } + + ipRouteInfo OBJECT-TYPE + SYNTAX OBJECT IDENTIFIER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A reference to MIB definitions specific to the + particular routing protocol which is responsible + for this route, as determined by the value + specified in the route's ipRouteProto value. If + this information is not present, its value should + be set to the OBJECT IDENTIFIER { 0 0 }, which is + a syntatically valid object identifier, and any + conformant implementation of ASN.1 and BER must be + able to generate and recognize this value." + ::= { ipRouteEntry 13 } + + + -- the IP Address Translation table + + -- The IP address translation table contain the IpAddress to + -- `physical' address equivalences. Some interfaces do not + -- use translation tables for determining address + -- equivalences (e.g., DDN-X.25 has an algorithmic method); + -- if all interfaces are of this type, then the Address + -- Translation table is empty, i.e., has zero entries. + + ipNetToMediaTable OBJECT-TYPE + SYNTAX SEQUENCE OF IpNetToMediaEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The IP Address Translation table used for mapping + from IP addresses to physical addresses." + ::= { ip 22 } + + ipNetToMediaEntry OBJECT-TYPE + SYNTAX IpNetToMediaEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Each entry contains one IpAddress to `physical' + address equivalence." + INDEX { ipNetToMediaIfIndex, + ipNetToMediaNetAddress } + ::= { ipNetToMediaTable 1 } + + IpNetToMediaEntry ::= + SEQUENCE { + ipNetToMediaIfIndex + INTEGER, + ipNetToMediaPhysAddress + PhysAddress, + ipNetToMediaNetAddress + IpAddress, + ipNetToMediaType + INTEGER + } + + ipNetToMediaIfIndex OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The interface on which this entry's equivalence + is effective. The interface identified by a + particular value of this index is the same + interface as identified by the same value of + ifIndex." + ::= { ipNetToMediaEntry 1 } + + ipNetToMediaPhysAddress OBJECT-TYPE + SYNTAX PhysAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The media-dependent `physical' address." + ::= { ipNetToMediaEntry 2 } + + + + ipNetToMediaNetAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The IpAddress corresponding to the media- + dependent `physical' address." + ::= { ipNetToMediaEntry 3 } + + ipNetToMediaType OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + invalid(2), -- an invalidated mapping + dynamic(3), + static(4) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The type of mapping. + + Setting this object to the value invalid(2) has + the effect of invalidating the corresponding entry + in the ipNetToMediaTable. That is, it effectively + dissasociates the interface identified with said + entry from the mapping identified with said entry. + It is an implementation-specific matter as to + whether the agent removes an invalidated entry + from the table. Accordingly, management stations + must be prepared to receive tabular information + from agents that corresponds to entries not + currently in use. Proper interpretation of such + entries requires examination of the relevant + ipNetToMediaType object." + ::= { ipNetToMediaEntry 4 } + + + -- additional IP objects + + ipRoutingDiscards OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of routing entries which were chosen + to be discarded even though they are valid. One + possible reason for discarding such an entry could + be to free-up buffer space for other routing + entries." + ::= { ip 23 } + + + -- the ICMP group + + -- Implementation of the ICMP group is mandatory for all + -- systems. + + icmpInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ICMP messages which the + entity received. Note that this counter includes + all those counted by icmpInErrors." + ::= { icmp 1 } + + icmpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP messages which the entity + received but determined as having ICMP-specific + errors (bad ICMP checksums, bad length, etc.)." + ::= { icmp 2 } + + icmpInDestUnreachs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Destination Unreachable + messages received." + ::= { icmp 3 } + + icmpInTimeExcds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Time Exceeded messages + received." + ::= { icmp 4 } + + + icmpInParmProbs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Parameter Problem messages + received." + ::= { icmp 5 } + + icmpInSrcQuenchs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Source Quench messages + received." + ::= { icmp 6 } + + icmpInRedirects OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Redirect messages received." + ::= { icmp 7 } + + icmpInEchos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo (request) messages + received." + ::= { icmp 8 } + + icmpInEchoReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo Reply messages received." + ::= { icmp 9 } + + icmpInTimestamps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp (request) messages + received." + ::= { icmp 10 } + + icmpInTimestampReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp Reply messages + received." + ::= { icmp 11 } + + icmpInAddrMasks OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Request messages + received." + ::= { icmp 12 } + + icmpInAddrMaskReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Reply messages + received." + ::= { icmp 13 } + + icmpOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ICMP messages which this + entity attempted to send. Note that this counter + includes all those counted by icmpOutErrors." + ::= { icmp 14 } + + icmpOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP messages which this entity did + not send due to problems discovered within ICMP + such as a lack of buffers. This value should not + include errors discovered outside the ICMP layer + such as the inability of IP to route the resultant + datagram. In some implementations there may be no + types of error which contribute to this counter's + value." + ::= { icmp 15 } + + icmpOutDestUnreachs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Destination Unreachable + messages sent." + ::= { icmp 16 } + + icmpOutTimeExcds OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Time Exceeded messages sent." + ::= { icmp 17 } + + icmpOutParmProbs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Parameter Problem messages + sent." + ::= { icmp 18 } + + icmpOutSrcQuenchs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Source Quench messages sent." + ::= { icmp 19 } + + icmpOutRedirects OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Redirect messages sent. For a + host, this object will always be zero, since hosts + do not send redirects." + ::= { icmp 20 } + + icmpOutEchos OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo (request) messages sent." + ::= { icmp 21 } + + icmpOutEchoReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Echo Reply messages sent." + ::= { icmp 22 } + + icmpOutTimestamps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp (request) messages + sent." + ::= { icmp 23 } + + icmpOutTimestampReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Timestamp Reply messages + sent." + ::= { icmp 24 } + + icmpOutAddrMasks OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Request messages + sent." + ::= { icmp 25 } + + + icmpOutAddrMaskReps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of ICMP Address Mask Reply messages + sent." + ::= { icmp 26 } + + + -- the TCP group + + -- Implementation of the TCP group is mandatory for all + -- systems that implement the TCP. + + -- Note that instances of object types that represent + -- information about a particular TCP connection are + -- transient; they persist only as long as the connection + -- in question. + + tcpRtoAlgorithm OBJECT-TYPE + SYNTAX INTEGER { + other(1), -- none of the following + + constant(2), -- a constant rto + rsre(3), -- MIL-STD-1778, Appendix B + vanj(4) -- Van Jacobson's algorithm [10] + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The algorithm used to determine the timeout value + used for retransmitting unacknowledged octets." + ::= { tcp 1 } + + tcpRtoMin OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The minimum value permitted by a TCP + implementation for the retransmission timeout, + measured in milliseconds. More refined semantics + for objects of this type depend upon the algorithm + used to determine the retransmission timeout. In + particular, when the timeout algorithm is rsre(3), + an object of this type has the semantics of the + LBOUND quantity described in RFC 793." + ::= { tcp 2 } + + + tcpRtoMax OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The maximum value permitted by a TCP + implementation for the retransmission timeout, + measured in milliseconds. More refined semantics + for objects of this type depend upon the algorithm + used to determine the retransmission timeout. In + particular, when the timeout algorithm is rsre(3), + an object of this type has the semantics of the + UBOUND quantity described in RFC 793." + ::= { tcp 3 } + + tcpMaxConn OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The limit on the total number of TCP connections + the entity can support. In entities where the + maximum number of connections is dynamic, this + object should contain the value -1." + ::= { tcp 4 } + + tcpActiveOpens OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the SYN-SENT state from the + CLOSED state." + ::= { tcp 5 } + + tcpPassiveOpens OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the SYN-RCVD state from the + LISTEN state." + ::= { tcp 6 } + + + + tcpAttemptFails OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the CLOSED state from either + the SYN-SENT state or the SYN-RCVD state, plus the + number of times TCP connections have made a direct + transition to the LISTEN state from the SYN-RCVD + state." + ::= { tcp 7 } + + tcpEstabResets OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of times TCP connections have made a + direct transition to the CLOSED state from either + the ESTABLISHED state or the CLOSE-WAIT state." + ::= { tcp 8 } + + tcpCurrEstab OBJECT-TYPE + SYNTAX Gauge + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of TCP connections for which the + current state is either ESTABLISHED or CLOSE- + WAIT." + ::= { tcp 9 } + + tcpInSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments received, including + those received in error. This count includes + segments received on currently established + connections." + ::= { tcp 10 } + + tcpOutSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments sent, including + those on current connections but excluding those + containing only retransmitted octets." + ::= { tcp 11 } + + tcpRetransSegs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments retransmitted - that + is, the number of TCP segments transmitted + containing one or more previously transmitted + octets." + ::= { tcp 12 } + + + -- the TCP Connection table + + -- The TCP connection table contains information about this + -- entity's existing TCP connections. + + tcpConnTable OBJECT-TYPE + SYNTAX SEQUENCE OF TcpConnEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table containing TCP connection-specific + information." + ::= { tcp 13 } + + tcpConnEntry OBJECT-TYPE + SYNTAX TcpConnEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about a particular current TCP + connection. An object of this type is transient, + in that it ceases to exist when (or soon after) + the connection makes the transition to the CLOSED + state." + INDEX { tcpConnLocalAddress, + tcpConnLocalPort, + tcpConnRemAddress, + tcpConnRemPort } + ::= { tcpConnTable 1 } + + + TcpConnEntry ::= + SEQUENCE { + tcpConnState + INTEGER, + tcpConnLocalAddress + IpAddress, + tcpConnLocalPort + INTEGER (0..65535), + tcpConnRemAddress + IpAddress, + tcpConnRemPort + INTEGER (0..65535) + } + + tcpConnState OBJECT-TYPE + SYNTAX INTEGER { + closed(1), + listen(2), + synSent(3), + synReceived(4), + established(5), + finWait1(6), + finWait2(7), + closeWait(8), + lastAck(9), + closing(10), + timeWait(11), + deleteTCB(12) + } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "The state of this TCP connection. + + The only value which may be set by a management + station is deleteTCB(12). Accordingly, it is + appropriate for an agent to return a `badValue' + response if a management station attempts to set + this object to any other value. + + If a management station sets this object to the + value deleteTCB(12), then this has the effect of + deleting the TCB (as defined in RFC 793) of the + corresponding connection on the managed node, + resulting in immediate termination of the + connection. + + As an implementation-specific option, a RST + segment may be sent from the managed node to the + other TCP endpoint (note however that RST segments + are not sent reliably)." + ::= { tcpConnEntry 1 } + + tcpConnLocalAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local IP address for this TCP connection. In + the case of a connection in the listen state which + is willing to accept connections for any IP + interface associated with the node, the value + 0.0.0.0 is used." + ::= { tcpConnEntry 2 } + + tcpConnLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local port number for this TCP connection." + ::= { tcpConnEntry 3 } + + tcpConnRemAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The remote IP address for this TCP connection." + ::= { tcpConnEntry 4 } + + tcpConnRemPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The remote port number for this TCP connection." + ::= { tcpConnEntry 5 } + + + -- additional TCP objects + + tcpInErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of segments received in error + (e.g., bad TCP checksums)." + ::= { tcp 14 } + + tcpOutRsts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of TCP segments sent containing the + RST flag." + ::= { tcp 15 } + + + -- the UDP group + + -- Implementation of the UDP group is mandatory for all + -- systems which implement the UDP. + + udpInDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of UDP datagrams delivered to + UDP users." + ::= { udp 1 } + + udpNoPorts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of received UDP datagrams for + which there was no application at the destination + port." + ::= { udp 2 } + + udpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of received UDP datagrams that could + not be delivered for reasons other than the lack + of an application at the destination port." + ::= { udp 3 } + + + udpOutDatagrams OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of UDP datagrams sent from this + entity." + ::= { udp 4 } + + + -- the UDP Listener table + + -- The UDP listener table contains information about this + -- entity's UDP end-points on which a local application is + -- currently accepting datagrams. + + udpTable OBJECT-TYPE + SYNTAX SEQUENCE OF UdpEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "A table containing UDP listener information." + ::= { udp 5 } + + udpEntry OBJECT-TYPE + SYNTAX UdpEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about a particular current UDP + listener." + INDEX { udpLocalAddress, udpLocalPort } + ::= { udpTable 1 } + + UdpEntry ::= + SEQUENCE { + udpLocalAddress + IpAddress, + udpLocalPort + INTEGER (0..65535) + } + + udpLocalAddress OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local IP address for this UDP listener. In + the case of a UDP listener which is willing to + accept datagrams for any IP interface associated + with the node, the value 0.0.0.0 is used." + ::= { udpEntry 1 } + + udpLocalPort OBJECT-TYPE + SYNTAX INTEGER (0..65535) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The local port number for this UDP listener." + ::= { udpEntry 2 } + + + -- the EGP group + + -- Implementation of the EGP group is mandatory for all + -- systems which implement the EGP. + + egpInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received without + error." + ::= { egp 1 } + + egpInErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received that proved + to be in error." + ::= { egp 2 } + + egpOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of locally generated EGP + messages." + ::= { egp 3 } + + egpOutErrors OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages not + sent due to resource limitations within an EGP + entity." + ::= { egp 4 } + + + -- the EGP Neighbor table + + -- The EGP neighbor table contains information about this + -- entity's EGP neighbors. + + egpNeighTable OBJECT-TYPE + SYNTAX SEQUENCE OF EgpNeighEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "The EGP neighbor table." + ::= { egp 5 } + + egpNeighEntry OBJECT-TYPE + SYNTAX EgpNeighEntry + ACCESS not-accessible + STATUS mandatory + DESCRIPTION + "Information about this entity's relationship with + a particular EGP neighbor." + INDEX { egpNeighAddr } + ::= { egpNeighTable 1 } + + EgpNeighEntry ::= + SEQUENCE { + egpNeighState + INTEGER, + egpNeighAddr + IpAddress, + egpNeighAs + INTEGER, + egpNeighInMsgs + Counter, + egpNeighInErrs + Counter, + egpNeighOutMsgs + Counter, + egpNeighOutErrs + Counter, + egpNeighInErrMsgs + Counter, + egpNeighOutErrMsgs + Counter, + egpNeighStateUps + Counter, + egpNeighStateDowns + Counter, + egpNeighIntervalHello + INTEGER, + egpNeighIntervalPoll + INTEGER, + egpNeighMode + INTEGER, + egpNeighEventTrigger + INTEGER + } + + egpNeighState OBJECT-TYPE + SYNTAX INTEGER { + idle(1), + acquisition(2), + down(3), + up(4), + cease(5) + } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The EGP state of the local system with respect to + this entry's EGP neighbor. Each EGP state is + represented by a value that is one greater than + the numerical value associated with said state in + RFC 904." + ::= { egpNeighEntry 1 } + + egpNeighAddr OBJECT-TYPE + SYNTAX IpAddress + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The IP address of this entry's EGP neighbor." + ::= { egpNeighEntry 2 } + + egpNeighAs OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The autonomous system of this EGP peer. Zero + should be specified if the autonomous system + number of the neighbor is not yet known." + ::= { egpNeighEntry 3 } + + egpNeighInMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received without error + from this EGP peer." + ::= { egpNeighEntry 4 } + + egpNeighInErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP messages received from this EGP + peer that proved to be in error (e.g., bad EGP + checksum)." + ::= { egpNeighEntry 5 } + + egpNeighOutMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages to + this EGP peer." + ::= { egpNeighEntry 6 } + + egpNeighOutErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of locally generated EGP messages not + sent to this EGP peer due to resource limitations + within an EGP entity." + ::= { egpNeighEntry 7 } + + egpNeighInErrMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP-defined error messages received + from this EGP peer." + ::= { egpNeighEntry 8 } + + egpNeighOutErrMsgs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP-defined error messages sent to + this EGP peer." + ::= { egpNeighEntry 9 } + + egpNeighStateUps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP state transitions to the UP + state with this EGP peer." + ::= { egpNeighEntry 10 } + + egpNeighStateDowns OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The number of EGP state transitions from the UP + state to any other state with this EGP peer." + ::= { egpNeighEntry 11 } + + egpNeighIntervalHello OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interval between EGP Hello command + retransmissions (in hundredths of a second). This + represents the t1 timer as defined in RFC 904." + ::= { egpNeighEntry 12 } + + egpNeighIntervalPoll OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The interval between EGP poll command + retransmissions (in hundredths of a second). This + represents the t3 timer as defined in RFC 904." + ::= { egpNeighEntry 13 } + + egpNeighMode OBJECT-TYPE + SYNTAX INTEGER { active(1), passive(2) } + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The polling mode of this EGP entity, either + passive or active." + ::= { egpNeighEntry 14 } + + egpNeighEventTrigger OBJECT-TYPE + SYNTAX INTEGER { start(1), stop(2) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A control variable used to trigger operator- + initiated Start and Stop events. When read, this + variable always returns the most recent value that + egpNeighEventTrigger was set to. If it has not + been set since the last initialization of the + network management subsystem on the node, it + returns a value of `stop'. + + When set, this variable causes a Start or Stop + event on the specified neighbor, as specified on + pages 8-10 of RFC 904. Briefly, a Start event + causes an Idle peer to begin neighbor acquisition + and a non-Idle peer to reinitiate neighbor + acquisition. A stop event causes a non-Idle peer + to return to the Idle state until a Start event + occurs, either via egpNeighEventTrigger or + otherwise." + ::= { egpNeighEntry 15 } + + + -- additional EGP objects + + egpAs OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The autonomous system number of this EGP entity." + ::= { egp 6 } + + + + -- the Transmission group + + -- Based on the transmission media underlying each interface + -- on a system, the corresponding portion of the Transmission + -- group is mandatory for that system. + + -- When Internet-standard definitions for managing + -- transmission media are defined, the transmission group is + -- used to provide a prefix for the names of those objects. + + -- Typically, such definitions reside in the experimental + -- portion of the MIB until they are "proven", then as a + -- part of the Internet standardization process, the + -- definitions are accordingly elevated and a new object + -- identifier, under the transmission group is defined. By + -- convention, the name assigned is: + -- + -- type OBJECT IDENTIFIER ::= { transmission number } + -- + -- where "type" is the symbolic value used for the media in + -- the ifType column of the ifTable object, and "number" is + -- the actual integer value corresponding to the symbol. + + + -- the SNMP group + + -- Implementation of the SNMP group is mandatory for all + -- systems which support an SNMP protocol entity. Some of + -- the objects defined below will be zero-valued in those + -- SNMP implementations that are optimized to support only + -- those functions specific to either a management agent or + -- a management station. In particular, it should be + -- observed that the objects below refer to an SNMP entity, + -- and there may be several SNMP entities residing on a + -- managed node (e.g., if the node is hosting acting as + -- a management station). + + snmpInPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of Messages delivered to the + SNMP entity from the transport service." + ::= { snmp 1 } + + snmpOutPkts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages which were + passed from the SNMP protocol entity to the + transport service." + ::= { snmp 2 } + + snmpInBadVersions OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages which were + delivered to the SNMP protocol entity and were for + an unsupported SNMP version." + ::= { snmp 3 } + + snmpInBadCommunityNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages delivered to + the SNMP protocol entity which used a SNMP + community name not known to said entity." + ::= { snmp 4 } + + snmpInBadCommunityUses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Messages delivered to + the SNMP protocol entity which represented an SNMP + operation which was not allowed by the SNMP + community named in the Message." + ::= { snmp 5 } + + snmpInASNParseErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of ASN.1 or BER errors + encountered by the SNMP protocol entity when + decoding received SNMP Messages." + ::= { snmp 6 } + + + -- { snmp 7 } is not used + + snmpInTooBigs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `tooBig'." + ::= { snmp 8 } + + snmpInNoSuchNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `noSuchName'." + ::= { snmp 9 } + + snmpInBadValues OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `badValue'." + ::= { snmp 10 } + + snmpInReadOnlys OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number valid SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `readOnly'. It should be noted that it is a + protocol error to generate an SNMP PDU which + contains the value `readOnly' in the error-status + field, as such this object is provided as a means + of detecting incorrect implementations of the + SNMP." + ::= { snmp 11 } + + snmpInGenErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + delivered to the SNMP protocol entity and for + which the value of the error-status field is + `genErr'." + ::= { snmp 12 } + + snmpInTotalReqVars OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of MIB objects which have been + retrieved successfully by the SNMP protocol entity + as the result of receiving valid SNMP Get-Request + and Get-Next PDUs." + ::= { snmp 13 } + + snmpInTotalSetVars OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of MIB objects which have been + altered successfully by the SNMP protocol entity + as the result of receiving valid SNMP Set-Request + PDUs." + ::= { snmp 14 } + + snmpInGetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 15 } + + snmpInGetNexts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been accepted and processed by the SNMP protocol + entity." + ::= { snmp 16 } + + snmpInSetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 17 } + + snmpInGetResponses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been accepted and processed by the SNMP + protocol entity." + ::= { snmp 18 } + + snmpInTraps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been accepted and processed by the SNMP protocol + entity." + ::= { snmp 19 } + + snmpOutTooBigs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `tooBig.'" + ::= { snmp 20 } + + + snmpOutNoSuchNames OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status is + `noSuchName'." + ::= { snmp 21 } + + snmpOutBadValues OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `badValue'." + ::= { snmp 22 } + + -- { snmp 23 } is not used + + snmpOutGenErrs OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP PDUs which were + generated by the SNMP protocol entity and for + which the value of the error-status field is + `genErr'." + ::= { snmp 24 } + + snmpOutGetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 25 } + + snmpOutGetNexts OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Next PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 26 } + + snmpOutSetRequests OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Set-Request PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 27 } + + snmpOutGetResponses OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Get-Response PDUs which + have been generated by the SNMP protocol entity." + ::= { snmp 28 } + + snmpOutTraps OBJECT-TYPE + SYNTAX Counter + ACCESS read-only + STATUS mandatory + DESCRIPTION + "The total number of SNMP Trap PDUs which have + been generated by the SNMP protocol entity." + ::= { snmp 29 } + + snmpEnableAuthenTraps OBJECT-TYPE + SYNTAX INTEGER { enabled(1), disabled(2) } + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Indicates whether the SNMP agent process is + permitted to generate authentication-failure + traps. The value of this object overrides any + configuration information; as such, it provides a + means whereby all authentication-failure traps may + be disabled. + + Note that it is strongly recommended that this + object be stored in non-volatile memory so that it + remains constant between re-initializations of the + network management system." + ::= { snmp 30 } + +END diff --git a/results/README b/boardfarm/results/README similarity index 100% rename from results/README rename to boardfarm/results/README diff --git a/boardfarm/tests/__init__.py b/boardfarm/tests/__init__.py new file mode 100644 index 00000000..dcad9619 --- /dev/null +++ b/boardfarm/tests/__init__.py @@ -0,0 +1,58 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +import lib + +# Import from every file +import os +import glob +import unittest2 +import inspect +import sys + +test_files = glob.glob(os.path.dirname(__file__) + "/*.py") +if 'BFT_OVERLAY' in os.environ: + for overlay in os.environ['BFT_OVERLAY'].split(' '): + overlay = os.path.abspath(overlay) + sys.path.insert(0, overlay + '/tests') + test_files += glob.glob(overlay + '/tests/*.py') + + sys.path.insert(0, os.getcwd() + '/tests') + +test_mappings = {} +for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): + if x == "tests": + raise Exception("INVALID test file name found, tests.py will cause namespace issues, please rename") + try: + exec("import %s as test_file" % x) + test_mappings[test_file] = [] + for obj in dir(test_file): + ref = getattr(test_file, obj) + if inspect.isclass(ref) and issubclass(ref, unittest2.TestCase): + test_mappings[test_file].append(ref) + exec("from %s import %s" % (x, obj)) + except Exception as e: + if 'BFT_DEBUG' in os.environ: + import traceback + traceback.print_exc() + print("Warning: could not import from file %s. Run with BFT_DEBUG=y for more details" % x) + +def init(config): + for test_file, tests in test_mappings.iteritems(): + for test in tests: + #print('checking %s in %s' % (test, test_file)) + if hasattr(test, "parse"): + try: + #print("calling parse on %s" % test) + new_tests = test.parse(config) or [] + for new_test in new_tests: + globals()[new_test] = getattr(test_file, new_test) + except Exception as e: + if 'BFT_DEBUG' in os.environ: + import traceback + traceback.print_exc() + print("Failed to run %s parse function!" % test) + pass diff --git a/boardfarm/tests/bittorrent.py b/boardfarm/tests/bittorrent.py new file mode 100644 index 00000000..bdaccb2e --- /dev/null +++ b/boardfarm/tests/bittorrent.py @@ -0,0 +1,108 @@ +# Copyright (c) 2018 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import re +import socat +import rootfs_boot +from devices import board, wan, lan, prompt + +import pexpect + +class BitTorrentBasic(socat.SoCat): + '''Super simple simulation of BitTorrent traffic''' + socat_recv = "UDP4-RECVFROM" + socat_send = "UDP4-SENDTO" + payload = "d1:ad2:id20:" + +class BitTorrentSingle(BitTorrentBasic): + '''Single UDP/Bittorrent flow''' + + def runTest(self): + #for d in [wan, lan]: + #d.sendline('apt-get update && apt-get -o Dpkg::Options::="--force-confnew" -y install socat pv') + #d.expect(prompt) + + sz, rate, ip, port = self.startSingleFlow() + print("started UDP to %s:%s sz = %s, rate = %sk" % (ip, port, sz, rate)) + time = sz / ( rate * 1024) + print("time should be ~%s" % time) + self.check_and_clean_ips() + lan.sendline('fg') + lan.expect(prompt, timeout=time+10) + + # TODO: make this a function that's more robust + board.get_pp_dev().sendline('cat /proc/net/nf_conntrack | grep dst=%s.*dport=%s' % (ip, port)) + board.get_pp_dev().expect(prompt) + + self.recover() + +class BitTorrentB2B(BitTorrentBasic): + '''Single UDP/Bittorrent flow back-to-back''' + + def runTest(self): + #for d in [wan, lan]: + #d.sendline('apt-get update && apt-get -o Dpkg::Options::="--force-confnew" -y install socat pv') + #d.expect(prompt) + + maxtime=5 + + board.get_nf_conntrack_conn_count() + + for i in range(10000): + sz, rate, ip, port = self.startSingleFlow(maxtime=maxtime) + print("started UDP to %s:%s sz = %s, rate = %sk" % (ip, port, sz, rate)) + time = sz / ( rate * 1024) + print("time should be ~%s" % time) + self.check_and_clean_ips() + lan.sendline('fg') + lan.expect(prompt, timeout=5) + + board.get_pp_dev().sendline('cat /proc/net/nf_conntrack | grep dst=%s.*dport=%s' % (ip, port)) + board.get_pp_dev().expect(prompt) + + board.get_nf_conntrack_conn_count() + + self.recover() + +class BitTorrentClient(rootfs_boot.RootFSBootTest): + def runTest(self): + board.sendcontrol('c') + board.expect(board.prompt) + board.sendline('logread -f &') + board.expect(board.prompt) + + lan.sendline('rm -rf Fedora*') + lan.expect(lan.prompt) + # TODO: apt-get install bittornado + for i in range(10): + lan.sendline("btdownloadheadless 'https://torrent.fedoraproject.org/torrents/Fedora-Games-Live-x86_64-28_Beta.torrent'") + lan.expect('saving:') + done = False + while not done: + lan.expect(pexpect.TIMEOUT, timeout=1) # flush buffer + if 0 == lan.expect(['time left: Download Succeeded!', pexpect.TIMEOUT], timeout=10): + print("Finished, restarting....") + done = True + board.expect(pexpect.TIMEOUT, timeout=5) + board.sendline() # keepalive + lan.sendcontrol('c') + lan.sendcontrol('c') + lan.sendcontrol('c') + lan.expect(lan.prompt) + lan.sendline('rm -rf Fedora*') + lan.expect(lan.prompt) + + def recover(self): + lan.sendcontrol('c') + lan.expect(lan.prompt) + lan.sendline('rm -rf Fedora*') + lan.expect(lan.prompt) + board.sendcontrol('c') + board.expect(board.prompt) + board.sendline('fg') + board.sendcontrol('c') + board.expect(board.prompt) diff --git a/tests/bridge_mode.py b/boardfarm/tests/bridge_mode.py similarity index 97% rename from tests/bridge_mode.py rename to boardfarm/tests/bridge_mode.py index 94bbda88..84c9070c 100644 --- a/tests/bridge_mode.py +++ b/boardfarm/tests/bridge_mode.py @@ -55,5 +55,5 @@ def runTest(self): board.network_restart() board.firewall_restart() - lan.sendline('ifconfig eth1 192.168.0.2') + lan.sendline('ifconfig %s 192.168.0.2' % lan.iface_dut) lan.expect(prompt) diff --git a/boardfarm/tests/cdrouter_bootstrap.py b/boardfarm/tests/cdrouter_bootstrap.py new file mode 100644 index 00000000..64344b7b --- /dev/null +++ b/boardfarm/tests/cdrouter_bootstrap.py @@ -0,0 +1,353 @@ +# Copyright (c) 2017 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +from cdrouter import CDRouter +from cdrouter.configs import Config +from cdrouter.cdrouter import CDRouterError +from cdrouter.jobs import Job +from cdrouter.packages import Package + +import time +import rootfs_boot +from devices import board, wan, lan, wlan, prompt +import os +import pexpect +import lib + +class CDrouterStub(rootfs_boot.RootFSBootTest): + '''First attempt at test that runs a CDrouter job, waits for completion, + and grabs results''' + + # To be overriden by children class + tests = None + extra_config = False + cdrouter_server = None + + def runTest(self): + if 'cdrouter_server' in self.config.board: + self.cdrouter_server = self.config.board['cdrouter_server'] + elif self.config.cdrouter_server is not None: + self.cdrouter_server = self.config.cdrouter_server + + if 'cdrouter_wan_iface' in self.config.board: + self.cdrouter_wan_iface = self.config.board['cdrouter_wan_iface'] + else: + self.cdrouter_wan_iface = self.config.cdrouter_wan_iface + + if 'cdrouter_lan_iface' in self.config.board: + self.cdrouter_lan_iface = self.config.board['cdrouter_lan_iface'] + else: + self.cdrouter_lan_iface = self.config.cdrouter_lan_iface + + if self.tests is None: + self.skipTest("No tests defined!") + + if self.cdrouter_server is None: + self.skipTest("No cdrouter server specified") + + lan.sendline('ifconfig %s down' % lan.iface_dut) + lan.expect(prompt) + + if not board.has_cmts: + wan.sendline('ifconfig %s down' % wan.iface_dut) + wan.expect(prompt) + + c = CDRouter(self.cdrouter_server) + + try: + board.sendcontrol('c') + board.expect(prompt) + board.sendline('reboot') + board.expect('reboot: Restarting system') + except: + board.reset() + board.wait_for_linux() + board.wait_for_network() + + # Add extra board specific delay + board.expect(pexpect.TIMEOUT, timeout=getattr(board, 'cdrouter_bootdelay', 0)) + + # If alt mac addr is specified in config, use that.. + # CMTS = we route so no wan mac is used + # if we route, we need to add routes + wandutmac = None + if board.has_cmts and wan.wan_cmts_provisioner: + # TODO: there are more missing ones CDrouter expects + wan.sendline('ip route add 200.0.0.0/8 via 192.168.3.2') + wan.expect(prompt) + elif not wan.static_ip: + for device in self.config.board['devices']: + if device['name'] == 'wan': + if 'alt_macaddr' in device: + wandutmac = device['alt_macaddr'] + break + + # Otherwise grab this from the device interface + if wandutmac is None: + board.sendline('ifconfig %s' % board.wan_iface) + board.expect('([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') + wandutmac = board.match.group() + board.expect(prompt) + + print("Using %s for WAN mac address" % wandutmac) + + lan.vlan = wan.vlan = 0 + for device in self.config.board['devices']: + d = None + if device['name'] == 'wan': + d = wan + elif device['name'] == 'lan': + d = lan + else: + continue + + if d is not None: + d.vlan = getattr(device, 'vlan', 0) + if d.vlan == 0: + d.sendline('cat /proc/net/vlan/config') + d.expect_exact('cat /proc/net/vlan/config') + if 0 == d.expect([pexpect.TIMEOUT, '%s.*\|\s([0-9]+).*\|' % d.iface_dut], timeout=5): + d.vlan = 0 + else: + d.vlan = d.match.group(1) + d.expect(prompt) + + print("Using %s for WAN vlan" % wan.vlan) + print("Using %s for LAN vlan" % lan.vlan) + + # TODO: move wan and lan interface to bft config? + contents=""" +testvar wanInterface """ + self.cdrouter_wan_iface + if wandutmac is not None: + contents=contents +""" +testvar wanDutMac """ + wandutmac + + if wan.vlan != 0: + contents=contents + """ +testvar wanVlanId """ + wan.vlan + + contents=contents + """ +testvar lanInterface """ + self.cdrouter_lan_iface + + if lan.vlan != 0: + contents=contents + """ +testvar lanVlanId """ + lan.vlan + + def add_cdrouter_config(config): + cdr_conf = None + + # TODO: make a generic helper to search path and overlays + if os.path.isfile(config): + cdr_conf = open(config, 'r').readlines() + elif 'BFT_OVERLAY' in os.environ: + for p in os.environ['BFT_OVERLAY'].split(' '): + p = os.path.realpath(p) + try: + cdr_conf = open(os.path.join(p, config), 'r').readlines() + except: + continue + else: + break + + return "\n" + "".join(cdr_conf) + + # Take config from overall config, but fallback to board config + if self.config.cdrouter_config is not None: + contents = contents + add_cdrouter_config(self.config.cdrouter_config) + elif board.cdrouter_config is not None: + contents = contents + add_cdrouter_config(board.cdrouter_config) + + if self.extra_config: + contents=contents + "\n" + self.extra_config.replace(',', '\n') + + if board.has_cmts: + for i in range(5): + try: + wan_ip = board.get_interface_ipaddr(board.erouter_iface) + except: + board.expect(pexpect.TIMEOUT, timeout=15) + continue + else: + if i == 4: + raise Exception("Failed to get erouter ip address") + break + + # TODO: mask from config? wanNatIp vs. wanIspAssignGateway? + contents=contents + """ +testvar wanMode static +testvar wanIspIp %s +testvar wanIspGateway %s +testvar wanIspMask 255.255.255.0 +testvar wanIspAssignIp %s +testvar wanNatIp %s +testvar IPv4HopCount %s +testvar lanDnsServer %s +testvar wanDnsServer %s""" % (self.config.board['cdrouter_wanispip'], \ + self.config.board['cdrouter_wanispgateway'], \ + wan_ip, wan_ip, \ + self.config.board['cdrouter_ipv4hopcount'], \ + board.get_dns_server(), \ + board.get_dns_server_upstream()) + + print("Using below for config:") + print(contents) + print("#######################") + + config_name="bft-automated-job-%s" % str(time.time()).replace('.', '') + cfg = c.configs.create(Config(name=config_name, contents=contents)) + + p = c.packages.create(Package(name=config_name, + testlist=self.tests, + config_id=cfg.id)) + + self.start_time = time.time() + j = c.jobs.launch(Job(package_id=p.id)) + + while j.result_id is None: + if (time.time() - self.start_time) > 300: + # delete job if it fails to start + c.jobs.delete(j.id) + raise Exception("Failed to start CDrouter job") + + board.expect(pexpect.TIMEOUT, timeout=1) + j = c.jobs.get(j.id) + + print('Job Result-ID: {0}'.format(j.result_id)) + + self.job_id = j.result_id + self.results = c.results + unpaused = False + end_of_start = False + no_more_pausing = False + while True: + r = c.results.get(j.result_id) + print(r.status) + + # we are ready to go from boardfarm reset above + if r.status == "paused" and unpaused == False: + c.results.unpause(j.result_id) + unpaused = True + board.expect(pexpect.TIMEOUT, timeout=1) + c.results.pause(j.result_id, when="end-of-test") + end_of_start = True + continue + + + if r.status == "paused" and end_of_start == True: + end_of_start = False + # TODO: do we need this anymore? we have board specific cdrouter_bootdelay + board.expect(pexpect.TIMEOUT, timeout=60) + c.results.unpause(j.result_id) + board.expect(pexpect.TIMEOUT, timeout=1) + no_more_pausing = True + continue + + if no_more_pausing and r.status == "paused": + print("Error: test is still paused") + c.results.stop(j.result_id) + break + + if r.status != "running" and r.status != "paused": + break + + board.expect(pexpect.TIMEOUT, timeout=5) + + print(r.result) + self.result_message = r.result.encode('ascii','ignore') + # TODO: results URL? + elapsed_time = time.time() - self.start_time + print("Test took %s" % time.strftime("%H:%M:%S", time.gmtime(elapsed_time))) + + summary = c.results.summary_stats(j.result_id) + + self.result_message += " (Failed= %s, Passed = %s, Skipped = %s)" \ + % (summary.result_breakdown.failed, \ + summary.result_breakdown.passed, \ + summary.result_breakdown.skipped) + + for test in summary.test_summaries: + self.logged[test.name] = vars(test) + + if str(test.name) not in ["start", "final"]: + from lib.common import TestResult + try: + grade_map = {"pass": "OK", "fail": "FAIL", "skip": "SKIP"}[test.result] + tr = TestResult(test.name, grade_map, test.description) + if test.started is not None: + tr.start_time = test.started + tr.stop_time = test.started + test.duration + else: + tr.elapsed_time = test.duration + self.subtests.append(tr) + except: + continue + + # TODO: handle skipped tests + + try: + metric = c.results.get(result_id, test.name, "bandwidth") + print(vars(metric)) + # TODO: decide how to export data to kibana + except: + # Not all tests have this metric, no other way? + pass + + + assert (r.result == "The package completed successfully") + + self.recover() + + def recover(self): + if board.has_cmts and wan.wan_cmts_provisioner: + # TODO: there are more missing ones (see above) + wan.sendline('ip route del 200.0.0.0/8 via 192.168.3.2') + wan.expect(prompt) + + if hasattr(self, 'results'): + r = self.results.get(self.job_id) + + if r.status == "running": + self.results.stop(self.job_id) + # TODO: full recovery... + for d in [wan,lan]: + d.sendline('ifconfig %s up' % d.iface_dut) + d.expect(prompt) + + # make sure board is back in a sane state + board.sendcontrol('c') + board.sendline() + if 0 != board.expect([pexpect.TIMEOUT] + board.uprompt, timeout=5): + board.reset() + board.wait_for_linux() + + @staticmethod + @lib.common.run_once + def parse(config): + if hasattr(config, 'board') and 'cdrouter_server' in config.board: + cdrouter_server = config.board['cdrouter_server'] + elif config.cdrouter_server is not None: + cdrouter_server = config.cdrouter_server + else: + return [] + + c = CDRouter(cdrouter_server) + cdrouter_test_matrix = {} + new_tests = [] + for mod in c.testsuites.list_modules(): + name = "CDrouter" + mod.name.replace('.', '').replace('-','_') + list_of_tests = [ x.encode('ascii','ignore') for x in mod.tests ] + globals()[name] = type(name.encode('ascii','ignore') , (CDrouterStub, ), + { + 'tests': list_of_tests + }) + new_tests.append(name) + + return new_tests + +class CDrouterCustom(CDrouterStub): + tests = os.environ.get("BFT_CDROUTER_CUSTOM", "").split(" ") + extra_config = os.environ.get("BFT_CDROUTER_CUSTOM_CONFIG", "") diff --git a/boardfarm/tests/concurrent_iperf.py b/boardfarm/tests/concurrent_iperf.py new file mode 100644 index 00000000..2bdceb2e --- /dev/null +++ b/boardfarm/tests/concurrent_iperf.py @@ -0,0 +1,79 @@ +import rootfs_boot +import pexpect + +from devices.common import print_bold +from datetime import datetime + +from devices import board, prompt, wan, lan + +class ConcurrentIperf(rootfs_boot.RootFSBootTest): + '''Determine's max number of iperf connections''' + def runTest(self): + wan_ip = wan.get_interface_ipaddr(wan.iface_dut) + wan.sendline('iperf -s -l 1M -w 1M') + wan.expect('Server listening on ') + + board.collect_stats(stats=['mpstat']) + + time = 10 + cmd = "iperf -R -c %s -P %s -t 10 -N -w 1M -l 1M | grep SUM" + # prime the pipes... + lan.sendline(cmd % (wan_ip, 4)) + lan.expect(prompt) + + prev_con = 0 + prev_failed = 0 + for con_conn in range(32, 513, 16): + try: + tstart = datetime.now() + lan.sendline(cmd % (wan_ip, con_conn)) + failed_cons = 0 + while (datetime.now() - tstart).seconds < (time * 2): + timeout=(time*2)-(datetime.now() - tstart).seconds + if 0 == lan.expect(['write failed: Connection reset by peer'] + prompt, timeout=timeout): + failed_cons += 1 + else: + break + print_bold("For iperf with %s connections, %s failed...." % (con_conn, failed_cons)) + lan.expect('.*') + wan.expect('.*') + + board.touch() + prev_conn = con_conn + prev_failed = failed_cons + + if con_conn == 512: + self.result_message = "iPerf Concurrent passed 512 connections (failed conns = %s)" % failed_cons + except: + self.result_message = "iPerf Concurrent Connections failed entirely at %s (failed conns = %s)" % (prev_conn, prev_failed) + break + + print(self.result_message) + + self.recover() + + def recover(self): + for d in [wan, lan]: + d.sendcontrol('z') + if 0 == d.expect([pexpect.TIMEOUT] + prompt): + d.sendcontrol('c') + d.sendcontrol('c') + d.sendcontrol('c') + d.sendcontrol('c') + d.sendcontrol('c') + d.sendline('') + d.sendline('echo FOOBAR') + d.expect_exact('echo FOOBAR') + d.expect_exact('FOOBAR') + d.expect(prompt) + else: + d.sendline("kill %1") + d.expect(prompt) + + d.sendline('pkill -9 -f iperf') + d.expect_exact('pkill -9 -f iperf') + d.expect(prompt) + + board.parse_stats(dict_to_log=self.logged) + + self.result_message += ", cpu usage = %.2f" % self.logged['mpstat'] diff --git a/boardfarm/tests/connection_stress.py b/boardfarm/tests/connection_stress.py new file mode 100644 index 00000000..6dce57b1 --- /dev/null +++ b/boardfarm/tests/connection_stress.py @@ -0,0 +1,66 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import rootfs_boot +import time +from devices import board, wan, lan, wlan, prompt + +from lib.installers import install_lighttpd + +class Connection_Stress(rootfs_boot.RootFSBootTest): + '''Measured CPU use while creating thousands of connections.''' + + concurrency = 25 + num_conn = 5000 + + # for results + reqs_per_sec = 0 + + def runTest(self): + install_lighttpd(wan) + wan.sendline('/etc/init.d/lighttpd start') + wan.expect(prompt) + # Wan device: Create small file in web dir + fname = 'small.txt' + cmd = '\nhead -c 10000 /dev/urandom > /var/www/%s' % fname + wan.sendline(cmd) + wan.expect(prompt) + # Lan Device: download small file a lot + # TODO: this is actually a 404 for lighthttpd config issues? + url = 'http://%s/%s' % (wan.gw, fname) + # Start CPU monitor + board.collect_stats(stats=['mpstat']) + # Lan Device: download small file a lot + lan.sendline('\nab -dn %s -c %s %s' % (self.num_conn, self.concurrency, url)) + lan.expect('Benchmarking') + timeout=0.05*self.num_conn + if 0 != lan.expect(['Requests per second:\s+(\d+)', 'apr_socket_recv: Connection reset by peer'], timeout=timeout): + raise Exception("ab failed to run") + self.reqs_per_sec = int(lan.match.group(1)) + lan.expect(prompt) + + self.recover() + + def recover(self): + lan.sendcontrol('c') + time.sleep(5) # Give router a few seconds to recover + board.parse_stats(dict_to_log=self.logged) + avg_cpu = self.logged['mpstat'] + msg = "ApacheBench measured %s connections/second, CPU use = %s%%." % (self.reqs_per_sec, avg_cpu) + self.result_message = msg + +class Connection_Stress_Lite(Connection_Stress): + '''Measured CPU use while creating thousands of connections.''' + + concurrency = 5 + num_conn = 500 + +class Connection_Stress_Intense(Connection_Stress): + '''Measured CPU use while creating thousands of connections.''' + + concurrency = 25 + num_conn = 20000 diff --git a/boardfarm/tests/console_delay.py b/boardfarm/tests/console_delay.py new file mode 100644 index 00000000..00dbe6c5 --- /dev/null +++ b/boardfarm/tests/console_delay.py @@ -0,0 +1,23 @@ +import rootfs_boot + +from devices import board + +class DelayBetweenChar(rootfs_boot.RootFSBootTest): + def runTest(self): + board.delaybetweenchar = 0.1 + + for console in board.consoles: + console.delaybetweenchar = 0.1 + +class DelayBetweenCharExtreme(rootfs_boot.RootFSBootTest): + def runTest(self): + board.delaybetweenchar = 1 + for console in board.consoles: + console.delaybetweenchar = 1 + +class NoDelayBetweenChar(rootfs_boot.RootFSBootTest): + def runTest(self): + board.delaybetweenchar = None + for console in board.consoles: + console.delaybetweenchar = None + diff --git a/tests/curl_https.py b/boardfarm/tests/curl_https.py similarity index 93% rename from tests/curl_https.py rename to boardfarm/tests/curl_https.py index 33c0a244..bdd9de30 100644 --- a/tests/curl_https.py +++ b/boardfarm/tests/curl_https.py @@ -21,7 +21,7 @@ def runTest(self): board.sendline('curl ' + check) board.expect('<!DOCTYPE html>') board.expect(prompt) - print '\n\nCurl downloaded ' + check + ' as expected\n' + print('\n\nCurl downloaded ' + check + ' as expected\n') class CurlSSLBad(rootfs_boot.RootFSBootTest): '''Curl can't access https with bad signature.''' @@ -44,5 +44,5 @@ def runTest(self): board.sendline('curl ' + check[0]) board.expect(check[1]) board.expect(prompt) - print '\n\nCurl refused to download ' + check[0] + ' as expected\n' + print('\n\nCurl refused to download ' + check[0] + ' as expected\n') diff --git a/tests/firewall_on_off.py b/boardfarm/tests/firewall_on_off.py similarity index 100% rename from tests/firewall_on_off.py rename to boardfarm/tests/firewall_on_off.py diff --git a/boardfarm/tests/hping3.py b/boardfarm/tests/hping3.py new file mode 100644 index 00000000..aeedf8bd --- /dev/null +++ b/boardfarm/tests/hping3.py @@ -0,0 +1,59 @@ +import rootfs_boot +import pexpect + +from lib.installers import install_hping3 + +from devices import board, prompt, wan, lan + +class hping3_basic_udp(rootfs_boot.RootFSBootTest): + '''Floods hping3, creating lots of firewall entries in router''' + + conn_rate = "u2000" + conns = 20000 + + def runTest(self): + install_hping3(lan) + wan_ip = wan.get_interface_ipaddr(wan.iface_dut) + wan.sendline('nc -lvu %s 565' % wan_ip) + wan.expect_exact('nc -lvu %s 565' % wan_ip) + + board.collect_stats(stats=['mpstat']) + + # dest ip and port are fixed, random src port, fixed src ip, 100 us between + lan.sendline('hping3 -2 -c %s -d 120 -S -w 64 -p 445 -i %s %s' % (self.conns, self.conn_rate, wan_ip)) + lan.expect('HPING') + + self.max_conns = 0 + for not_used in range(10): + self.max_conns = max(self.max_conns, board.get_nf_conntrack_conn_count()) + board.get_proc_vmstat() + lan.expect(pexpect.TIMEOUT, timeout=3) + board.expect(pexpect.TIMEOUT, timeout=3) + board.touch() + + self.recover() + + def recover(self): + lan.sendcontrol('c') + lan.expect(prompt) + lan.sendline('pkill -9 -f hping3') + lan.expect_exact('pkill -9 -f hping3') + lan.expect(prompt) + + wan.sendcontrol('c') + wan.expect(prompt) + wan.sendline('pkill -9 -f nc ') + wan.expect_exact('pkill -9 -f nc') + wan.expect(prompt) + + board.parse_stats(dict_to_log=self.logged) + + args = (self.conn_rate, self.max_conns, self.logged['mpstat']) + self.result_message = "hping3 udp firewall test, conn_rate = %s, max_conns = %s, cpu usage = %.2f" % args + + +class hping3_basic_udp_long(hping3_basic_udp): + '''Floods hping3, creating lots of firewall entries in router''' + + conn_rate = "u2000" + conns = "60000" diff --git a/tests/igmpv3_basic.py b/boardfarm/tests/igmpv3_basic.py similarity index 100% rename from tests/igmpv3_basic.py rename to boardfarm/tests/igmpv3_basic.py diff --git a/tests/interact.py b/boardfarm/tests/interact.py similarity index 59% rename from tests/interact.py rename to boardfarm/tests/interact.py index 5bc0b169..4a80d32b 100644 --- a/tests/interact.py +++ b/boardfarm/tests/interact.py @@ -28,7 +28,11 @@ def print_legacy_devices(self): def print_dynamic_devices(self): for device in self.config.devices: d = getattr(self.config, device) - print(" %s device: ssh %s@%s" % (device, d.username, d.name)) + # TODO: should move all classes to use string repr + if hasattr(d, 'username'): + print(" %s device: ssh %s@%s" % (device, d.username, d.name)) + else: + print(" %s device: %s" % (d.name, d)) def runTest(self): legacy = hasattr(self.config, "wan_device") @@ -48,13 +52,22 @@ def runTest(self): print('Pro-tip: Increase kernel message verbosity with\n' ' echo "7 7 7 7" > /proc/sys/kernel/printk') print("Menu") - print(" 1: Enter console") i = 2 + if board.consoles is None: + print(" 1: Enter console") + i += 1 + else: + i = 1 + for c in board.consoles: + print(" %s: Enter console" % i) + i += 1 if legacy: - print(" 2: Enter wan console") - print(" 3: Enter lan console") - print(" 4: Enter wlan console") - i = 5 + print(" %s: Enter wan console" % i) + i += 1 + print(" %s: Enter lan console" % i) + i += 1 + print(" %s: Enter wlan console" % i) + i += 1 print(" %s: List all tests" % i) i += 1 @@ -73,20 +86,41 @@ def runTest(self): d = getattr(self.config, key) d.interact() - if key == "1": - board.interact() - elif legacy and key == "2": - wan.interact() - elif legacy and key == "3": - lan.interact() - elif legacy and key == "4": - wlan.interact() - elif (legacy and key == "5") or key == "2": + i = 1 + for c in board.consoles: + if key == str(i): + c.interact() + i += 1 + + if legacy: + if key == str(i): + wan.interact() + continue + i += 1 + + if key == str(i): + lan.interact() + continue + i += 1 + + if key == str(i): + wlan.interact() + continue + i += 1 + + if key == str(i): try: # re import the tests test_files = glob.glob(os.path.dirname(__file__)+"/*.py") for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): exec("from %s import *" % x) + + if 'BFT_OVERLAY' in os.environ: + for o in os.environ['BFT_OVERLAY'].split(' ' ): + o = os.path.realpath(o) + test_files = glob.glob(o + "/tests/*.py") + for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): + exec("from %s import *" % x) except: print("Unable to re-import tests!") else: @@ -94,12 +128,22 @@ def runTest(self): rfs_boot = rootfs_boot.RootFSBootTest print("Available tests:") print_subclasses(rfs_boot) - elif (legacy and key == "6") or key == "3": + continue + i += 1 + + if key == str(i): try: # re import the tests test_files = glob.glob(os.path.dirname(__file__)+"/*.py") for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): exec("from %s import *" % x) + + if 'BFT_OVERLAY' in os.environ: + for o in os.environ['BFT_OVERLAY'].split(' ' ): + overlay = os.path.realpath(overlay) + test_files = glob.glob(o + "/tests/*.py") + for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): + exec("from %s import *" % x) except: print("Unable to re-import tests!") else: @@ -107,25 +151,37 @@ def runTest(self): print("Type test to run: ") test = sys.stdin.readline() + #try: + board.sendline() + # default are 1 1 1 7 + board.set_printk() + board.expect(prompt) try: - board.sendline() - board.sendline('echo \"1 1 1 7\" > /proc/sys/kernel/printk') - board.expect(prompt) t = eval(test) + reload(sys.modules[t.__module__]) cls = t(self.config) lib.common.test_msg("\n==================== Begin %s ====================" % cls.__class__.__name__) cls.testWrapper() lib.common.test_msg("\n==================== End %s ======================" % cls.__class__.__name__) board.sendline() except: - print("Unable to (re-)run specified test") + lib.common.test_msg("Failed to find and/or run test, continuing..") + continue + #except: + # print("Unable to (re-)run specified test") + + continue + i += 1 - elif (legacy and key == "7") or key == "4": + if key == str(i): board.reset() print("Press Ctrl-] to stop interaction and return to menu") board.interact() - elif (legacy and key == "8") or key == "5": - print "Enter python shell, press Ctrl-D to exit" + continue + i += 1 + + if key == str(i): + print("Enter python shell, press Ctrl-D to exit") try: from IPython import embed embed() @@ -138,6 +194,9 @@ def runTest(self): shell = code.InteractiveConsole(vars) shell.interact() except: - print "Unable to spawn interactive shell!" - elif key == "x": + print("Unable to spawn interactive shell!") + continue + i += 1 + + if key == "x": break diff --git a/tests/ip_link.py b/boardfarm/tests/ip_link.py similarity index 100% rename from tests/ip_link.py rename to boardfarm/tests/ip_link.py diff --git a/boardfarm/tests/iperf3_test.py b/boardfarm/tests/iperf3_test.py new file mode 100644 index 00000000..2ac57528 --- /dev/null +++ b/boardfarm/tests/iperf3_test.py @@ -0,0 +1,109 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import re +import rootfs_boot +from lib import installers +from devices import board, wan, lan, wlan, prompt + +try: + from devices import winwsl +except: + winwsl = None + +class iPerf3Test(rootfs_boot.RootFSBootTest): + '''iPerf3 generic performance tests''' + + opts= "" + time = 60 + server_port = "5201" + client = lan + target_ip = None + + def runTest(self): + installers.install_iperf3(wan) + installers.install_iperf3(self.client) + + wan.sendline('iperf3 -s -p %s' % self.server_port) + wan.expect('-----------------------------------------------------------') + wan.expect('-----------------------------------------------------------') + + if self.target_ip == None: + self.target_ip = wan.gw + + board.collect_stats(stats=['mpstat']) + + self.client.sendline('iperf3 %s -c %s -P5 -t %s -i 0 -p %s' % (self.opts, self.target_ip, self.time, self.server_port)) + self.client.expect(prompt, timeout=self.time+10) + + sender = re.findall('SUM.*Bytes\s*(.*/sec).*sender', self.client.before)[-1] + if 'Mbits' in sender: + s_rate = float(sender.split()[0]) + elif 'Kbits' in sender: + s_rate = float(sender.split()[0])/1024 + elif 'Gbits' in sender: + s_rate = float(sender.split()[0])*1024 + else: + raise Exception("Unknown rate in sender results") + + recv = re.findall('SUM.*Bytes\s*(.*/sec).*receiver', self.client.before)[-1] + if 'Mbits' in recv: + r_rate = float(recv.split()[0]) + elif 'Kbits' in recv: + r_rate = float(recv.split()[0])/1024 + elif 'Gbits' in recv: + r_rate = float(recv.split()[0])*1024 + else: + raise Exception("Unknown rate in recv results") + + self.logged['s_rate'] = s_rate + self.logged['r_rate'] = r_rate + + self.recovery() + + def recovery(self): + for d in [wan, self.client]: + d.sendcontrol('c') + d.sendcontrol('c') + d.expect(prompt) + + board.parse_stats(dict_to_log=self.logged) + + args = (self.logged['s_rate'], self.logged['r_rate'], self.logged['mpstat']) + self.result_message = "Sender rate = %s MBits/sec, Receiver rate = %s Mbits/sec, cpu = %.2f\n" % args + +class iPerf3RTest(iPerf3Test): + '''iPerf3 reverse generic performance tests''' + + opts = "-R" + +class iPerf3_v6Test(iPerf3Test): + '''iPerf3 ipv6 generic performance tests''' + + opts = "-6" + + def runTest(self): + self.target_ip = wan.gwv6 + super(iPerf3_v6Test, self).runTest() + +class iPerf3R_v6Test(iPerf3Test): + '''iPerf3 ipv6 reverse generic performance tests''' + + opts = "-6 -R" + + def runTest(self): + self.target_ip = wan.gwv6 + super(iPerf3R_v6Test, self).runTest() + +class iPerf3Test2nd(iPerf3Test): + '''iPerf3 on second server port''' + server_port = "5202" + +class iPerf3TestWSL(iPerf3Test): + '''iPerf3 with windows WSL client''' + + client = winwsl diff --git a/tests/iperf_test.py b/boardfarm/tests/iperf_test.py similarity index 98% rename from tests/iperf_test.py rename to boardfarm/tests/iperf_test.py index a41679f2..99b8b34e 100644 --- a/tests/iperf_test.py +++ b/boardfarm/tests/iperf_test.py @@ -10,7 +10,7 @@ import rootfs_boot import ipv6_setup import lib -from lib import streamboost, installers +from lib import installers from devices import board, wan, lan, wlan, prompt # change this if you want to one time tweak iperf opts @@ -73,7 +73,7 @@ def server_opts_forward(self): def server_opts_reverse(self, node=lan): try: - lan_priv_ip = node.get_interface_ipaddr("eth1") + lan_priv_ip = node.get_interface_ipaddr(lan.iface_dut) except: lan_priv_ip = node.get_interface_ipaddr("wlan0") board.uci_forward_traffic_redirect("tcp", "5001", lan_priv_ip) @@ -164,7 +164,7 @@ class iPerfNonRoutedTest(iPerfTest): '''iPerf from LAN to Router''' def forward_ip(self): - return "192.168.1.1" + return board.get_interface_ipaddr(board.lan_iface) def runTest(self): super(iPerfNonRoutedTest, self).runTest(client=lan, server=board) @@ -342,7 +342,7 @@ def runTest(self): if not wlan: self.skipTest("skipping test no wlan") - self.fip = lan.get_interface_ipaddr("eth1") + self.fip = lan.get_interface_ipaddr(lan.iface_dut) self.rip = wlan.get_interface_ipaddr("wlan0") wlan.sendline('iwconfig') diff --git a/tests/iperf_udp_test.py b/boardfarm/tests/iperf_udp_test.py similarity index 98% rename from tests/iperf_udp_test.py rename to boardfarm/tests/iperf_udp_test.py index c8d2c6ba..bc26abe1 100644 --- a/tests/iperf_udp_test.py +++ b/boardfarm/tests/iperf_udp_test.py @@ -10,7 +10,7 @@ import rootfs_boot import ipv6_setup import lib -from lib import streamboost, installers +from lib import installers from devices import board, wan, lan, wlan, prompt # change this if you want to one time tweak iperf opts @@ -73,7 +73,7 @@ def server_opts_forward(self): def server_opts_reverse(self, node=lan): try: - lan_priv_ip = node.get_interface_ipaddr("eth1") + lan_priv_ip = node.get_interface_ipaddr(lan.iface_dut) except: lan_priv_ip = node.get_interface_ipaddr("wlan0") board.uci_forward_traffic_redirect("udp", "5001", lan_priv_ip) @@ -164,7 +164,7 @@ class iPerfUDPNonRoutedTest(iPerfUDPTest): '''iPerf from LAN to Router''' def forward_ip(self): - return "192.168.1.1" + return board.get_interface_ipaddr(board.lan_iface) def runTest(self): super(iPerfUDPNonRoutedTest, self).runTest(client=lan, server=board) diff --git a/boardfarm/tests/iptables.py b/boardfarm/tests/iptables.py new file mode 100644 index 00000000..f752bdd6 --- /dev/null +++ b/boardfarm/tests/iptables.py @@ -0,0 +1,32 @@ +import rootfs_boot + +import os + +from devices import board + +class IPTablesDump(rootfs_boot.RootFSBootTest): + '''Dumps all IPTables rules with stats''' + def runTest(self): + pp = board.get_pp_dev() + with open(os.path.join(self.config.output_dir, 'iptables.log'), 'w') as ipt_log: + for tbl in ['filter', 'nat', 'mangle', 'raw', 'security']: + pp.sendline('iptables -n -t %s -L -v; echo DONE' % tbl) + pp.expect_exact('echo DONE') + pp.expect_exact('DONE') + ipt_log.write(pp.before) + pp.expect(pp.prompt) + +class IPTablesFlushMangle(rootfs_boot.RootFSBootTest): + '''Flushes mangle table''' + def runTest(self): + pp = board.get_pp_dev() + pp.sendline('iptables -t mangle -F; iptables -t mangle -X') + pp.expect(pp.prompt) + + +class IPTablesResetCounters(rootfs_boot.RootFSBootTest): + '''Reset iptables counters''' + def runTest(self): + pp = board.get_pp_dev() + pp.sendline('iptables -Z') + pp.expect(pp.prompt) diff --git a/tests/ipv6_curl.py b/boardfarm/tests/ipv6_curl.py similarity index 100% rename from tests/ipv6_curl.py rename to boardfarm/tests/ipv6_curl.py diff --git a/tests/ipv6_setup.py b/boardfarm/tests/ipv6_setup.py similarity index 88% rename from tests/ipv6_setup.py rename to boardfarm/tests/ipv6_setup.py index 948e87c8..37cf63ea 100644 --- a/tests/ipv6_setup.py +++ b/boardfarm/tests/ipv6_setup.py @@ -37,22 +37,22 @@ def runTest(self): board.expect(prompt) board.network_restart() # Lan-side Device - lan.sendline('\nip -6 addr add 4aaa::6/64 dev eth1') + lan.sendline('\nip -6 addr add 4aaa::6/64 dev %s' % lan.iface_dut) lan.expect('ip -6') lan.expect(prompt) - lan.sendline('ip -6 route add 4aaa::1 dev eth1') + lan.sendline('ip -6 route add 4aaa::1 dev %s' % lan.iface_dut) lan.expect(prompt) - lan.sendline('ip -6 route add default via 4aaa::1 dev eth1') + lan.sendline('ip -6 route add default via 4aaa::1 dev %s' % lan.iface_dut) lan.expect(prompt) if 'No route to host' in lan.before: raise Exception('Error setting ivp6 routes') # Wan-side Device - wan.sendline('\nip -6 addr add 5aaa::6/64 dev eth1') + wan.sendline('\nip -6 addr add 5aaa::6/64 dev %s' % wan.iface_dut) wan.expect('ip -6') wan.expect(prompt) - wan.sendline('ip -6 route add 5aaa::1 dev eth1') + wan.sendline('ip -6 route add 5aaa::1 dev %s' % wan.iface_dut) wan.expect(prompt) - wan.sendline('ip -6 route add default via 5aaa::1 dev eth1') + wan.sendline('ip -6 route add default via 5aaa::1 dev %s' % wan.iface_dut) wan.expect(prompt) if 'No route to host' in wan.before: raise Exception('Error setting ivp6 routes') diff --git a/boardfarm/tests/jmeter.py b/boardfarm/tests/jmeter.py new file mode 100644 index 00000000..bee3645d --- /dev/null +++ b/boardfarm/tests/jmeter.py @@ -0,0 +1,140 @@ +import os +import shutil +import pexpect + +import rootfs_boot +from lib.installers import install_jmeter + +from devices import board, lan, prompt +from devices.common import scp_from + +def rm_r(path): + if not os.path.exists(path): + return + if os.path.isfile(path) or os.path.islink(path): + os.unlink(path) + else: + shutil.rmtree(path) + +class JMeter(rootfs_boot.RootFSBootTest): + '''Runs JMeter jmx file from LAN device''' + + jmx = "https://jmeter.apache.org/demos/ForEachTest2.jmx" + shortname = "ForEachTest2" + default_time = 600 + + def runTest(self): + self.dir = 'jmeter_%s' % self.shortname + install_jmeter(lan) + + lan.sendline('rm -rf $HOME/%s' % self.dir) + lan.expect(prompt) + lan.sendline('mkdir -p $HOME/%s/wd' % self.dir) + lan.expect(prompt) + lan.sendline('mkdir -p $HOME/%s/results' % self.dir) + lan.expect(prompt) + + if self.jmx.startswith('http'): + lan.sendline('curl %s > $HOME/%s/test.jmx' % (self.jmx, self.dir)) + lan.expect(prompt) + else: + print("Copying %s to lan device" % self.jmx) + lan.sendline('echo $HOME') + lan.expect_exact('echo $HOME') + lan.expect(prompt) + lan.copy_file_to_server(self.jmx, dst=lan.before.strip() + '/%s/test.jmx' % self.dir) + + board.collect_stats(stats=['mpstat']) + + lan.sendline('cd $HOME/%s/wd' % self.dir) + lan.expect(prompt) + lan.sendline('JVM_ARGS="-Xms4096m -Xmx8192m" jmeter -n -t ../test.jmx -l foo.log -e -o $HOME/%s/results' % self.dir) + lan.expect_exact('$HOME/%s/results' % self.dir) + for i in range(self.default_time): + if 0 != lan.expect([pexpect.TIMEOUT] + prompt, timeout=5): + break; + conns = board.get_nf_conntrack_conn_count() + board.get_proc_vmstat() + board.touch() + + if i > 100 and conns < 20: + raise Exception("jmeter is dead/stuck/broke, aborting the run") + + if i == 599: + raise Exception("jmeter did not have enough time to complete") + + + lan.sendline('cd -') + lan.expect(prompt) + lan.sendline('rm test.jmx') + lan.expect(prompt) + + self.recover() + + def recover(self): + board.touch() + lan.sendcontrol('c') + lan.expect(prompt) + board.touch() + + print("Copying files from lan to dir = %s" % self.config.output_dir) + lan.sendline('readlink -f $HOME/%s/' % self.dir) + lan.expect_exact('$HOME/%s/' % self.dir) + board.touch() + lan.expect(prompt) + board.touch() + fname=lan.before.replace('\n', '').replace('\r', '') + board.touch() + rm_r(os.path.join(self.config.output_dir, self.dir)) + scp_from(fname, lan.ipaddr, lan.username, lan.password, lan.port, self.config.output_dir) + + # let board settle down + board.expect(pexpect.TIMEOUT, timeout=30) + board.touch() + + board.parse_stats(dict_to_log=self.logged) + board.touch() + self.result_message = 'JMeter: DONE, name = %s cpu usage = %s' % (self.shortname, self.logged['mpstat']) + + +class JMeter_10x_10u_5t(JMeter): + '''Runs JMeter jmx 10x_10u_5t''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_10x_10u_5t.jmx') + shortname = "httpreq_10x_10u_5t" + +class JMeter_1x_9u_5t(JMeter): + '''Runs JMeter jmx 1x_9u_5t''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_1x_9u_5t.jmx') + shortname = "httpreq_1x_9u_5t" + +class JMeter_20x_9u_1t(JMeter): + '''Runs JMeter jmx 20x_9u_1t''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_20x_9u_1t.jmx') + shortname = "httpreq_20x_9u_1t" + +class JMeter_20x_9u_1t_300msdelay(JMeter): + '''Runs JMeter jmx 20x_9u_1t_300msdelay''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_20x_9u_1t_300msdelay.jmx') + shortname = "httpreq_20x_9u_1t_300msdelay" + +class JMeter_20x_9u_1t_500msdelay(JMeter): + '''Runs JMeter jmx 20x_9u_1t_500msdelay''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_20x_9u_1t_500msdelay.jmx') + shortname = "httpreq_20x_9u_1t_500msdelay" + +class JMeter_20x_9u_1t_1000msdelay(JMeter): + '''Runs JMeter jmx 20x_9u_1t_1000msdelay''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_20x_9u_1t_1000msdelay.jmx') + shortname = "httpreq_20x_9u_1t_1000msdelay" + +class JMeter_20x_9u_1t_1500msdelay(JMeter): + '''Runs JMeter jmx 20x_9u_1t_1500msdelay''' + + jmx = os.path.join(os.path.dirname(__file__), 'jmeter/httpreq_20x_9u_1t_1500msdelay.jmx') + shortname = "httpreq_20x_9u_1t_1500msdelay" diff --git a/boardfarm/tests/jmeter/httpreq_10x_10u_5t.jmx b/boardfarm/tests/jmeter/httpreq_10x_10u_5t.jmx new file mode 100644 index 00000000..e15ae9f7 --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_10x_10u_5t.jmx @@ -0,0 +1,562 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">10</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">5</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="Graph Results" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="RespTimeGraphVisualizer" testclass="ResultCollector" testname="Response Time Graph" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="StatGraphVisualizer" testclass="ResultCollector" testname="Aggregate Graph" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_1x_9u_5t.jmx b/boardfarm/tests/jmeter/httpreq_1x_9u_5t.jmx new file mode 100644 index 00000000..7c1d6a0f --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_1x_9u_5t.jmx @@ -0,0 +1,562 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">1</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">5</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="Graph Results" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="RespTimeGraphVisualizer" testclass="ResultCollector" testname="Response Time Graph" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="StatGraphVisualizer" testclass="ResultCollector" testname="Aggregate Graph" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_20x_9u_1t.jmx b/boardfarm/tests/jmeter/httpreq_20x_9u_1t.jmx new file mode 100644 index 00000000..9ac1ebde --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_20x_9u_1t.jmx @@ -0,0 +1,525 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">20</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">1</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="Graph Results" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="RespTimeGraphVisualizer" testclass="ResultCollector" testname="Response Time Graph" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1000msdelay.jmx b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1000msdelay.jmx new file mode 100755 index 00000000..3e1173f1 --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1000msdelay.jmx @@ -0,0 +1,455 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">20</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">1</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> + <stringProp name="ConstantTimer.delay">1000</stringProp> + </ConstantTimer> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1500msdelay.jmx b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1500msdelay.jmx new file mode 100755 index 00000000..d76e4987 --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_1500msdelay.jmx @@ -0,0 +1,455 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">20</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">1</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> + <stringProp name="ConstantTimer.delay">1500</stringProp> + </ConstantTimer> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_20x_9u_1t_300msdelay.jmx b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_300msdelay.jmx new file mode 100755 index 00000000..bb9da75f --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_300msdelay.jmx @@ -0,0 +1,455 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">20</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">1</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> + <stringProp name="ConstantTimer.delay">300</stringProp> + </ConstantTimer> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/jmeter/httpreq_20x_9u_1t_500msdelay.jmx b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_500msdelay.jmx new file mode 100755 index 00000000..a3afe97d --- /dev/null +++ b/boardfarm/tests/jmeter/httpreq_20x_9u_1t_500msdelay.jmx @@ -0,0 +1,455 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">20</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">1</stringProp> + <stringProp name="ThreadGroup.ramp_time">0</stringProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">jmeter.apache.org</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">usermanual/test_plan.html</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">yahoo.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">youtube.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">watch?v=FITnB6b7tpg</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">cnn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="false"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">msn.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bloomberg.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">nytimes.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">bbc.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">amazon.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">walmart.com</stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp> + <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp> + <boolProp name="HTTPSampler.image_parser">true</boolProp> + <boolProp name="HTTPSampler.concurrentDwn">true</boolProp> + <stringProp name="HTTPSampler.concurrentPool">60</stringProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree/> + <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> + <stringProp name="ConstantTimer.delay">500</stringProp> + </ConstantTimer> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="false"> + <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="graphiteMetricsSender" elementType="Argument"> + <stringProp name="Argument.name">graphiteMetricsSender</stringProp> + <stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.graphite.TextGraphiteMetricsSender</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphiteHost" elementType="Argument"> + <stringProp name="Argument.name">graphiteHost</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphitePort" elementType="Argument"> + <stringProp name="Argument.name">graphitePort</stringProp> + <stringProp name="Argument.value">2003</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="rootMetricsPrefix" elementType="Argument"> + <stringProp name="Argument.name">rootMetricsPrefix</stringProp> + <stringProp name="Argument.value">jmeter.</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="summaryOnly" elementType="Argument"> + <stringProp name="Argument.name">summaryOnly</stringProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="samplersList" elementType="Argument"> + <stringProp name="Argument.name">samplersList</stringProp> + <stringProp name="Argument.value"></stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="percentiles" elementType="Argument"> + <stringProp name="Argument.name">percentiles</stringProp> + <stringProp name="Argument.value">90;95;99</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="classname">org.apache.jmeter.visualizers.backend.graphite.GraphiteBackendListenerClient</stringProp> + </BackendListener> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/boardfarm/tests/latency_all.py b/boardfarm/tests/latency_all.py new file mode 100644 index 00000000..08974a37 --- /dev/null +++ b/boardfarm/tests/latency_all.py @@ -0,0 +1,57 @@ +import rootfs_boot +import re + +from devices import board + +class LatencyAllDevices(rootfs_boot.RootFSBootTest): + '''finds latency between all devices''' + def runTest(self): + + # TODO: create a get devices function? + devs = [] + for device in self.config.devices: + devs.append(getattr(self.config, device)) + + devs.append(board.get_pp_dev()) + + results = [] + + for d1 in devs: + for d2 in devs: + if d1 is d2: + continue + + board.touch() + + print("comparing " + d1.name + " to " + d2.name) + + try: + ip1 = d1.get_interface_ipaddr(d1.iface_dut) + ip2 = d2.get_interface_ipaddr(d2.iface_dut) + + def parse_ping_times(string): + r = [float(i) for i in re.findall('time=([^\s]*) ms', string)] + return sum(r)/len(r) + + d1.sendline("ping -c20 %s" % ip2) + d1.expect_exact("ping -c20 %s" % ip2) + d1.expect(d1.prompt) + + result = parse_ping_times(d1.before) + if result is not float('nan'): + results.append('latency from %s to %s = %s ms' % (d1.name, d2.name, str(result))) + + d2.sendline("ping -c20 %s" % ip1) + d2.expect_exact("ping -c20 %s" % ip1) + d2.expect(d2.prompt) + + result = parse_ping_times(d2.before) + if result is not float('nan'): + results.append('latency from %s to %s = %s ms' % (d2.name, d1.name, str(result))) + except: + print("failed to ping " + d1.name + " to " + d2.name) + continue + + print("Results:") + for line in results: + print(line) diff --git a/boardfarm/tests/lib/SnmpHelper.py b/boardfarm/tests/lib/SnmpHelper.py new file mode 100644 index 00000000..0a77dcba --- /dev/null +++ b/boardfarm/tests/lib/SnmpHelper.py @@ -0,0 +1,132 @@ +# Copyright (c) 2019 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import os +import json +import re + + +if __name__ == '__main__': + # this allows it to run as a standalone module (i.e. python tests/lib/SnmpHelper.py) + # forces the import of the global logging not the local one + # for this part to be removed we need to rename the local logging.py + # or move this module elsewhere + import sys + sys.path.insert(0, '/usr/lib/python2.7/') + import logging + +from pysmi.reader import FileReader, HttpReader +from pysmi.searcher import StubSearcher +from pysmi.writer import CallbackWriter +from pysmi.parser import SmiStarParser +from pysmi.codegen import JsonCodeGen +from pysmi.compiler import MibCompiler + +class SnmpMibs(object): + """ + Look up specific ASN.1 MIBs at configured Web and FTP sites, + compile them into JSON documents and print them out to stdout. + Try to support both SMIv1 and SMIv2 flavors of SMI as well as + popular deviations from official syntax found in the wild. + Source: + http://snmplabs.com/pysmi/examples/download-and-compile-smistar-mibs-into-json.html + + DEBUG: + BFT_DEBUG=y shows which mib module is being parsed + BFT_DEBUG=yy VERY verbose, shows the compiled dictionary and + mibs/oid details + """ + dbg = None + mib_dict = {} + + def __init__(self, mib_list, src_dir_list, http_sources=None): + if "BFT_DEBUG" in os.environ: + self.dbg = os.environ.get('BFT_DEBUG') + else: + self.dbg = "" + + if "yy" in self.dbg: + # VERY verbose, but essential for spotting + # possible ASN.1 errors + from pysmi import debug + debug.setLogger(debug.Debug('reader', 'compiler')) + + # Initialize compiler infrastructure + mibCompiler = MibCompiler( + SmiStarParser(), JsonCodeGen(), CallbackWriter(self.callback_func) + ) + + # search for source MIBs here + mibCompiler.addSources(*[FileReader(x) for x in src_dir_list]) + + if http_sources: + # search for source MIBs at Web sites + mibCompiler.addSources(*[HttpReader(*x) for x in http_sources]) + + # never recompile MIBs with MACROs + mibCompiler.addSearchers(StubSearcher(*JsonCodeGen.baseMibs)) + + # run recursive MIB compilation + results = mibCompiler.compile(*mib_list) + + def callback_func(self, mibName, jsonDoc, cbCtx): + if "y" in self.dbg: + print('# MIB module %s' % mibName) + + for k,v in json.loads(jsonDoc).iteritems(): + if "oid" in v: + if "objects" in v or "revisions" in v: + # we want to skip objects that have no use + continue + # add it to my dict + if "yy" in self.dbg: + print("adding %s:{%s}" %(k, v)) + self.mib_dict[k] = v + if "yy" in self.dbg: + print(json.dumps(self.mib_dict, indent=4)) + + def get_dict_mib(self): + return self.mib_dict + + def get_mib_oid(self, mib_name): + """ + Given a mib name, returns the OID, None if not found + """ + oid = None + try: + oid = self.mib_dict[mib_name]['oid'] + except: + if "y" in self.dbg: + print("ERROR: mib \'%s\' not found"%mib_name) + pass + return oid.encode('ascii','ignore') + +############################################################################################## + +if __name__ == '__main__': + + # this section can be used to test the classes above + # (maybe by redirecting the output to a file) + # BUT for this to run as a standalone file, it needs an + # absolute import (see the file import section) + + location = None + + if len(sys.argv) < 3: + if len(sys.argv) == 1: + print("Using default values from unit test: %s"%(SnmpMibsUnitTest.srcDirectories)) + else: + print("Usage:\n%s <path_to_global_mibs> [<path_to_vendor_mibs>]"%sys.argv[0]) + sys.exit(1) + else: + print('sys.argv='+sys.argv) + location = sys.argv + + unit_test = SnmpMibsUnitTest(mibs_location=location) + assert (unit_test.unitTest()) + + print('Done.') diff --git a/tests/lib/__init__.py b/boardfarm/tests/lib/__init__.py similarity index 100% rename from tests/lib/__init__.py rename to boardfarm/tests/lib/__init__.py diff --git a/boardfarm/tests/lib/bft_logging.py b/boardfarm/tests/lib/bft_logging.py new file mode 100644 index 00000000..d23eb00e --- /dev/null +++ b/boardfarm/tests/lib/bft_logging.py @@ -0,0 +1,94 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import time +import types +from datetime import datetime +from termcolor import colored +import re + +def now_short(_format = "%Y%m%d-%H%M%S"): + """ + Name:now_short + Purpose: Get current date and time string + Input:None + Output:String in "YYYYMMDD-hhmmss" format + """ + timeString = time.strftime(_format, time.localtime())+"\t" + return timeString + +def logfile_assert_message(s, condition, message): + if not condition: + s.log_to_file += now_short()+message+": FAIL\r\n" + assert 0, message+": FAIL\r\n" + else: + s.log_to_file += now_short()+message+": PASS\r\n" + +class LoggerMeta(type): + def __new__(cls, name, bases, attrs): + for attr_name, attr_value in attrs.iteritems(): + if isinstance(attr_value, types.FunctionType): + attrs[attr_name] = cls.deco(attr_value) + + return super(LoggerMeta, cls).__new__(cls, name, bases, attrs) + + @classmethod + def deco(cls, func): + def wrapper(*args, **kwargs): + func_args_str = "%s %s" % (repr(args), repr(kwargs)) + to_log = '%s.%s ( %s )' % (func.__module__, func.__name__, func_args_str) + + if hasattr(args[0], 'start'): + args[0].log_calls += '[%.6f]calling %s\r\n' % ((datetime.now()-args[0].start).total_seconds(), to_log) + + ret = func(*args, **kwargs) + + if hasattr(args[0], 'start'): + args[0].log_calls += "[%.6f]returned %s = %s\r\n" % ((datetime.now()-args[0].start).total_seconds(), to_log, repr(ret)) + + return ret + return wrapper + +def log_message(s, msg, header = False): + + line_sep = ('=' * (len(msg))) + full_msg = "\n\t\t"+line_sep+"\n\t\t"+msg+"\n\t\t"+line_sep+"\n" + if header: + print("\n\n\t\t\t***"+msg+"***\n\n") + s.log_to_file += now_short()+full_msg+"\r\n" + else: + print(full_msg) + s.log_to_file += now_short()+msg+"\r\n" + +class o_helper(object): + def __init__(self, parent, out, color): + self.color = color + self.out = out + self.parent = parent + self.first_write = True + def write(self, string): + if self.first_write: + self.first_write = False + string = "\r\n" + string + if self.color is not None: + self.out.write(colored(string, self.color)) + else: + self.out.write(string) + if not hasattr(self.parent, 'start'): + return + td = datetime.now()-self.parent.start + # check for the split case + if len(self.parent.log) > 1 and self.parent.log[-1] == '\r' and string[0] == '\n': + tmp = '\n[%.6f]' % td.total_seconds() + tmp += string[1:] + string = tmp + to_log = re.sub('\r\n', '\r\n[%.6f]' % td.total_seconds(), string) + self.parent.log += to_log + if hasattr(self.parent, 'test_to_log'): + self.parent.test_to_log.log += re.sub('\r\n\[', '\r\n%s: [' % self.parent.test_prefix, to_log) + def flush(self): + self.out.flush() diff --git a/boardfarm/tests/lib/common.py b/boardfarm/tests/lib/common.py new file mode 100644 index 00000000..21ab519e --- /dev/null +++ b/boardfarm/tests/lib/common.py @@ -0,0 +1,550 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import json +import pexpect +import sys +import time +import os +from termcolor import cprint +import re +import ipaddress + +from selenium import webdriver +from selenium.webdriver.common.proxy import * + +ubootprompt = ['ath>', '\(IPQ\) #', 'ar7240>'] +linuxprompt = ['root\\@.*:.*#', '@R7500:/# '] +prompts = ubootprompt + linuxprompt + ['/.* # ', ] + +def run_once(f): + def wrapper(*args, **kwargs): + if not wrapper.has_run: + wrapper.has_run = True + return f(*args, **kwargs) + wrapper.has_run = False + return wrapper + +def spawn_ssh_pexpect(ip, user='root', pw='bigfoot1', prompt=None, port="22", via=None, color=None, o=sys.stdout): + if via: + p = via.sendline("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ + % (user, ip, port)) + p = via + else: + p = pexpect.spawn("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ + % (user, ip, port)) + + i = p.expect(["yes/no", "assword:", "Last login"], timeout=30) + if i == 0: + p.sendline("yes") + i = p.expect(["Last login", "assword:"]) + if i == 1: + p.sendline(pw) + else: + pass + + if prompt is None: + p.prompt = "%s@.*$" % user + else: + p.prompt = prompt + + p.expect(p.prompt) + + from termcolor import colored + class o_helper_foo(): + def __init__(self, color): + self.color = color + def write(self, string): + o.write(colored(string, color)) + def flush(self): + o.flush() + + if color is not None: + p.logfile_read = o_helper_foo(color) + else: + p.logfile_read = o + + return p + +def clear_buffer(console): + try: + console.read_nonblocking(size=2000, timeout=1) + except: + pass + +def phantom_webproxy_driver(ipport): + ''' + Use this if you started web proxy on a machine connected to router's LAN. + ''' + service_args = [ + '--proxy=' + ipport, + '--proxy-type=http', + ] + print("Attempting to setup Phantom.js via proxy %s" % ipport) + driver = webdriver.PhantomJS(service_args=service_args) + driver.set_window_size(1024, 768) + driver.set_page_load_timeout(30) + return driver + +def firefox_webproxy_driver(ipport, config): + ''' + Use this if you started web proxy on a machine connected to router's LAN. + ''' + + ip, port = ipport.split(':') + + profile = webdriver.FirefoxProfile() + profile.set_preference("network.proxy.type", 1) + profile.set_preference("network.proxy.http", ip) + profile.set_preference("network.proxy.http_port", int(port)) + # missing the ssl proxy, should we add it? + profile.set_preference("network.proxy.ftp", ip) + profile.set_preference("network.proxy.ftp_port", int(port)) + profile.set_preference("network.proxy.socks", ip) + profile.set_preference("network.proxy.socks_port", int(port)) + profile.set_preference("network.proxy.socks_remote_dns", True) + profile.set_preference("browser.download.dir", os.getcwd()) + #number 2 is to save the file to the above current location instead of downloads + profile.set_preference("browser.download.folderList", 2) + #added this line to open the file without asking any questions + profile.set_preference("browser.helperApps.neverAsk.openFile", "text/anytext,text/comma-separated-values,text/csv,application/octet-stream") + profile.update_preferences() + driver = webdriver.Firefox(firefox_profile=profile) + driver.implicitly_wait(30) + driver.set_page_load_timeout(30) + + return driver + +def chrome_webproxy_driver(ipport, config): + ''' + Use this if you prefer Chrome. Should be the same as firefox_webproxy_driver + above, although ChromeWebDriver seems to be slower in loading pages. + ''' + + chrome_options = webdriver.ChromeOptions() + if config.default_proxy_type == 'sock5': + chrome_options.add_argument("--proxy-server=socks5://" + ipport); + else: + chrome_options.add_argument('--proxy-server=' + ipport) + + chrome_options.add_argument("--start-maximized") + + if "BFT_DEBUG" in os.environ: + print("chrome can be connected to Xvnc") + else: + print("chrome running headless") + chrome_options.add_argument("--headless") + + driver = webdriver.Chrome(options=chrome_options) + + driver.implicitly_wait(30) + driver.set_page_load_timeout(30) + + return driver + +def get_webproxy_driver(ipport, config): + if config.default_web_driver == "ffox": + d = firefox_webproxy_driver(ipport, config) + d.maximize_window() + return d + elif config.default_web_driver == "chrome": + return chrome_webproxy_driver(ipport, config) + # the win maximise is done in the chrome options + else: + # something has gone wrong, make the error message as self explanatory as possible + msg = "No usable web_driver specified, please add one to the board config" + if config.default_web_driver is not None: + msg = msg + " (value in config: '"+config.default_web_driver+"' not recognised)" + else: + # this should never happen + msg = msg + "(no default value set, please check boardfarm/config.py)" + raise Exception(msg) + + print("Using proxy %s, webdriver: %s" % (proxy, config.default_web_driver)) + +def test_msg(msg): + cprint(msg, None, attrs=['bold']) + +def sha256_checksum(filename, block_size=65536): + '''Calculates the SHA256 on a file''' + import hashlib + sha256 = hashlib.sha256() + with open(filename, 'rb') as f: + for block in iter(lambda: f.read(block_size), b''): + sha256.update(block) + return sha256.hexdigest() + +class TestResult: + logged = {} + def __init__(self, name, grade, message): + self.name = name + self.result_grade = grade + self.result_message = message + +cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK) for path in os.environ["PATH"].split(os.pathsep)) + +from library import print_bold +def start_ipbound_httpservice(device, ip="0.0.0.0", port="9000"): + ''' + Starts a simple web service on a specified port, + bound to a specified interface. (e.g. tun0) + Send ctrl-c to stop + ''' + device.sendline("python -c 'import BaseHTTPServer as bhs, SimpleHTTPServer as shs; bhs.HTTPServer((\"%s\", %s), shs.SimpleHTTPRequestHandler).serve_forever()'"%(ip, port)) + if 0 ==device.expect(['Traceback', pexpect.TIMEOUT], timeout=10): + if "BFT_DEBUG" in os.environ: + print_bold("Faield to start service on "+ip+":"+port) + return False + else: + if "BFT_DEBUG" in os.environ: + print_bold("Service started on "+ip+":"+port) + return True + +def start_ip6bound_httpservice(device, ip="::", port="9001"): + ''' + Starts a simple web service on a specified port, + bound to a specified interface. (e.g. tun0) + Send ctrl-c to stop (twice? needs better signal handling) + ''' + device.sendline('''cat > /root/SimpleHTTPServer6.py<<EOF +import socket +import BaseHTTPServer as bhs +import SimpleHTTPServer as shs + +class HTTPServerV6(bhs.HTTPServer): + address_family = socket.AF_INET6 +HTTPServerV6((\"%s\", %s),shs.SimpleHTTPRequestHandler).serve_forever() +EOF'''%(ip, port)) + + device.expect(device.prompt) + device.sendline ("python -m /root/SimpleHTTPServer6") + if 0 == device.expect(['Traceback', pexpect.TIMEOUT], timeout=10): + if "BFT_DEBUG" in os.environ: + print_bold('Faield to start service on ['+ip+']:'+port) + return False + else: + if "BFT_DEBUG" in os.environ: + print_bold("Service started on ["+ip+"]:"+port) + return True + +def start_ipbound_httpsservice(device, ip="0.0.0.0", port="443", cert="/root/server.pem"): + ''' + Starts a simple HTTPS web service on a specified port, + bound to a specified interface. (e.g. tun0) + Send ctrl-c to stop (twice? needs better signal handling) + ''' + import re + # the https server needs a certificate, lets create a bogus one + device.sendline("openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes") + for i in range(10): + if device.expect([re.escape("]:"), re.escape("Email Address []:")]) > 0: + device.sendline() + break + device.sendline() + device.expect(device.prompt) + device.sendline("python -c 'import os; print os.path.exists(\"%s\")'"%cert) + if 1 == device.expect(["True", "False"]): + # no point in carrying on + print_bold("Failed to create certificate for HTTPs service") + return False + device.expect(device.prompt) + # create and run the "secure" server + device.sendline('''cat > /root/SimpleHTTPsServer.py<< EOF +# taken from http://www.piware.de/2011/01/creating-an-https-server-in-python/ +# generate server.xml with the following command: +# openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes +# run as follows: +# python simple-https-server.py +# then in your browser, visit: +# https://<ip>:<port> + +import BaseHTTPServer, SimpleHTTPServer +import ssl + +httpd = BaseHTTPServer.HTTPServer((\"%s\", %s), SimpleHTTPServer.SimpleHTTPRequestHandler) +httpd.socket = ssl.wrap_socket (httpd.socket, certfile=\"%s\", server_side=True) +httpd.serve_forever() +EOF'''%(ip, port, cert)) + + device.expect(device.prompt) + device.sendline ("python -m /root/SimpleHTTPsServer") + if 0 == device.expect(['Traceback', pexpect.TIMEOUT], timeout=10): + print_bold("Failed to start service on ["+ip+"]:"+port) + return False + else: + if "BFT_DEBUG" in os.environ: + print_bold("Service started on ["+ip+"]:"+port) + return True + +def start_ip6bound_httpsservice(device, ip="::", port="4443", cert="/root/server.pem"): + ''' + Starts a simple HTTPS web service on a specified port, + bound to a specified interface. (e.g. tun0) + Send ctrl-c to stop (twice? needs better signal handling) + ''' + import re + # the https server needs a certificate, lets create a bogus one + device.sendline("openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes") + for i in range(10): + if device.expect([re.escape("]:"), re.escape("Email Address []:")]) > 0: + device.sendline() + break + device.sendline() + device.expect(device.prompt) + device.sendline("python -c 'import os; print os.path.exists(\"%s\")'"%cert) + if 1 == device.expect(["True", "False"]): + # no point in carrying on + print_bold("Failed to create certificate for HTTPs service") + return False + device.expect(device.prompt) + # create and run the "secure" server + device.sendline('''cat > /root/SimpleHTTPsServer.py<< EOF +import socket +import BaseHTTPServer as bhs +import SimpleHTTPServer as shs +import ssl + +class HTTPServerV6(bhs.HTTPServer): + address_family = socket.AF_INET6 +https=HTTPServerV6((\"%s\", %s),shs.SimpleHTTPRequestHandler) +https.socket = ssl.wrap_socket (https.socket, certfile=\"%s\", server_side=True) +https.serve_forever() +EOF'''%(ip, port, cert)) + + device.expect(device.prompt) + device.sendline ("python -m /root/SimpleHTTPsServer") + if 0 == device.expect(['Traceback', pexpect.TIMEOUT], timeout=10): + print_bold("Failed to start service on ["+ip+"]:"+port) + return False + else: + if "BFT_DEBUG" in os.environ: + print_bold("Service started on ["+ip+"]:"+port) + return True + +def configure_postfix(device): + ''' + configures TSL and SSL ports to access postfix server + The function can be extended with configuration changes to test emails. + ''' + device.sendline ('''cat > /etc/postfix/master.cf << EOF +smtp inet n - y - - smtpd +465 inet n - n - - smtpd +587 inet n - n - - smtpd +smtp inet n - y - 1 postscreen +smtpd pass - - y - - smtpd + +#628 inet n - y - - qmqpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +# + +maildrop unix - n n - - pipe + flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} + +# +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} +mailman unix - n n - - pipe + flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py + ${nexthop} ${user} +EOF''') + device.expect(device.prompt, timeout = 10) + device.sendline("service postfix start") + device.expect(device.prompt) + device.sendline("service postfix reload") + assert 0 != device.expect(['failed']+ device.prompt, timeout = 20) , "Unable to reolad server with new configurations" + + +def file_open_json(file): + try: + with open(file) as f: + return json.load(f) + except: + raise Exception("Could not open the json file") + +def snmp_mib_set(device, board, iface_ip, mib_name, index, set_type, set_value, timeout=10, retry=3): + """ + Name: snmp_mib_set + Purpose: set value of mibs via snmp + Input: wan, board, prompt, wan_ip, mib_name, index, set_type ,set_value + Output: set value + mib_name has to be passed with name of the mib, index is query index + set_type is "i" or "s" or "a" or "x" + set_value is the value to be set for the mib + Usage: snmp_mib_set(wan, board, board.wan_iface, "wifiMgmtBssSecurityMode", "32", "i", "1") + """ + match = re.search("\d+.(.*)",board.mib[mib_name]) + mib_oid = match.group(1)+'.'+index + time_out = (timeout*retry)+30 + device.sendline("snmpset -v 2c -c private -t " +str(timeout)+ " -r "+str(retry)+" "+iface_ip+" "+board.mib[mib_name]+"."+str(index)+" "+set_type+" "+str(set_value)) + if set_type == "i" or set_type == "a" or set_type == "u": + idx = device.expect(['Timeout: No Response from'] + ['iso\.'+mib_oid+'\s+\=\s+\S+\:\s+(%s)\r\n' % set_value] + device.prompt, timeout=time_out) + elif set_type == "s": + idx = device.expect(['Timeout: No Response from'] + ['iso\.'+mib_oid+'\s+\=\s+\S+\:\s+\"(%s)\"\r\n' % set_value] + device.prompt, timeout=time_out) + elif set_type == "x": + set_value_hex = set_value[2:].upper() + set_value_output = ' '.join([set_value_hex[i:i+2] for i in range(0, len(set_value_hex), 2)]) + idx = device.expect(['Timeout: No Response from'] + ['iso\.'+mib_oid+'\s+\=\s+\S+\:\s+(%s)\s+\r\n' % set_value_output] + device.prompt, timeout=40) + assert idx==1,"Setting the mib %s" % mib_name + snmp_out = device.match.group(1) + device.expect(device.prompt) + return snmp_out + +def snmp_mib_get(device, parser, iface_ip, mib_name, index, timeout=10, retry=3, community='private'): + """ + Name: snmp_mib_get + Purpose: get the value of mibs via snmp + Input: wan, board/snmpParser, prompt, wan_ip, mib_name, index + Output: get value + mib_name has to be passed with name of the mib, index is query index + Usage: snmp_mib_set(wan, board/snmpParser, board.wan_iface, "wifiMgmtBssSecurityMode", "32") + """ + time_out = (timeout*retry)+30 + extra_arg = '' + + # this should allow for legacy behaviour (with board passed in) + try: + match = re.search("\d+.(.*)",parser.mib[mib_name]) + mib_oid = 'iso\.' + match.group(1) + '.'+index + oid = parser.mib[mib_name] + except: + extra_arg = ' -On ' + oid = parser.get_mib_oid(mib_name) + mib_oid = oid + '.'+index + escaped_oid = re.escape(oid) + '\.' + str(index) + + device.sendline("snmpget -v 2c " + extra_arg + " -c "+community+" -t " +str(timeout)+ " -r "+str(retry)+" "+iface_ip+" "+ oid +"."+str(index)) + idx = device.expect(['Timeout: No Response from'] + [mib_oid+'\s+\=\s+\S+\:\s+(.*)\r\n'] + device.prompt, timeout=time_out) + assert idx==1,"Getting the mib %s"% mib_name + snmp_out = device.match.group(1) + device.expect(device.prompt) + snmp_out = snmp_out.strip("\"").strip() + return snmp_out + +def hex2ipv6(hexstr): + """ + Can parse strings in this form: + FE 80 00 00 00 00 00 00 3A 43 7D FF FE DC A6 C3 + """ + hexstr = hexstr.replace(' ', '').lower() + blocks = (''.join(block) for block in zip(*[iter(hexstr)]*4)) + return ipaddress.IPv6Address(':'.join(str(block) for block in blocks).decode('utf-8')) + +def retry(func_name, max_retry, *args): + for i in range(max_retry): + output = func_name(*args) + if output: + return output + else: + time.sleep(5) + else: + return None + +def retry_on_exception(method, args, retries=10, tout=5): + """ + Retries a method if an exception occurs + NOTE: args must be a tuple, hence a 1 arg tuple is (<arg>,) + """ + output = None + for not_used in range(retries): + try: + output = method(*args) + break + except Exception as e: + print_bold("method failed %d time (%s)"%((not_used+1), e)) + time.sleep(tout) + pass + + return output + +def resolv_dict(dic, key): + """ + This function used to get the value from gui json, replacement of eval + """ + key = key.strip("[]'").replace("']['", '#').split('#') + key_val = dic + for elem in key: + key_val = key_val[elem] + return key_val + +def setup_asterisk_config(device,numbers): + # to add sip.conf and extensions.conf + gen_conf = '''cat > /etc/asterisk/sip.conf << EOF +[general] +context=default +bindport=5060 +allowguest=yes +qualify=yes +registertimeout=900 +allow=all +allow=alaw +allow=gsm +allow=g723 +allow=g729 +EOF''' + gen_mod = '''cat > /etc/asterisk/extensions.conf << EOF +[default] +EOF''' + device.sendline(gen_conf) + device.expect(device.prompt) + device.sendline(gen_mod) + device.expect(device.prompt) + for i in numbers: + num_conf = '''( +echo ['''+i+'''] +echo type=friend +echo regexten='''+i+''' +echo secret=1234 +echo qualify=no +echo nat=force_rport +echo host=dynamic +echo canreinvite=no +echo context=default +echo dial=SIP/'''+i+''' +)>> /etc/asterisk/sip.conf''' + device.sendline(num_conf) + device.expect(device.prompt) + num_mod = '''( +echo exten \=\> '''+i+''',1,Dial\(SIP\/'''+i+''',10,r\) +echo same \=\>n,Wait\(20\) +)>> /etc/asterisk/extensions.conf''' + device.sendline(num_mod) + device.expect(device.prompt) diff --git a/boardfarm/tests/lib/gui_helper.py b/boardfarm/tests/lib/gui_helper.py new file mode 100644 index 00000000..3f3791d8 --- /dev/null +++ b/boardfarm/tests/lib/gui_helper.py @@ -0,0 +1,118 @@ +# Copyright (c) 2018 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +from selenium.webdriver.support.select import Select +from selenium.common.exceptions import NoSuchElementException +import time + +def enter_input(web_gui, input_path, input_value): + try: + #enter input value in text box for web page + input_tab = web_gui.find_element_by_id(input_path) + input_tab.clear() + input_tab.send_keys(input_value) + return True + except NoSuchElementException: + return False + +def click_button_id(web_gui, clickbutton): + try: + #to click any button using id + click_tab = web_gui.find_element_by_id(clickbutton) + click_tab.click() + time.sleep(5) + return True + except NoSuchElementException: + return False + +def click_button_xpath(web_gui, clickbutton): + try: + #to click any button using xpath + click_tab = web_gui.find_element_by_xpath(clickbutton) + click_tab.click() + time.sleep(5) + return True + except NoSuchElementException: + return False + +def select_option_by_id(web_gui, select_button, select_value): + try: + #To select the option required + select = Select(web_gui.find_element_by_id(select_button)) + select.select_by_visible_text(select_value) + time.sleep(5) + return select + except NoSuchElementException: + return None + +def select_option_by_name(web_gui, select_button, select_value): + try: + #To select the option required + select = Select(web_gui.find_element_by_name(select_button)) + select.select_by_visible_text(select_value) + time.sleep(5) + return select + except NoSuchElementException: + return None + +def select_option_by_xpath(web_gui, select_button, select_value): + try: + #To select the option required + select = Select(web_gui.find_element_by_xpath(select_button)) + select.select_by_visible_text(select_value) + time.sleep(5) + return select + except NoSuchElementException: + return None + +def get_drop_down_value(web_gui, get_value): + try: + #To get the value which already exists + select = Select(web_gui.find_element_by_id(get_value)) + selected_option = select.first_selected_option + selected_value = selected_option.text + return selected_value + except NoSuchElementException: + return None + +def get_radio_button_value(web_gui, get_value): + try: + #To get radio button value + radio_button = web_gui.find_elements_by_id(get_value) + for radiobutton in radio_button: + radio = radiobutton.get_attribute('src') + if "radio-box-checked" in radio: + return True + else: + return False + except NoSuchElementException: + return None + +def get_text_value(web_gui, get_value): + try: + #To get the text box value + text_button = web_gui.find_element_by_id(get_value) + text_value = text_button.text + return text_value + except NoSuchElementException: + return None + +def get_text_value_by_xpath(web_gui, get_value): + try: + #To get the text box value + text_button = web_gui.find_element_by_xpath(get_value) + text_value = text_button.text + return text_value + except NoSuchElementException: + return None + +def get_value_from_disabled_input(web_gui, get_value): + #To get the text with dynamic value + js="return document.getElementById(\"{!s}\").value;".format(str(get_value)) + text_value = web_gui.execute_script(js) + return str(text_value) diff --git a/boardfarm/tests/lib/installers.py b/boardfarm/tests/lib/installers.py new file mode 100755 index 00000000..70af27b1 --- /dev/null +++ b/boardfarm/tests/lib/installers.py @@ -0,0 +1,681 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +def apt_install(device, name, timeout=120): + apt_update(device) + device.sendline('apt-get install -q -y %s' % name) + device.expect('Reading package') + device.expect(device.prompt, timeout=timeout) + device.sendline('dpkg -l %s' % name) + device.expect_exact('dpkg -l %s' % name) + i = device.expect(['dpkg-query: no packages found' ] + device.prompt) + assert (i != 0) + +def apt_update(device, timeout=120): + device.sendline('apt-get update') + device.expect('Reading package') + device.expect(device.prompt, timeout=timeout) + +def install_iperf(device): + '''Install iPerf benchmark tool if not present.''' + device.sendline('\niperf -v') + try: + device.expect('iperf version', timeout=10) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf') + device.expect(device.prompt, timeout=60) + +def install_iperf3(device): + '''Install iPerf benchmark tool if not present.''' + device.sendline('\niperf3 -v') + try: + device.expect('iperf 3', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf3') + device.expect(device.prompt, timeout=60) + +def install_tcpick(device): + '''Install tcpick if not present.''' + device.sendline('\ntcpick --version') + try: + device.expect('tcpick 0.2', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install tcpick -y') + assert 0 == device.expect(['Setting up tcpick']+ device.prompt, timeout=60),"tcpick installation failed" + device.expect(device.prompt) + +def install_upnp(device): + '''Install miniupnpc if not present.''' + device.sendline('\nupnpc --version') + try: + device.expect('upnpc : miniupnpc', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install miniupnpc -y') + assert 0 == device.expect(['Setting up miniupnpc.*']+ device.prompt, timeout=60),"upnp installation failed" + device.expect(device.prompt) + +def install_lighttpd(device): + '''Install lighttpd web server if not present.''' + device.sendline('\nlighttpd -v') + try: + device.expect('lighttpd/1', timeout=8) + device.expect(device.prompt) + except: + device.expect(device.prompt) + apt_install(device, 'lighttpd') + +def install_netperf(device): + '''Install netperf benchmark tool if not present.''' + device.sendline('\nnetperf -V') + try: + device.expect('Netperf version 2.4', timeout=10) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install netperf') + device.expect(device.prompt, timeout=60) + device.sendline('/etc/init.d/netperf restart') + device.expect('Restarting') + device.expect(device.prompt) + +def install_endpoint(device): + '''Install endpoint if not present.''' + device.sendline('\npgrep endpoint') + try: + device.expect('pgrep endpoint') + device.expect('[0-9]+\r\n', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('wget http://downloads.ixiacom.com/products/ixchariot/endpoint_library/8.00/pelinux_amd64_80.tar.gz') + device.expect(device.prompt, timeout=120) + device.sendline('tar xvzf pelinux_amd64_80.tar.gz') + device.expect('endpoint.install', timeout=90) + device.expect(device.prompt, timeout=60) + device.sendline('./endpoint.install accept_license') + device.expect('Installation of endpoint was successful.', timeout=90) + device.expect(device.prompt, timeout=60) + +def install_hping3(device): + '''Install hping3 if not present.''' + device.sendline('\nhping3 --version') + try: + device.expect('hping3 version', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt) + apt_install(device, 'hping3') +def install_python(device): + '''Install python if not present.''' + device.sendline('\npython --version') + try: + device.expect('Python 2', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install python-pip python-mysqldb') + device.expect(device.prompt, timeout=60) + +def install_java(device): + '''Install java if not present.''' + device.sendline('\njavac -version') + try: + device.expect('javac 1.8', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main" | tee /etc/apt/sources.list.d/webupd8team-java.list') + device.expect(device.prompt) + device.sendline('echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list') + device.expect(device.prompt) + device.sendline('apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886') + device.expect(device.prompt) + device.sendline('apt-get update -y') + device.expect(device.prompt) + device.sendline('apt-get install oracle-java8-installer -y') + device.expect_exact("Do you accept the Oracle Binary Code license terms") + device.sendline('yes') + device.expect(device.prompt, timeout=60) + device.sendline('\njavac -version') + try: + device.expect('javac 1.8', timeout=5) + device.expect(device.prompt) + except: + device.sendline('sed -i \'s|JAVA_VERSION=8u144|JAVA_VERSION=8u152|\' /var/lib/dpkg/info/oracle-java8-installer.*') + device.sendline('sed -i \'s|PARTNER_URL=http://download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/|PARTNER_URL=http://download.oracle.com/otn-pub/java/jdk/8u152-b16/aa0333dd3019491ca4f6ddbe78cdb6d0/|\' /var/lib/dpkg/info/oracle-java8-installer.*') + device.sendline('sed -i \'s|SHA256SUM_TGZ="e8a341ce566f32c3d06f6d0f0eeea9a0f434f538d22af949ae58bc86f2eeaae4"|SHA256SUM_TGZ="218b3b340c3f6d05d940b817d0270dfe0cfd657a636bad074dcabe0c111961bf"|\' /var/lib/dpkg/info/oracle-java8-installer.*') + device.sendline('sed -i \'s|J_DIR=jdk1.8.0_144|J_DIR=jdk1.8.0_152|\' /var/lib/dpkg/info/oracle-java8-installer.*') + device.sendline('apt-get install oracle-java8-installer -y') + device.expect(device.prompt, timeout=60) + +def install_telnet_server(device): + '''Install xinetd/telnetd if not present.''' + device.sendline('\nxinetd -version') + try: + device.expect('xinetd Version 2.3', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install xinetd -y') + device.expect(device.prompt) + device.sendline('\ndpkg -l | grep telnetd') + try: + device.expect('telnet server', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install telnetd -y') + device.expect(device.prompt) + device.sendline('echo \"service telnet\" > /etc/xinetd.d/telnet') + device.sendline('echo "{" >> /etc/xinetd.d/telnet') + device.sendline('echo \"disable = no\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"flags = REUSE IPv6\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"socket_type = stream\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"wait = no\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"user = root\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"server = /usr/sbin/in.telnetd\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"log_on_failure += USERID\" >> /etc/xinetd.d/telnet') + device.sendline('echo \"}\" >> /etc/xinetd.d/telnet') + device.expect(device.prompt, timeout=60) + +def install_tcl(device): + '''Install tcl if not present.''' + device.sendline('\necho \'puts $tcl_patchLevel\' | tclsh') + try: + device.expect('8.6', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install tcl -y') + device.expect(device.prompt, timeout=60) + +def install_telnet_client(device): + '''Install telnet client if not present.''' + device.sendline('\ndpkg -l | grep telnet') + try: + device.expect('telnet client', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install telnet -y') + device.expect(device.prompt, timeout=60) + +def install_expect(device): + '''Install expect if not present.''' + device.sendline('\nexpect -version') + try: + device.expect('expect version 5', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install expect -y') + device.expect(device.prompt, timeout=60) + +def install_wget(device): + '''Install wget if not present.''' + device.sendline('\nwget --version') + try: + device.expect('GNU Wget 1', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install wget -y') + device.expect(device.prompt, timeout=60) + +def install_ftp(device): + '''Install ftp if not present.''' + device.sendline('\ndpkg -l | grep ftp') + try: + device.expect('classical file transfer client', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install ftp -y') + device.expect(device.prompt, timeout=60) + +def install_xampp(device): + '''Install xampp if not present.''' + device.sendline('\n/opt/lampp/lampp --help') + try: + device.expect('Usage: lampp', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('wget https://www.apachefriends.org/xampp-files/5.6.20/xampp-linux-x64-5.6.20-0-installer.run') + device.expect(device.prompt) + device.sendline('chmod +x xampp-linux-x64-5.6.20-0-installer.run') + device.expect(device.prompt) + device.sendline('./xampp-linux-x64-5.6.20-0-installer.run') + device.expect_exact("XAMPP Developer Files") + device.sendline('n') + device.expect_exact("Is the selection above correct?") + device.sendline('y') + device.expect_exact("Press [Enter] to continue") + device.sendline('') + device.expect_exact("Do you want to continue?") + device.sendline('y') + device.expect(device.prompt, timeout=120) + device.sendline('/opt/lampp/lampp restart') + device.expect(device.prompt) + device.sendline('touch /opt/lampp/htdocs/test.txt') + device.expect(device.prompt, timeout=120) + +def install_snmpd(device, post_cmd=None): + ''' + Install snmpd, use the 'post_cmd' to edit /etc/snmp/snmpd.conf + (or for whatever is needed just after the installation) + ''' + device.sendline('apt update && apt install snmpd -y') + device.expect(device.prompt, timeout=60) + + # by default snmpd only listen to connections from localhost, comment it out + device.sendline("sed 's/agentAddress udp:127.0.0.1:161/#agentAddress udp:127.0.0.1:161/' -i /etc/snmp/snmpd.conf") + device.expect(device.prompt) + + if post_cmd: + device.sendline(post_cmd) + device.expect(device.prompt) + device.sendline('service snmpd restart') + device.expect(device.prompt) + +def install_snmp(device): + '''Install snmp if not present.''' + device.sendline('\nsnmpget --version') + try: + device.expect('NET-SNMP version:', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt update && apt-get install snmp -y') + device.expect(device.prompt, timeout=60) + +def install_vsftpd(device): + '''Install vsftpd if not present.''' + device.sendline('\nvsftpd -v') + try: + device.expect('vsftpd: version', timeout=10) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install vsftpd -y') + device.expect(device.prompt, timeout=60) + device.sendline('sed -i "s/pam_service_name=vsftpd/pam_service_name=ftp/g" /etc/vsftpd.conf') + device.expect(device.prompt, timeout=5) + device.sendline('sed -i "s/#write_enable=YES/write_enable=YES/g" /etc/vsftpd.conf') + device.expect(device.prompt, timeout=5) + device.sendline('service vsftpd restart') + device.expect(device.prompt, timeout=60) + +def install_pysnmp(device): + '''Install pysnmp if not present.''' + device.sendline('\npip freeze | grep pysnmp') + try: + device.expect('pysnmp==', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('pip install pysnmp') + device.expect(device.prompt, timeout=90) + +def install_iw(device): + '''Install iw if not present.''' + device.sendline('iw --version') + try: + device.expect('iw version', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install iw -y') + device.expect(device.prompt, timeout=90) + +def install_jmeter(device): + '''Install jmeter if not present.''' + device.sendline('export PATH=$PATH:/opt/apache-jmeter-5.1.1/bin/') + device.expect(device.prompt) + device.sendline('jmeter --version') + try: + device.expect_exact('The Apache Software Foundation') + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install wget openjdk-8-jre-headless -y') + device.expect(device.prompt, timeout=90) + device.sendline('wget https://www-us.apache.org/dist//jmeter/binaries/apache-jmeter-5.1.1.tgz') + device.expect(device.prompt, timeout=90) + device.sendline('tar -C /opt -zxf apache-jmeter-5.1.1.tgz') + device.expect(device.prompt, timeout=120) + device.sendline('rm apache-jmeter-*') + +def install_IRCserver(device): + '''Install irc server if not present.''' + device.sendline('inspircd --version') + try: + device.expect('InspIRCd-', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install update-inetd -y') # Update inetd before installation + device.expect(device.prompt, timeout=90) + device.sendline('apt-get install inspircd -y') + device.expect(['Setting up inspircd'], timeout=90) + device.expect(device.prompt) + +def install_dovecot(device): + '''Install dovecot server if not present.''' + device.sendline('dovecot --version') + try: + device.expect(' \(', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install update-inetd -y') # Update inetd before installation + device.expect(device.prompt, timeout=90) + device.sendline('apt-get install dovecot-imapd dovecot-pop3d -y') + device.expect(['Processing triggers for dovecot-core'], timeout=90) + device.expect(device.prompt) + +def install_ovpn_server(device, remove=False, _user='lan', _ip="ipv4"): + '''Un/Install the OpenVPN server via a handy script''' + device.sendline('cd') + device.expect_exact('cd') + device.expect(device.prompt) + + # This is where the original setup script comes from. For conveninence we shall + # copy the version commited in test/lib/scripts to the server (we cannot always + # guarantee tha the containers will have web access) + #device.sendline('curl -O https://raw.githubusercontent.com/Angristan/openvpn-install/master/openvpn-install.sh') + import os + ovpn_install_script = 'openvpn-install.sh' + fname = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scripts/'+ovpn_install_script) + dest = '/root/'+ovpn_install_script + device.copy_file_to_server(fname, dest) + device.sendline('chmod +x openvpn-install.sh') + device.expect(device.prompt) + + device.sendline('ls -l /etc/init.d/openvpn') + index = device.expect(['(\\sroot\\sroot\\s{1,}\\d{4}(.*)\\s{1,}\\/etc\\/init\\.d\\/openvpn)'] + ['No such file or directory']) + device.expect(device.prompt) + + # do we want to remove it? + if remove: + if index == 0: + # be brutal, the server may not be responding to a stop + device.sendline('killall -9 openvpn') + device.expect(device.prompt, timeout=60) + device.sendline('./openvpn-install.sh') + device.expect('Select an option.*: ') + device.sendline('3') + device.expect_exact('Do you really want to remove OpenVPN? [y/n]: n') + device.sendcontrol('h') + device.sendline('y') + device.expect(device.prompt, timeout=90) + return + + # do the install + if index != 0: + device.sendline('rm -f /etc/openvpn/server.conf') + device.expect(device.prompt) + dev_ip = device.get_interface_ipaddr(device.iface_dut) + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('./openvpn-install.sh') + device.expect('IP address:.*', timeout=120) + for i in range(20): + device.sendcontrol('h') + device.sendline(str(dev_ip)) + device.expect('Public IPv4 address or hostname:.*') + device.sendline(dev_ip) + device.expect('Do you want to enable IPv6 support.*:.*') + if _ip == "ipv4": + device.sendline() + elif _ip == "ipv6": + device.sendcontrol('h') + device.sendline('y') + device.expect('Port choice.*:.*') + device.sendline() + device.expect('Protocol.*:.*') + device.sendline() + device.expect('DNS.*:.*') + device.sendline() + device.expect('Enable compression.*:.*') + device.sendline() + device.expect('Customize encryption settings.*: n') + device.sendline() + device.expect('Press any key to continue...') + device.sendline() + device.expect('.*Client name: ', timeout=120) + device.sendline(_user) + device.expect('Select an option.*: 1') + device.sendline() + device.expect(device.prompt, timeout=90) + device.sendline('/etc/init.d/openvpn stop') + device.expect(device.prompt) + if _ip == "ipv4": + # only add it in ipv4 + device.sendline('echo "local '+dev_ip+'" > /etc/openvpn/server.conf.tmp') + device.expect(device.prompt) + device.sendline('cat /etc/openvpn/server.conf >> /etc/openvpn/server.conf.tmp') + device.expect(device.prompt) + device.sendline('mv /etc/openvpn/server.conf.tmp /etc/openvpn/server.conf') + device.expect(device.prompt) + + device.sendline('/etc/init.d/openvpn status') + index = device.expect(["VPN 'server' is running" ] + [ "VPN 'server' is not running ... failed"] +device.prompt, timeout=90) + if index != 0: + device.sendline('/etc/init.d/openvpn restart') + device.expect(["Starting virtual private network daemon: server" ]) + # the following are for diagnositcs + device.sendline('/etc/init.d/openvpn status') + device.expect(device.prompt) + device.sendline('ip a') + + device.expect(device.prompt) + +def install_ovpn_client(device, remove=False): + ''' + Un/Install the OpenVPN client + To run the client as a daemon use: + openvpn --daemon vpn --log ovpn.log --config ./<user>.ovpn + ''' + if remove: + device.sendline('killall -9 openvpn') + device.expect(device.prompt) + device.sendline('apt remove openvpn -y') + device.expect(device.prompt, timeout=120) + return + + device.sendline('apt-get update') + device.expect(device.prompt) + device.sendline('apt-get install openvpn -y') + device.expect(device.prompt, timeout=90) + +def install_pptpd_server(device, remove=False): + ''' + Un/Install the pptpd + ''' + import pexpect + device.expect([pexpect.TIMEOUT]+device.prompt, timeout=5) + device.sendline('ls -l /usr/sbin/pptpd') + index = device.expect(['(\\s{1,}\\d{4}()\\s{1,}\\/etc\\/init\\.d\\/openvpn)'] + device.prompt, timeout=90) + if remove: + if index == 0: + device.sendline("/etc/init.d/pptpd stop") + device.expect(device.prompt, timeout=60) + device.sendline("apt-get remove pptpd -y") + device.expect(device.prompt, timeout=60) + return + + if index != 0: + device.sendline("apt-get install pptpd -y") + device.expect(device.prompt, timeout=90) + + device.sendline("/etc/init.d/pptpd restart") + device.expect(device.prompt, timeout=60) + +def install_pptp_client(device, remove=False): + ''' + Un/Install the pptp-linux package + ''' + device.sendline('pptp --version') + index = device.expect(['pptp version'] + device.prompt, timeout=90) + + if remove: + if index == 0: + device.expect(device.prompt) + device.sendline("poff pptpserver") + device.expect(device.prompt) + device.sendline('apt-get remove pptp-linux -y') + device.expect(device.prompt, timeout=60) + return + + if index != 0: + device.sendline('apt-get install pptp-linux -y') + + device.expect(device.prompt, timeout=60) + +def install_postfix(device): + '''Install postfix server if not present.''' + device.sendline('postconf -d | grep mail_version') + try: + device.expect('mail_version =', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get update') # Update inetd before installation + device.expect(device.prompt, timeout=90) + device.sendline("apt-get install postfix -y") + install_settings = device.expect(['General type of mail configuration:'] + ['Errors were encountered'] + device.prompt, timeout = 120) + print(install_settings) + if install_settings ==0: + device.sendline("2") + assert 0 == device.expect(['System mail name:']+ device.prompt, timeout = 90), "System mail name option is note received. Installaion failed" + device.sendline("testingsmtp.com") + assert 0 != device.expect(['Errors were encountered']+ device.prompt, timeout = 90), "Errors Encountered. Installaion failed" + + elif install_settings ==1: + assert 0 != 1, "Errors Encountered. Installaion failed" + + elif install_settings ==2: + device.sendline('postconf -d | grep mail_version') + device.expect('mail_version =', timeout=5) + + device.sendline("service postfix start") + assert 0 != device.expect(['failed']+ device.prompt, timeout = 90), "Unable to start Postfix service.Service is not properly installed" + +def install_asterisk(device): + '''Install asterisk if not present.''' + device.sendline('apt list --installed | grep -i asterisk') + try: + device.expect(['asterisk/']) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install asterisk -y 2>&1 & ') + device.expect(device.prompt) + for not_used in range(100): + device.sendline('apt list --installed | grep -i asterisk') + idx = device.expect(['asterisk/'] + device.prompt) + if idx == 0: + device.expect(device.prompt) + break + if not_used > 99: + assert 0,"Failed to install asterisk" + +def install_make(device): + '''Install make if not present.''' + device.sendline('apt list --installed | grep -i make') + try: + device.expect('make/', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install make -y') + device.expect(['make/'] + device.prompt, timeout=60) + +def install_gcc(device): + '''Install make if not present.''' + device.sendline('apt list --installed | grep -i gcc') + try: + device.expect('gcc/', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install gcc -y') + device.expect(['gcc/'] + device.prompt, timeout=60) + +def install_pkgconfig(device): + '''Install make if not present.''' + device.sendline('apt list --installed | grep -i pkg-config') + try: + device.expect('pkg\-config/', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install pkg-config -y') + device.expect(['pkg\-config/'] + device.prompt, timeout=60) + +def install_libsound2dev(device): + '''Install make if not present.''' + device.sendline('apt list --installed | grep -i libasound2-dev') + try: + device.expect('libasound2\-dev/', timeout=5) + device.expect(device.prompt) + except: + device.expect(device.prompt) + device.sendline('apt-get install libasound2-dev -y') + device.expect(['libasound2\-dev/'] + device.prompt, timeout=100) + +def install_pjsua(device): + '''Install softphone if not present.''' + try: + device.sendline('ldconfig -p | grep pj') + device.expect(['libpjsua\.so\.2\s\(libc6\,x86\-64\)']) + device.expect(device.prompt) + except: + install_make(device) + install_gcc(device) + install_pkgconfig(device) + install_libsound2dev(device) + install_wget(device) + device.sendline('rm -r pjpr*') + device.expect(device.prompt,timeout=70) + device.sendline('wget http://www.pjsip.org/release/2.6/pjproject-2.6.tar.bz2') + device.expect(device.prompt,timeout=100) + device.sendline('tar -xjf pjproject-2.6.tar.bz2') + device.expect(device.prompt,timeout=70) + device.sendline('cd pjproject-2.6') + device.expect(device.prompt) + device.sendline('./configure && make dep && make && make install 2>&1 & ') + device.expect(device.prompt) + for not_used in range(100): + import pexpect + device.expect(pexpect.TIMEOUT, timeout=10) + device.sendline('ldconfig -p | grep pj') + idx = device.expect(['libpjsua\.so\.2\s\(libc6\,x86\-64\)'] + device.prompt) + if idx == 0: + device.expect(device.prompt) + break + if not_used > 99: + assert 0,"Failed to install pjsua" diff --git a/boardfarm/tests/lib/network_helper.py b/boardfarm/tests/lib/network_helper.py new file mode 100644 index 00000000..371660ea --- /dev/null +++ b/boardfarm/tests/lib/network_helper.py @@ -0,0 +1,48 @@ +# Copyright (c) 2019 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +from netaddr import * +import re +import ipaddress + +def mac_to_snmp_format(mac_addr): + mac_tmp = re.sub("[\s\.\-]", "", mac_addr) + mac = EUI(mac_tmp, dialect=mac_unix) + mac_final = str(mac).upper() + + return mac_final + +def ipv4_to_snmp_format(ipv4_str): + ipv4_tmp = re.sub("[\s\.\-]", "", ipv4_str) + ipv4_decimal = int(ipv4_tmp, 16) + ipv4_format = ipaddress.IPv4Address(ipv4_decimal) + ipv4_address = ipaddress.ip_address(u'%s' % ipv4_format) + + return ipv4_address + +def ipv6_to_snmp_format(ipv6_str): + ipv6_tmp = re.sub("[\s\.\-]", "", ipv6_str) + pattern = re.compile('.{4}') + ipv6_tmp_ip = ':'.join(pattern.findall(ipv6_tmp)) + ipv6_address = ipaddress.ip_address(u'%s' % ipv6_tmp_ip) + + return ipv6_address + +def valid_ipv4(ip_str): + try: + ipaddress.IPv4Address(unicode(ip_str)) + return True + except: + return False + +def valid_ipv6(ip_str): + try: + ipaddress.IPv6Address(unicode(ip_str)) + return True + except: + return False diff --git a/boardfarm/tests/lib/network_testing.py b/boardfarm/tests/lib/network_testing.py new file mode 100644 index 00000000..ac812049 --- /dev/null +++ b/boardfarm/tests/lib/network_testing.py @@ -0,0 +1,93 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import re + +def tcpdump_capture(device, interface, port=None, capture_file='pkt_capture.pcap'): + if port == None: + device.sudo_sendline("tcpdump -i %s -n -w %s &" %(interface, capture_file)) + else: + device.sudo_sendline("tcpdump -i %s -n \'portrange %s\' -w %s &" %(interface, port, capture_file)) + device.expect(device.prompt) + return device.before + +def kill_process(device, process="tcpdump"): + device.sudo_sendline("killall %s" %process) + device.expect(device.prompt) + device.sudo_sendline("sync") + device.expect(device.prompt) + return device.before + +def tcpdump_read(device, capture_file): + device.sudo_sendline("tcpdump -n -r %s" %(capture_file)) + device.expect(device.prompt) + output = device.before + device.sudo_sendline("rm %s" %(capture_file)) + device.expect(device.prompt) + return output + +def nmap_cli(device, ip_address, port, protocol=None, retry="0"): + if protocol == "tcp": + device.sudo_sendline("nmap -sS %s -p %s -Pn -r -max-retries %s" %(ip_address,port,retry)) + elif protocol == "udp": + device.sudo_sendline("nmap -sU %s -p %s -Pn -r -max-retries %s" %(ip_address,port,retry)) + else: + device.sudo_sendline("nmap -sS -sU %s -p %s -Pn -r -max-retries %s" %(ip_address,port,retry)) + device.expect(device.prompt,timeout=200) + return device.before + +def ping(device, ping_ip, ping_interface=None, count=4): + if ping_interface == None: + device.sudo_sendline("ping -c %s %s"%(count,ping_ip)) + else: + device.sudo_sendline("ping -I %s -c %s %s"%(ping_interface,count,ping_ip)) + device.expect(device.prompt, timeout=50) + match = re.search("%s packets transmitted, %s received, 0%% packet loss" % (count, count), device.before) + if match: + return True + else: + return False + +def ssh_service_verify(device, dest_device, ip, opts="", ssh_key="-oKexAlgorithms=+diffie-hellman-group1-sha1"): + """ + This function assumes that the server does not know the identity of the client!!!!! + I.e. no passwordless login + """ + device.sendline("ssh %s@%s" %(dest_device.username, ip)) + try: + idx = device.expect(['no matching key exchange method found']+ ['(yes/no)']+ ['assword:'], timeout=60) + if idx == 0: + device.expect(device.prompt) + device.sendline("ssh %s %s@%s %s" %(ssh_key, dest_device.username, ip, opts)) + idx = device.expect(['(yes/no)'] + ['assword:'], timeout=60) + if idx == 0: + idx = 1 + if idx == 1: + device.sendline('yes') + device.expect("assword:") + device.sendline(dest_device.password) + device.expect(dest_device.prompt) + device.sendline("exit") + device.expect(device.prompt, timeout=20) + except Exception as e: + print(e) + raise Exception("Failed to connect SSH to :%s" %device.before) + +def telnet_service_verify(device, dest_device, ip, opts=""): + device.sendline("telnet%s %s" %(opts, ip)) + try: + device.expect(["Username:"], timeout=60) + device.sendline(dest_device.username) + device.expect(["assword:"]) + device.sendline(dest_device.password) + device.expect(dest_device.prompt, timeout=40) + device.sendline("exit") + device.expect(device.prompt, timeout=20) + except Exception as e: + print(e) + raise Exception("Failed to connect telnet to :%s" %device.before) diff --git a/boardfarm/tests/lib/randomMAC.py b/boardfarm/tests/lib/randomMAC.py new file mode 100755 index 00000000..ee8c3cbd --- /dev/null +++ b/boardfarm/tests/lib/randomMAC.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import random + +def randomMAC(): + mac = [ (random.randint(0x00,0xff) & 0xfe), # the lsb is 0, i.e. no multicat bit + random.randint(0x00, 0xff), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff) ] + mac_to_be_decided = ':'.join(map(lambda x : hex(x)[2:].lstrip("0x").zfill(2),mac)) + + return (mac_to_be_decided) + +if __name__ == '__main__': + print(randomMAC()) + diff --git a/boardfarm/tests/lib/regexlib.py b/boardfarm/tests/lib/regexlib.py new file mode 100644 index 00000000..0df553e5 --- /dev/null +++ b/boardfarm/tests/lib/regexlib.py @@ -0,0 +1,65 @@ +# Copyright (c) 2018 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + + +# Restrict all 4 numbers in the IP address to 0..255. It stores each of the 4 +# numbers of the IP address into a capturing group. These groups can be used +# to further process the IP number. +ValidIpv4AddressRegex='(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' +ValidIpv4AddressRegexWordBound='\b'+ValidIpv4AddressRegex+'\b' + +# IPv6 text representation of addresses without compression from RFC 1884. This +# regular expression doesn't allow IPv6 compression ("::") or mixed +# IPv4 addresses. +# Matches: FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 | 1080:0:0:0:8:800:200C:417A | 0:0:0:0:0:0:0:1 +ValidIpv6AddressRegex='([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}' + +# IPv6 text representation of addresses with compression: +# Matches: +# FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 +# 1080:0:0:0:8:800:200C:417A +# 0:0:0:0:0:0:0:1 +# fd42:42:42:42::1 +# 1762:0:0:0:0:B03:1:AF18 +# 1762:0:0:0:0:B03:127.32.67.1 +# 1762::B03:1:AF18 +# 762::B03:127.32.67.15 +# 2001:0000:1234:0000:0000:C1C0:ABCD:0876 +# fe80:0:0:0:204:61ff:fe9d:f156 +# fe80::204:61ff:fe9d:f156 +# fe80:0000:0000:0000:0204:61ff:254.157.241.86 +# fe80:0:0:0:0204:61ff:254.157.241.86 +# fe80::204:61ff:254.157.241.86 +# ::1 +# fe80:: +# 2001:: +AllValidIpv6AddressesRegex='(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))' + + +# The CMTS mac-adderss format for e.g. 0025.2e34.4377 +CmtsMacFormat='([0-9a-f]{4}\.[0-9a-fA-F]{4}\.[0-9a-f]{4})' + +LinuxMacFormat='([0-9a-f]{2}(?::[0-9a-f]{2}){5})' + + +# traceroute returns no route to ip address (i.e. '<num> * * *' 30 times) +TracerouteNoRoute='((.[1-9]|[1-9][0-9])(\s\s\*\s\*\s\*)(\r\n|\r|\n)){30}' + +#Grep hex string format of Mac address in SNMP output +#Matches eg:F4 6D 04 61 74 E0 +SNMPMacAddressRegex = '([0-9A-Z][0-9A-Z]\s){6}' + +''' +This will match the follwing: + +Trying x.x.x.x... +Connected to x.x.x.x. +Escape character is '^]'. + +''' +telnet_ipv4_conn="Trying "+ValidIpv4AddressRegex+"\.\.\.\r\nConnected to "+ValidIpv4AddressRegex+"\.\r\nEscape character is '\^]'\.((\r\n){,2})" + diff --git a/boardfarm/tests/lib/scripts/openvpn-install.sh b/boardfarm/tests/lib/scripts/openvpn-install.sh new file mode 100644 index 00000000..d02ffe4a --- /dev/null +++ b/boardfarm/tests/lib/scripts/openvpn-install.sh @@ -0,0 +1,1207 @@ +#!/bin/bash + +# Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Fedora and Arch Linux +# https://github.com/angristan/openvpn-install + +function isRoot () { + if [ "$EUID" -ne 0 ]; then + return 1 + fi +} + +function tunAvailable () { + if [ ! -e /dev/net/tun ]; then + return 1 + fi +} + +function checkOS () { + if [[ -e /etc/debian_version ]]; then + OS="debian" + source /etc/os-release + + if [[ "$ID" == "debian" ]]; then + if [[ ! $VERSION_ID =~ (8|9) ]]; then + echo "⚠️ Your version of Debian is not supported." + echo "" + echo "However, if you're using Debian >= 9 or unstable/testing then you can continue." + echo "Keep in mind they are not supported, though." + echo "" + until [[ $CONTINUE =~ (y|n) ]]; do + read -rp "Continue? [y/n]: " -e CONTINUE + done + if [[ "$CONTINUE" = "n" ]]; then + exit 1 + fi + fi + elif [[ "$ID" == "ubuntu" ]];then + OS="ubuntu" + if [[ ! $VERSION_ID =~ (16.04|18.04) ]]; then + echo "⚠️ Your version of Ubuntu is not supported." + echo "" + echo "However, if you're using Ubuntu > 17 or beta, then you can continue." + echo "Keep in mind they are not supported, though." + echo "" + until [[ $CONTINUE =~ (y|n) ]]; do + read -rp "Continue? [y/n]: " -e CONTINUE + done + if [[ "$CONTINUE" = "n" ]]; then + exit 1 + fi + fi + fi + elif [[ -e /etc/fedora-release ]]; then + OS=fedora + elif [[ -e /etc/centos-release ]]; then + if ! grep -qs "^CentOS Linux release 7" /etc/centos-release; then + echo "Your version of CentOS is not supported." + echo "The script only support CentOS 7." + echo "" + unset CONTINUE + until [[ $CONTINUE =~ (y|n) ]]; do + read -rp "Continue anyway? [y/n]: " -e CONTINUE + done + if [[ "$CONTINUE" = "n" ]]; then + echo "Ok, bye!" + exit 1 + fi + fi + OS=centos + elif [[ -e /etc/arch-release ]]; then + OS=arch + else + echo "Looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS or Arch Linux system" + exit 1 + fi +} + +function initialCheck () { + if ! isRoot; then + echo "Sorry, you need to run this as root" + exit 1 + fi + if ! tunAvailable; then + echo "TUN is not available" + exit 1 + fi + checkOS +} + +function installUnbound () { + if [[ ! -e /etc/unbound/unbound.conf ]]; then + + if [[ "$OS" =~ (debian|ubuntu) ]]; then + apt-get install -y unbound + + # Configuration + echo 'interface: 10.8.0.1 +access-control: 10.8.0.1/24 allow +hide-identity: yes +hide-version: yes +use-caps-for-id: yes +prefetch: yes' >> /etc/unbound/unbound.conf + + elif [[ "$OS" = "centos" ]]; then + yum install -y unbound + + # Configuration + sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf + sed -i 's|# access-control: 127.0.0.0/8 allow|access-control: 10.8.0.1/24 allow|' /etc/unbound/unbound.conf + sed -i 's|# hide-identity: no|hide-identity: yes|' /etc/unbound/unbound.conf + sed -i 's|# hide-version: no|hide-version: yes|' /etc/unbound/unbound.conf + sed -i 's|use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf + + elif [[ "$OS" = "fedora" ]]; then + dnf install -y unbound + + # Configuration + sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf + sed -i 's|# access-control: 127.0.0.0/8 allow|access-control: 10.8.0.1/24 allow|' /etc/unbound/unbound.conf + sed -i 's|# hide-identity: no|hide-identity: yes|' /etc/unbound/unbound.conf + sed -i 's|# hide-version: no|hide-version: yes|' /etc/unbound/unbound.conf + sed -i 's|# use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf + + elif [[ "$OS" = "arch" ]]; then + pacman -Syu --noconfirm unbound + + # Get root servers list + curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache + + mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old + + echo 'server: + use-syslog: yes + do-daemonize: no + username: "unbound" + directory: "/etc/unbound" + trust-anchor-file: trusted-key.key + root-hints: root.hints + interface: 10.8.0.1 + access-control: 10.8.0.1/24 allow + port: 53 + num-threads: 2 + use-caps-for-id: yes + harden-glue: yes + hide-identity: yes + hide-version: yes + qname-minimisation: yes + prefetch: yes' > /etc/unbound/unbound.conf + fi + + if [[ ! "$OS" =~ (fedora|centos) ]];then + # DNS Rebinding fix + echo "private-address: 10.0.0.0/8 +private-address: 172.16.0.0/12 +private-address: 192.168.0.0/16 +private-address: 169.254.0.0/16 +private-address: fd00::/8 +private-address: fe80::/10 +private-address: 127.0.0.0/8 +private-address: ::ffff:0:0/96" >> /etc/unbound/unbound.conf + fi + else # Unbound is already installed + echo 'include: /etc/unbound/openvpn.conf' >> /etc/unbound/unbound.conf + + # Add Unbound 'server' for the OpenVPN subnet + echo 'server: +interface: 10.8.0.1 +access-control: 10.8.0.1/24 allow +hide-identity: yes +hide-version: yes +use-caps-for-id: yes +prefetch: yes +private-address: 10.0.0.0/8 +private-address: 172.16.0.0/12 +private-address: 192.168.0.0/16 +private-address: 169.254.0.0/16 +private-address: fd00::/8 +private-address: fe80::/10 +private-address: 127.0.0.0/8 +private-address: ::ffff:0:0/96' > /etc/unbound/openvpn.conf + fi + + systemctl enable unbound + systemctl restart unbound +} + +function installQuestions () { + echo "Welcome to the OpenVPN installer!" + echo "The git repository is available at: https://github.com/angristan/openvpn-install" + echo "" + + echo "I need to ask you a few questions before starting the setup." + echo "You can leave the default options and just press enter if you are ok with them." + echo "" + echo "I need to know the IPv4 address of the network interface you want OpenVPN listening to." + echo "Unless your server is behind NAT, it should be your public IPv4 address." + + # Detect public IPv4 address and pre-fill for the user + IP=$(ip addr | grep 'inet' | grep -v inet6 | grep -vE '127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | head -1) + read -rp "IP address: " -e -i "$IP" IP + # If $IP is a private IP address, the server must be behind NAT + if echo "$IP" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)'; then + echo "" + echo "It seems this server is behind NAT. What is its public IPv4 address or hostname?" + echo "We need it for the clients to connect to the server." + until [[ "$PUBLICIP" != "" ]]; do + read -rp "Public IPv4 address or hostname: " -e PUBLICIP + done + fi + + echo "" + echo "Checking for IPv6 connectivity..." + echo "" + # "ping6" and "ping -6" availability varies depending on the distribution + if type ping6 > /dev/null 2>&1; then + PING6="ping6 -c3 ipv6.google.com > /dev/null 2>&1" + else + PING6="ping -6 -c3 ipv6.google.com > /dev/null 2>&1" + fi + if eval "$PING6"; then + echo "Your host appears to have IPv6 connectivity." + SUGGESTION="y" + else + echo "Your host does not appear to have IPv6 connectivity." + SUGGESTION="n" + fi + echo "" + # Ask the user if they want to enable IPv6 regardless its availability. + until [[ $IPV6_SUPPORT =~ (y|n) ]]; do + read -rp "Do you want to enable IPv6 support (NAT)? [y/n]: " -e -i $SUGGESTION IPV6_SUPPORT + done + echo "" + echo "What port do you want OpenVPN to listen to?" + echo " 1) Default: 1194" + echo " 2) Custom" + echo " 3) Random [49152-65535]" + until [[ "$PORT_CHOICE" =~ ^[1-3]$ ]]; do + read -rp "Port choice [1-3]: " -e -i 1 PORT_CHOICE + done + case $PORT_CHOICE in + 1) + PORT="1194" + ;; + 2) + until [[ "$PORT" =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; do + read -rp "Custom port [1-65535]: " -e -i 1194 PORT + done + ;; + 3) + # Generate random number within private ports range + PORT=$(shuf -i49152-65535 -n1) + echo "Random Port: $PORT" + ;; + esac + echo "" + echo "What protocol do you want OpenVPN to use?" + echo "UDP is faster. Unless it is not available, you shouldn't use TCP." + echo " 1) UDP" + echo " 2) TCP" + until [[ "$PROTOCOL_CHOICE" =~ ^[1-2]$ ]]; do + read -rp "Protocol [1-2]: " -e -i 1 PROTOCOL_CHOICE + done + case $PROTOCOL_CHOICE in + 1) + PROTOCOL="udp" + ;; + 2) + PROTOCOL="tcp" + ;; + esac + echo "" + echo "What DNS resolvers do you want to use with the VPN?" + echo " 1) Current system resolvers (from /etc/resolv.conf)" + echo " 2) Self-hosted DNS Resolver (Unbound)" + echo " 3) Cloudflare (Anycast: worldwide)" + echo " 4) Quad9 (Anycast: worldwide)" + echo " 5) Quad9 uncensored (Anycast: worldwide)" + echo " 6) FDN (France)" + echo " 7) DNS.WATCH (Germany)" + echo " 8) OpenDNS (Anycast: worldwide)" + echo " 9) Google (Anycast: worldwide)" + echo " 10) Yandex Basic (Russia)" + echo " 11) AdGuard DNS (Russia)" + until [[ "$DNS" =~ ^[0-9]+$ ]] && [ "$DNS" -ge 1 ] && [ "$DNS" -le 11 ]; do + read -rp "DNS [1-10]: " -e -i 3 DNS + if [[ $DNS == 2 ]] && [[ -e /etc/unbound/unbound.conf ]]; then + echo "" + echo "Unbound is already installed." + echo "You can allow the script to configure it in order to use it from your OpenVPN clients" + echo "We will simply add a second server to /etc/unbound/unbound.conf for the OpenVPN subnet." + echo "No changes are made to the current configuration." + echo "" + + until [[ $CONTINUE =~ (y|n) ]]; do + read -rp "Apply configuration changes to Unbound? [y/n]: " -e CONTINUE + done + if [[ $CONTINUE = "n" ]];then + # Break the loop and cleanup + unset DNS + unset CONTINUE + fi + fi + done + echo "" + echo "Do you want to use compression? It is not recommended since the VORACLE attack make use of it." + until [[ $COMPRESSION_ENABLED =~ (y|n) ]]; do + read -rp"Enable compression? [y/n]: " -e -i n COMPRESSION_ENABLED + done + if [[ $COMPRESSION_ENABLED == "y" ]];then + echo "Choose which compression algorithm you want to use:" + echo " 1) LZ4 (more efficient)" + echo " 2) LZ0" + until [[ $COMPRESSION_CHOICE =~ ^[1-2]$ ]]; do + read -rp"Compression algorithm [1-2]: " -e -i 1 COMPRESSION_CHOICE + done + case $COMPRESSION_CHOICE in + 1) + COMPRESSION_ALG="lz4" + ;; + 2) + COMPRESSION_ALG="lzo" + ;; + esac + fi + echo "" + echo "Do you want to customize encryption settings?" + echo "Unless you know what you're doing, you should stick with the default parameters provided by the script." + echo "Note that whatever you choose, all the choices presented in the script are safe. (Unlike OpenVPN's defaults)" + echo "See https://github.com/angristan/openvpn-install#security-and-encryption to learn more." + echo "" + until [[ $CUSTOMIZE_ENC =~ (y|n) ]]; do + read -rp "Customize encryption settings? [y/n]: " -e -i n CUSTOMIZE_ENC + done + if [[ $CUSTOMIZE_ENC == "n" ]];then + # Use default, sane and fast parameters + CIPHER="AES-128-GCM" + CERT_TYPE="1" # ECDSA + CERT_CURVE="prime256v1" + CC_CIPHER="TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256" + DH_TYPE="1" # ECDH + DH_CURVE="prime256v1" + HMAC_ALG="SHA256" + TLS_SIG="1" # tls-crypt + else + echo "" + echo "Choose which cipher you want to use for the data channel:" + echo " 1) AES-128-GCM (recommended)" + echo " 2) AES-192-GCM" + echo " 3) AES-256-GCM" + echo " 4) AES-128-CBC" + echo " 5) AES-192-CBC" + echo " 6) AES-256-CBC" + until [[ "$CIPHER_CHOICE" =~ ^[1-6]$ ]]; do + read -rp "Cipher [1-6]: " -e -i 1 CIPHER_CHOICE + done + case $CIPHER_CHOICE in + 1) + CIPHER="AES-128-GCM" + ;; + 2) + CIPHER="AES-192-GCM" + ;; + 3) + CIPHER="AES-256-GCM" + ;; + 4) + CIPHER="AES-128-CBC" + ;; + 5) + CIPHER="AES-192-CBC" + ;; + 6) + CIPHER="AES-256-CBC" + ;; + esac + echo "" + echo "Choose what kind of certificate you want to use:" + echo " 1) ECDSA (recommended)" + echo " 2) RSA" + until [[ $CERT_TYPE =~ ^[1-2]$ ]]; do + read -rp"Certificate key type [1-2]: " -e -i 1 CERT_TYPE + done + case $CERT_TYPE in + 1) + echo "" + echo "Choose which curve you want to use for the certificate's key:" + echo " 1) prime256v1 (recommended)" + echo " 2) secp384r1" + echo " 3) secp521r1" + until [[ $CERT_CURVE_CHOICE =~ ^[1-3]$ ]]; do + read -rp"Curve [1-3]: " -e -i 1 CERT_CURVE_CHOICE + done + case $CERT_CURVE_CHOICE in + 1) + CERT_CURVE="prime256v1" + ;; + 2) + CERT_CURVE="secp384r1" + ;; + 3) + CERT_CURVE="secp521r1" + ;; + esac + ;; + 2) + echo "" + echo "Choose which size you want to use for the certificate's RSA key:" + echo " 1) 2048 bits (recommended)" + echo " 2) 3072 bits" + echo " 3) 4096 bits" + until [[ "$RSA_KEY_SIZE_CHOICE" =~ ^[1-3]$ ]]; do + read -rp "RSA key size [1-3]: " -e -i 1 RSA_KEY_SIZE_CHOICE + done + case $RSA_KEY_SIZE_CHOICE in + 1) + RSA_KEY_SIZE="2048" + ;; + 2) + RSA_KEY_SIZE="3072" + ;; + 3) + RSA_KEY_SIZE="4096" + ;; + esac + ;; + esac + echo "" + echo "Choose which cipher you want to use for the control channel:" + case $CERT_TYPE in + 1) + echo " 1) ECDHE-ECDSA-AES-128-GCM-SHA256 (recommended)" + echo " 2) ECDHE-ECDSA-AES-256-GCM-SHA384" + until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do + read -rp"Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + done + case $CC_CIPHER_CHOICE in + 1) + CC_CIPHER="TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256" + ;; + 2) + CC_CIPHER="TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384" + ;; + esac + ;; + 2) + echo " 1) ECDHE-RSA-AES-128-GCM-SHA256 (recommended)" + echo " 2) ECDHE-RSA-AES-256-GCM-SHA384" + until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do + read -rp"Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + done + case $CC_CIPHER_CHOICE in + 1) + CC_CIPHER="TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256" + ;; + 2) + CC_CIPHER="TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384" + ;; + esac + ;; + esac + echo "" + echo "Choose what kind of Diffie-Hellman key you want to use:" + echo " 1) ECDH (recommended)" + echo " 2) DH" + until [[ $DH_TYPE =~ [1-2] ]]; do + read -rp"DH key type [1-2]: " -e -i 1 DH_TYPE + done + case $DH_TYPE in + 1) + echo "" + echo "Choose which curve you want to use for the ECDH key:" + echo " 1) prime256v1 (recommended)" + echo " 2) secp384r1" + echo " 3) secp521r1" + while [[ $DH_CURVE_CHOICE != "1" && $DH_CURVE_CHOICE != "2" && $DH_CURVE_CHOICE != "3" ]]; do + read -rp"Curve [1-3]: " -e -i 1 DH_CURVE_CHOICE + done + case $DH_CURVE_CHOICE in + 1) + DH_CURVE="prime256v1" + ;; + 2) + DH_CURVE="secp384r1" + ;; + 3) + DH_CURVE="secp521r1" + ;; + esac + ;; + 2) + echo "" + echo "Choose what size of Diffie-Hellman key you want to use:" + echo " 1) 2048 bits (recommended)" + echo " 2) 3072 bits" + echo " 3) 4096 bits" + until [[ "$DH_KEY_SIZE_CHOICE" =~ ^[1-3]$ ]]; do + read -rp "DH key size [1-3]: " -e -i 1 DH_KEY_SIZE_CHOICE + done + case $DH_KEY_SIZE_CHOICE in + 1) + DH_KEY_SIZE="2048" + ;; + 2) + DH_KEY_SIZE="3072" + ;; + 3) + DH_KEY_SIZE="4096" + ;; + esac + ;; + esac + echo "" + # The "auth" options behaves differently with AEAD ciphers + if [[ "$CIPHER" =~ CBC$ ]]; then + echo "The digest algorithm authenticates data channel packets and tls-auth packets from the control channel." + elif [[ "$CIPHER" =~ GCM$ ]]; then + echo "The digest algorithm authenticates tls-auth packets from the control channel." + fi + echo "Which digest algorithm do you want to use for HMAC?" + echo " 1) SHA-256 (recommended)" + echo " 2) SHA-384" + echo " 3) SHA-512" + until [[ $HMAC_ALG_CHOICE =~ ^[1-3]$ ]]; do + read -rp "Digest algorithm [1-3]: " -e -i 1 HMAC_ALG_CHOICE + done + case $HMAC_ALG_CHOICE in + 1) + HMAC_ALG="SHA256" + ;; + 2) + HMAC_ALG="SHA384" + ;; + 3) + HMAC_ALG="SHA512" + ;; + esac + echo "" + echo "You can add an additional layer of security to the control channel with tls-auth and tls-crypt" + echo "tls-auth authenticates the packets, while tls-crypt authenticate and encrypt them." + echo " 1) tls-crypt (recommended)" + echo " 2) tls-auth" + until [[ $TLS_SIG =~ [1-2] ]]; do + read -rp "Control channel additional security mechanism [1-2]: " -e -i 1 TLS_SIG + done + fi + echo "" + echo "Okay, that was all I needed. We are ready to setup your OpenVPN server now." + echo "You will be able to generate a client at the end of the installation." + read -n1 -r -p "Press any key to continue..." +} + +function installOpenVPN () { + # Run setup questions first + installQuestions + + # Get the "public" interface from the default route + NIC=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -1) + + if [[ "$OS" =~ (debian|ubuntu) ]]; then + apt-get update + apt-get -y install ca-certificates gnupg + # We add the OpenVPN repo to get the latest version. + if [[ "$VERSION_ID" = "8" ]]; then + echo "deb http://build.openvpn.net/debian/openvpn/stable jessie main" > /etc/apt/sources.list.d/openvpn.list + wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add - + apt-get update + fi + if [[ "$VERSION_ID" = "16.04" ]]; then + echo "deb http://build.openvpn.net/debian/openvpn/stable trusty main" > /etc/apt/sources.list.d/openvpn.list + wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add - + apt-get update + fi + # Ubuntu > 16.04 and Debian > 8 have OpenVPN >= 2.4 without the need of a third party repository. + apt-get install -y openvpn iptables openssl wget ca-certificates curl + elif [[ "$OS" = 'centos' ]]; then + yum install -y epel-release + yum install -y openvpn iptables openssl wget ca-certificates curl + elif [[ "$OS" = 'fedora' ]]; then + dnf install -y openvpn iptables openssl wget ca-certificates curl + elif [[ "$OS" = 'arch' ]]; then + echo "" + echo "WARNING: As you're using ArchLinux, I need to update the packages on your system to install those I need." + echo "Not doing that could cause problems between dependencies, or missing files in repositories (Arch Linux does not support partial upgrades)." + echo "" + echo "Continuing will update your installed packages and install needed ones." + echo "" + unset CONTINUE + until [[ $CONTINUE =~ (y|n) ]]; do + read -rp "Continue? [y/n]: " -e -i y CONTINUE + done + if [[ "$CONTINUE" = "n" ]]; then + echo "Exiting because user did not permit updating the system." + exit 4 + fi + + # Install required dependencies and upgrade the system + pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl + fi + + # Find out if the machine uses nogroup or nobody for the permissionless group + if grep -qs "^nogroup:" /etc/group; then + NOGROUP=nogroup + else + NOGROUP=nobody + fi + + # An old version of easy-rsa was available by default in some openvpn packages + if [[ -d /etc/openvpn/easy-rsa/ ]]; then + rm -rf /etc/openvpn/easy-rsa/ + fi + + # Install the latest version of easy-rsa from source + local version="3.0.5" + wget -O ~/EasyRSA-nix-${version}.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-nix-${version}.tgz + tar xzf ~/EasyRSA-nix-${version}.tgz -C ~/ + mv ~/EasyRSA-${version}/ /etc/openvpn/ + mv /etc/openvpn/EasyRSA-${version}/ /etc/openvpn/easy-rsa/ + chown -R root:root /etc/openvpn/easy-rsa/ + rm -f ~/EasyRSA-nix-${version}.tgz + + cd /etc/openvpn/easy-rsa/ + case $CERT_TYPE in + 1) + echo "set_var EASYRSA_ALGO ec" > vars + echo "set_var EASYRSA_CURVE $CERT_CURVE" >> vars + ;; + 2) + echo "set_var EASYRSA_KEY_SIZE $RSA_KEY_SIZE" > vars + ;; + esac + + # Generate a random, alphanumeric identifier of 16 characters for CN and one for server name + SERVER_CN="cn_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" + SERVER_NAME="server_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" + echo "set_var EASYRSA_REQ_CN $SERVER_CN" >> vars + # Create the PKI, set up the CA, the DH params and the server certificate + ./easyrsa init-pki + ./easyrsa --batch build-ca nopass + + if [[ $DH_TYPE == "2" ]]; then + # ECDH keys are generated on-the-fly so we don't need to generate them beforehand + openssl dhparam -out dh.pem $DH_KEY_SIZE + fi + + ./easyrsa build-server-full "$SERVER_NAME" nopass + EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl + + case $TLS_SIG in + 1) + # Generate tls-crypt key + openvpn --genkey --secret /etc/openvpn/tls-crypt.key + ;; + 2) + # Generate tls-auth key + openvpn --genkey --secret /etc/openvpn/tls-auth.key + ;; + esac + + # Move all the generated files + cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn + if [[ $DH_TYPE == "2" ]]; then + cp dh.pem /etc/openvpn + fi + + # Make cert revocation list readable for non-root + chmod 644 /etc/openvpn/crl.pem + + # Generate server.conf + echo "port $PORT" > /etc/openvpn/server.conf + if [[ "$IPV6_SUPPORT" = 'n' ]]; then + echo "proto $PROTOCOL" >> /etc/openvpn/server.conf + elif [[ "$IPV6_SUPPORT" = 'y' ]]; then + echo "proto ${PROTOCOL}6" >> /etc/openvpn/server.conf + fi + + echo "dev tun +user nobody +group $NOGROUP +persist-key +persist-tun +keepalive 10 120 +topology subnet +server 10.8.0.0 255.255.255.0 +ifconfig-pool-persist ipp.txt" >> /etc/openvpn/server.conf + + # DNS resolvers + case $DNS in + 1) + # Locate the proper resolv.conf + # Needed for systems running systemd-resolved + if grep -q "127.0.0.53" "/etc/resolv.conf"; then + RESOLVCONF='/run/systemd/resolve/resolv.conf' + else + RESOLVCONF='/etc/resolv.conf' + fi + # Obtain the resolvers from resolv.conf and use them for OpenVPN + grep -v '#' $RESOLVCONF | grep 'nameserver' | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | while read -r line; do + echo "push \"dhcp-option DNS $line\"" >> /etc/openvpn/server.conf + done + ;; + 2) + echo 'push "dhcp-option DNS 10.8.0.1"' >> /etc/openvpn/server.conf + ;; + 3) # Cloudflare + echo 'push "dhcp-option DNS 1.0.0.1"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 1.1.1.1"' >> /etc/openvpn/server.conf + ;; + 4) # Quad9 + echo 'push "dhcp-option DNS 9.9.9.9"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 149.112.112.112"' >> /etc/openvpn/server.conf + ;; + 5) # Quad9 uncensored + echo 'push "dhcp-option DNS 9.9.9.10"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 149.112.112.10"' >> /etc/openvpn/server.conf + ;; + 6) # FDN + echo 'push "dhcp-option DNS 80.67.169.40"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 80.67.169.12"' >> /etc/openvpn/server.conf + ;; + 7) # DNS.WATCH + echo 'push "dhcp-option DNS 84.200.69.80"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 84.200.70.40"' >> /etc/openvpn/server.conf + ;; + 8) # OpenDNS + echo 'push "dhcp-option DNS 208.67.222.222"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 208.67.220.220"' >> /etc/openvpn/server.conf + ;; + 9) # Google + echo 'push "dhcp-option DNS 8.8.8.8"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 8.8.4.4"' >> /etc/openvpn/server.conf + ;; + 10) # Yandex Basic + echo 'push "dhcp-option DNS 77.88.8.8"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 77.88.8.1"' >> /etc/openvpn/server.conf + ;; + 11) # AdGuard DNS + echo 'push "dhcp-option DNS 176.103.130.130"' >> /etc/openvpn/server.conf + echo 'push "dhcp-option DNS 176.103.130.131"' >> /etc/openvpn/server.conf + ;; + esac + echo 'push "redirect-gateway def1 bypass-dhcp"' >> /etc/openvpn/server.conf + + # IPv6 network settings if needed + if [[ "$IPV6_SUPPORT" = 'y' ]]; then + echo 'server-ipv6 fd42:42:42:42::/112 +tun-ipv6 +push tun-ipv6 +push "route-ipv6 2000::/3" +push "redirect-gateway ipv6"' >> /etc/openvpn/server.conf + fi + + if [[ $COMPRESSION_ENABLED == "y" ]]; then + echo "compress $COMPRESSION_ALG" >> /etc/openvpn/server.conf + fi + + if [[ $DH_TYPE == "1" ]]; then + echo "dh none" >> /etc/openvpn/server.conf + echo "ecdh-curve $DH_CURVE" >> /etc/openvpn/server.conf + elif [[ $DH_TYPE == "2" ]]; then + echo "dh dh.pem" >> /etc/openvpn/server.conf + fi + + case $TLS_SIG in + 1) + echo "tls-crypt tls-crypt.key 0" >> /etc/openvpn/server.conf + ;; + 2) + echo "tls-auth tls-auth.key 0" >> /etc/openvpn/server.conf + ;; + esac + + echo "crl-verify crl.pem +ca ca.crt +cert $SERVER_NAME.crt +key $SERVER_NAME.key +auth $HMAC_ALG +cipher $CIPHER +ncp-ciphers $CIPHER +tls-server +tls-version-min 1.2 +tls-cipher $CC_CIPHER +status /var/log/openvpn/status.log +verb 3" >> /etc/openvpn/server.conf + + # Create log dir + mkdir -p /var/log/openvpn + + # Enable routing + echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.d/20-openvpn.conf + if [[ "$IPV6_SUPPORT" = 'y' ]]; then + echo 'net.ipv6.conf.all.forwarding=1' >> /etc/sysctl.d/20-openvpn.conf + fi + # Avoid an unneeded reboot + sysctl --system + + # If SELinux is enabled and a custom port was selected, we need this + if hash sestatus 2>/dev/null; then + if sestatus | grep "Current mode" | grep -qs "enforcing"; then + if [[ "$PORT" != '1194' ]]; then + semanage port -a -t openvpn_port_t -p "$PROTOCOL" "$PORT" + fi + fi + fi + + # Finally, restart and enable OpenVPN + if [[ "$OS" = 'arch' || "$OS" = 'fedora' ]]; then + # Don't modify package-provided service + cp /usr/lib/systemd/system/openvpn-server@.service /etc/systemd/system/openvpn-server@.service + + # Workaround to fix OpenVPN service on OpenVZ + sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service + # Another workaround to keep using /etc/openvpn/ + sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn-server@.service + # On fedora, the service hardcodes the ciphers. We want to manage the cipher ourselves, so we remove it from the service + if [[ "$OS" == "fedora" ]];then + sed -i 's|--cipher AES-256-GCM --ncp-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC:AES-128-CBC:BF-CBC||' /etc/systemd/system/openvpn-server@.service + fi + + systemctl daemon-reload + systemctl restart openvpn-server@server + systemctl enable openvpn-server@server + elif [[ "$OS" == "ubuntu" ]] && [[ "$VERSION_ID" == "16.04" ]]; then + # On Ubuntu 16.04, we use the package from the OpenVPN repo + # This package uses a sysvinit service + systemctl enable openvpn + systemctl start openvpn + else + # Don't modify package-provided service + cp /lib/systemd/system/openvpn\@.service /etc/systemd/system/openvpn\@.service + + # Workaround to fix OpenVPN service on OpenVZ + sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn\@.service + # Another workaround to keep using /etc/openvpn/ + sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn\@.service + + systemctl daemon-reload + systemctl restart openvpn@server + systemctl enable openvpn@server + fi + + if [[ $DNS == 2 ]];then + installUnbound + fi + + # Add iptables rules in two scripts + mkdir /etc/iptables + + # Script to add rules + echo "#!/bin/sh +iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o $NIC -j MASQUERADE +iptables -A INPUT -i tun0 -j ACCEPT +iptables -A FORWARD -i $NIC -o tun0 -j ACCEPT +iptables -A FORWARD -i tun0 -o $NIC -j ACCEPT +iptables -A INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" > /etc/iptables/add-openvpn-rules.sh + + if [[ "$IPV6_SUPPORT" = 'y' ]]; then + echo "ip6tables -t nat -A POSTROUTING -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE +ip6tables -A INPUT -i tun0 -j ACCEPT +ip6tables -A FORWARD -i $NIC -o tun0 -j ACCEPT +ip6tables -A FORWARD -i tun0 -o $NIC -j ACCEPT" >> /etc/iptables/add-openvpn-rules.sh + fi + + # Script to remove rules + echo "#!/bin/sh +iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o $NIC -j MASQUERADE +iptables -D INPUT -i tun0 -j ACCEPT +iptables -D FORWARD -i $NIC -o tun0 -j ACCEPT +iptables -D FORWARD -i tun0 -o $NIC -j ACCEPT +iptables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" > /etc/iptables/rm-openvpn-rules.sh + + if [[ "$IPV6_SUPPORT" = 'y' ]]; then + echo "ip6tables -t nat -D POSTROUTING -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE +ip6tables -D INPUT -i tun0 -j ACCEPT +ip6tables -D FORWARD -i $NIC -o tun0 -j ACCEPT +ip6tables -D FORWARD -i tun0 -o $NIC -j ACCEPT" >> /etc/iptables/rm-openvpn-rules.sh + fi + + chmod +x /etc/iptables/add-openvpn-rules.sh + chmod +x /etc/iptables/rm-openvpn-rules.sh + + # Handle the rules via a systemd script + echo "[Unit] +Description=iptables rules for OpenVPN +Before=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/etc/iptables/add-openvpn-rules.sh +ExecStop=/etc/iptables/rm-openvpn-rules.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target" > /etc/systemd/system/iptables-openvpn.service + + # Enable service and apply rules + systemctl daemon-reload + systemctl enable iptables-openvpn + systemctl start iptables-openvpn + + # If the server is behind a NAT, use the correct IP address for the clients to connect to + if [[ "$PUBLICIP" != "" ]]; then + IP=$PUBLICIP + fi + + # client-template.txt is created so we have a template to add further users later + echo "client" > /etc/openvpn/client-template.txt + if [[ "$PROTOCOL" = 'udp' ]]; then + echo "proto udp" >> /etc/openvpn/client-template.txt + elif [[ "$PROTOCOL" = 'tcp' ]]; then + echo "proto tcp-client" >> /etc/openvpn/client-template.txt + fi + echo "remote $IP $PORT +dev tun +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +verify-x509-name $SERVER_NAME name +auth $HMAC_ALG +auth-nocache +cipher $CIPHER +tls-client +tls-version-min 1.2 +tls-cipher $CC_CIPHER +setenv opt block-outside-dns # Prevent Windows 10 DNS leak +verb 3" >> /etc/openvpn/client-template.txt + +if [[ $COMPRESSION_ENABLED == "y" ]]; then + echo "compress $COMPRESSION_ALG" >> /etc/openvpn/client-template.txt +fi + + # Generate the custom client.ovpn + newClient + echo "If you want to add more clients, you simply need to run this script another time!" +} + +function newClient () { + echo "" + echo "Tell me a name for the client." + echo "Use one word only, no special characters." + + until [[ "$CLIENT" =~ ^[a-zA-Z0-9_]+$ ]]; do + read -rp "Client name: " -e CLIENT + done + + echo "" + echo "Do you want to protect the configuration file with a password?" + echo "(e.g. encrypt the private key with a password)" + echo " 1) Add a passwordless client" + echo " 2) Use a password for the client" + + until [[ "$PASS" =~ ^[1-2]$ ]]; do + read -rp "Select an option [1-2]: " -e -i 1 PASS + done + + cd /etc/openvpn/easy-rsa/ || return + case $PASS in + 1) + ./easyrsa build-client-full "$CLIENT" nopass + ;; + 2) + echo "⚠️ You will be asked for the client password below ⚠️" + ./easyrsa build-client-full "$CLIENT" + ;; + esac + + # Home directory of the user, where the client configuration (.ovpn) will be written + if [ -e "/home/$CLIENT" ]; then # if $1 is a user name + homeDir="/home/$CLIENT" + elif [ "${SUDO_USER}" ]; then # if not, use SUDO_USER + homeDir="/home/${SUDO_USER}" + else # if not SUDO_USER, use /root + homeDir="/root" + fi + + # Determine if we use tls-auth or tls-crypt + if grep -qs "^tls-crypt" /etc/openvpn/server.conf; then + TLS_SIG="1" + elif grep -qs "^tls-auth" /etc/openvpn/server.conf; then + TLS_SIG="2" + fi + + # Generates the custom client.ovpn + cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn" + { + echo "<ca>" + cat "/etc/openvpn/easy-rsa/pki/ca.crt" + echo "</ca>" + + echo "<cert>" + awk '/BEGIN/,/END/' "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" + echo "</cert>" + + echo "<key>" + cat "/etc/openvpn/easy-rsa/pki/private/$CLIENT.key" + echo "</key>" + + case $TLS_SIG in + 1) + echo "<tls-crypt>" + cat /etc/openvpn/tls-crypt.key + echo "</tls-crypt>" + ;; + 2) + echo "key-direction 1" + echo "<tls-auth>" + cat /etc/openvpn/tls-auth.key + echo "</tls-auth>" + ;; + esac + } >> "$homeDir/$CLIENT.ovpn" + + echo "" + echo "Client $CLIENT added, the configuration file is available at $homeDir/$CLIENT.ovpn." + echo "Download the .ovpn file and import it in your OpenVPN client." +} + +function revokeClient () { + NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V") + if [[ "$NUMBEROFCLIENTS" = '0' ]]; then + echo "" + echo "You have no existing clients!" + exit 1 + fi + + echo "" + echo "Select the existing client certificate you want to revoke" + tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' + if [[ "$NUMBEROFCLIENTS" = '1' ]]; then + read -rp "Select one client [1]: " CLIENTNUMBER + else + read -rp "Select one client [1-$NUMBEROFCLIENTS]: " CLIENTNUMBER + fi + + CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) + cd /etc/openvpn/easy-rsa/ + ./easyrsa --batch revoke "$CLIENT" + EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl + # Cleanup + rm -f "pki/reqs/$CLIENT.req" + rm -f "pki/private/$CLIENT.key" + rm -f "pki/issued/$CLIENT.crt" + rm -f /etc/openvpn/crl.pem + cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem + chmod 644 /etc/openvpn/crl.pem + find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete + rm -f "/root/$CLIENT.ovpn" + sed -i "s|^$CLIENT,.*||" /etc/openvpn/ipp.txt + + echo "" + echo "Certificate for client $CLIENT revoked." +} + +function removeUnbound () { + # Remove OpenVPN-related config + sed -i 's|include: \/etc\/unbound\/openvpn.conf||' /etc/unbound/unbound.conf + rm /etc/unbound/openvpn.conf + systemctl restart unbound + + until [[ $REMOVE_UNBOUND =~ (y|n) ]]; do + echo "" + echo "If you were already using Unbound before installing OpenVPN, I removed the configuration related to OpenVPN." + read -rp "Do you want to completely remove Unbound? [y/n]: " -e REMOVE_UNBOUND + done + + if [[ "$REMOVE_UNBOUND" = 'y' ]]; then + # Stop Unbound + systemctl stop unbound + + if [[ "$OS" =~ (debian|ubuntu) ]]; then + apt-get autoremove --purge -y unbound + elif [[ "$OS" = 'arch' ]]; then + pacman --noconfirm -R unbound + elif [[ "$OS" = 'centos' ]]; then + yum remove -y unbound + elif [[ "$OS" = 'fedora' ]]; then + dnf remove -y unbound + fi + + rm -rf /etc/unbound/ + + echo "" + echo "Unbound removed!" + else + echo "" + echo "Unbound wasn't removed." + fi +} + +function removeOpenVPN () { + echo "" + read -rp "Do you really want to remove OpenVPN? [y/n]: " -e -i n REMOVE + if [[ "$REMOVE" = 'y' ]]; then + # Get OpenVPN port from the configuration + PORT=$(grep '^port ' /etc/openvpn/server.conf | cut -d " " -f 2) + + # Stop OpenVPN + if [[ "$OS" =~ (fedora|arch) ]]; then + systemctl disable openvpn-server@server + systemctl stop openvpn-server@server + # Remove customised service + rm /etc/systemd/system/openvpn-server@.service + elif [[ "$OS" == "ubuntu" ]] && [[ "$VERSION_ID" == "16.04" ]]; then + systemctl disable openvpn + systemctl stop openvpn + else + systemctl disable openvpn@server + systemctl stop openvpn@server + # Remove customised service + rm /etc/systemd/system/openvpn\@.service + fi + + # Remove the iptables rules related to the script + systemctl stop iptables-openvpn + # Cleanup + systemctl disable iptables-openvpn + rm /etc/systemd/system/iptables-openvpn.service + systemctl daemon-reload + rm /etc/iptables/add-openvpn-rules.sh + rm /etc/iptables/rm-openvpn-rules.sh + + # SELinux + if hash sestatus 2>/dev/null; then + if sestatus | grep "Current mode" | grep -qs "enforcing"; then + if [[ "$PORT" != '1194' ]]; then + semanage port -d -t openvpn_port_t -p udp "$PORT" + fi + fi + fi + + if [[ "$OS" =~ (debian|ubuntu) ]]; then + apt-get autoremove --purge -y openvpn + if [[ -e /etc/apt/sources.list.d/openvpn.list ]];then + rm /etc/apt/sources.list.d/openvpn.list + apt-get update + fi + elif [[ "$OS" = 'arch' ]]; then + pacman --noconfirm -R openvpn + elif [[ "$OS" = 'centos' ]]; then + yum remove -y openvpn + elif [[ "$OS" = 'fedora' ]]; then + dnf remove -y openvpn + fi + + # Cleanup + find /home/ -maxdepth 2 -name "*.ovpn" -delete + find /root/ -maxdepth 1 -name "*.ovpn" -delete + rm -rf /etc/openvpn + rm -rf /usr/share/doc/openvpn* + rm -f /etc/sysctl.d/20-openvpn.conf + rm -rf /var/log/openvpn + + # Unbound + if [[ -e /etc/unbound/openvpn.conf ]]; then + removeUnbound + fi + echo "" + echo "OpenVPN removed!" + else + echo "" + echo "Removal aborted!" + fi +} + +function manageMenu () { + clear + echo "Welcome to OpenVPN-install!" + echo "The git repository is available at: https://github.com/angristan/openvpn-install" + echo "" + echo "It looks like OpenVPN is already installed." + echo "" + echo "What do you want to do?" + echo " 1) Add a new user" + echo " 2) Revoke existing user" + echo " 3) Remove OpenVPN" + echo " 4) Exit" + until [[ "$MENU_OPTION" =~ ^[1-4]$ ]]; do + read -rp "Select an option [1-4]: " MENU_OPTION + done + + case $MENU_OPTION in + 1) + newClient + ;; + 2) + revokeClient + ;; + 3) + removeOpenVPN + ;; + 4) + exit 0 + ;; + esac +} + +# Check for root, TUN, OS... +initialCheck + +# Check if OpenVPN is already installed +if [[ -e /etc/openvpn/server.conf ]]; then + manageMenu +else + installOpenVPN +fi diff --git a/tests/lib/wifi.py b/boardfarm/tests/lib/wifi.py similarity index 51% rename from tests/lib/wifi.py rename to boardfarm/tests/lib/wifi.py index 2dc04b2b..5db55d70 100644 --- a/tests/lib/wifi.py +++ b/boardfarm/tests/lib/wifi.py @@ -10,7 +10,6 @@ import re import string import time -from devices import prompt wlan_iface = None @@ -30,60 +29,60 @@ def wifi_interface(console): return wlan_iface def randomSSIDName(): - return 'wifi-' + ''.join(random.sample(string.lowercase+string.digits,10)) + return 'WIFI-' + ''.join(random.sample(string.lowercase+string.digits,10)) def uciSetWifiSSID(console, ssid): console.sendline('uci set wireless.@wifi-iface[0].ssid=%s; uci commit wireless; wifi' % ssid) - console.expect(prompt) + console.expect_prompt() def uciSetWifiMode(console, radio, hwmode): console.sendline('uci set wireless.wifi%s.hwmode=%s; uci commit wireless' % (radio, hwmode)) - console.expect(prompt) + console.expect_prompt() def uciSetChannel(console, radio, channel): console.sendline('uci set wireless.wifi%s.channel=%s; uci commit wireless' % (radio, channel)) - console.expect(prompt) + console.expect_prompt() def enable_wifi(board, index=0): board.sendline('\nuci set wireless.@wifi-device[%s].disabled=0; uci commit wireless' % index) board.expect('uci set') - board.expect(prompt) + board.expect_prompt() board.sendline('wifi') board.expect('wifi') - board.expect(prompt, timeout=50) + board.expect_prompt(timeout=50) time.sleep(20) def enable_all_wifi_interfaces(board): '''Find all wireless interfaces, and enable them.''' board.sendline('\nuci show wireless | grep disabled') board.expect('grep disabled') - board.expect(prompt) + board.expect_prompt() # The following re.findall should return list of settings: # ['wireless.radio0.disabled', 'wireless.radio1.disabled'] settings = re.findall('([\w\.]+)=\d', board.before) for s in settings: board.sendline('uci set %s=0' % s) - board.expect(prompt) + board.expect_prompt() board.sendline('uci commit wireless') - board.expect(prompt) + board.expect_prompt() board.sendline('wifi') - board.expect(prompt, timeout=50) + board.expect_prompt(timeout=50) def disable_wifi(board, wlan_iface="ath0"): board.sendline('uci set wireless.@wifi-device[0].disabled=1; uci commit wireless') board.expect('uci set') - board.expect(prompt) + board.expect_prompt() board.sendline('wifi') - board.expect(prompt) + board.expect_prompt() board.sendline('iwconfig %s' % wlan_iface) - board.expect(prompt) + board.expect_prompt() def wifi_on(board): '''Return True if WiFi is enabled.''' board.sendline('\nuci show wireless.@wifi-device[0].disabled') try: board.expect('disabled=0', timeout=5) - board.expect(prompt) + board.expect_prompt() return True except: return False @@ -99,7 +98,7 @@ def wifi_get_info(board, wlan_iface): essid = board.match.group(1) board.expect('Bit Rate[:=]([^ ]+) ') rate = float(board.match.group(1)) - board.expect(prompt) + board.expect_prompt() # TODO: determine channel channel = -1.0 elif "wlan" in wlan_iface: @@ -114,12 +113,12 @@ def wifi_get_info(board, wlan_iface): rate = float(board.match.group(1)) except: rate = -1.0 - board.expect(prompt) + board.expect_prompt() else: print("Unknown wireless type") except: board.sendline('dmesg') - board.expect(prompt) + board.expect_prompt() raise return essid, channel, rate, freq @@ -140,40 +139,132 @@ def wait_wifi_up(board, num_tries=10, sleep=15, wlan_iface="ath0"): def wifi_add_vap(console, phy, ssid): console.sendline('uci add wireless wifi-iface') - console.expect(prompt) + console.expect_prompt() console.sendline('uci set wireless.@wifi-iface[-1].device="%s"' % phy) - console.expect(prompt) + console.expect_prompt() console.sendline('uci set wireless.@wifi-iface[-1].network="lan"') - console.expect(prompt) + console.expect_prompt() console.sendline('uci set wireless.@wifi-iface[-1].mode="ap"') - console.expect(prompt) + console.expect_prompt() console.sendline('uci set wireless.@wifi-iface[-1].ssid="%s"' % ssid) - console.expect(prompt) + console.expect_prompt() console.sendline('uci set wireless.@wifi-iface[-1].encryption="none"') - console.expect(prompt) + console.expect_prompt() console.sendline('uci commit') - console.expect(prompt) + console.expect_prompt() def wifi_del_vap(console, index): console.sendline('uci delete wireless.@wifi-iface[%s]' % index) - console.expect(prompt) + console.expect_prompt() console.sendline('uci commit') - console.expect(prompt) + console.expect_prompt() def uciSetWifiSecurity(board, vap_iface, security): if security.lower() in ['none']: print("Setting security to none.") board.sendline('uci set wireless.@wifi-iface[%s].encryption=none' % vap_iface) - board.expect(prompt) + board.expect_prompt() elif security.lower() in ['wpa-psk']: print("Setting security to WPA-PSK.") board.sendline('uci set wireless.@wifi-iface[%s].encryption=psk+tkip' % vap_iface) - board.expect(prompt) + board.expect_prompt() board.sendline('uci set wireless.@wifi-iface[%s].key=1234567890abcdexyz' % vap_iface) - board.expect(prompt) + board.expect_prompt() elif security.lower() in ['wpa2-psk']: print("Setting security to WPA2-PSK.") board.sendline('uci set wireless.@wifi-iface[%s].encryption=psk2+ccmp' % vap_iface) - board.expect(prompt) + board.expect_prompt() board.sendline('uci set wireless.@wifi-iface[%s].key=1234567890abcdexyz' % vap_iface) - board.expect(prompt) + board.expect_prompt() + +class wifi_stub(): + apply_changes_no_delay = True + # The above variable can tweak the behavior of the below functions + # If it is set to True, it will apply the changes after setting wifi parameters + # If it is set to False, it will not save any changes & apply_changes() will be skipped + def enable_wifi(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_ssid(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_broadcast(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_security(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_password(self, *args, **kwargs): + raise Exception("Not implemented!") + def enable_channel_utilization(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_operating_mode(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_bandwidth(self, *args, **kwargs): + raise Exception("Not implemented!") + def set_channel_number(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_wifi_enabled(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_ssid(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_security(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_password(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_channel_utilization(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_operating_mode(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_bandwidth(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_broadcast(self, *args, **kwargs): + raise Exception("Not implemented!") + def get_channel_number(self, *args, **kwargs): + raise Exception("Not implemented!") + def prepare(self): + pass + def cleanup(self): + pass + def apply_changes(self): + '''This function used to save the configs to be modified''' + pass + +class wifi_client_stub(): + def enable_wifi(self): + '''Function to make the wifi interface UP''' + raise Exception("Not implemented!") + def disable_wifi(self): + '''Function to make the wifi interface DOWN''' + raise Exception("Not implemented!") + def disable_and_enable_wifi(self): + '''Function to make the wifi interface DOWN and UP''' + raise Exception("Not implemented!") + def wifi_scan(self): + '''Function that scans for SSIDs on a particular radio, and return a list of SSID''' + raise Exception("Not implemented!") + # this code does not execute, but rather serves as an example for the API + return "SSID: <ssid_name1> \ + SSID: <ssid_name2>.." + def wifi_check_ssid(self, ssid_name): + '''Function that scans for a particular SSID + Takes ssid to be scanned as an argument''' + raise Exception("Not implemented!") + # this code does not execute, but rather serves as an example for the API + return True # if found + return False # if not found + def wifi_connect(self, ssid_name, password, security_mode): + '''Function to connect to wifi either with ssid name and password or with ssid name alone + Takes arguments as SSID and (password,security) if required''' + raise Exception("Not implemented!") + def wifi_connectivity_verify(self): + '''Function to verify wifi connectivity + Returns True or False based on connectivity''' + raise Exception("Not implemented!") + # this code does not execute, but rather serves as an example for the API + return "True or False" + def wifi_disconnect(self): + '''Function to disconnect wifi''' + raise Exception("Not implemented!") + def wifi_change_region(self, country): + '''Function to change the country + Takes country name as an argument Eg:Germany + Return the country code Eg: Germany as DE''' + raise Exception("Not implemented!") + return "DE" diff --git a/tests/linux_boot.py b/boardfarm/tests/linux_boot.py similarity index 53% rename from tests/linux_boot.py rename to boardfarm/tests/linux_boot.py index 86568079..3a0c2d9a 100644 --- a/tests/linux_boot.py +++ b/boardfarm/tests/linux_boot.py @@ -10,10 +10,17 @@ import lib import sys import traceback +import time from devices import board, wan, lan, wlan, prompt +from lib.bft_logging import LoggerMeta, now_short class LinuxBootTest(unittest2.TestCase): + _testMethodName = "UNDEFINED" + __metaclass__ = LoggerMeta + log = "" + log_calls = "" + _format = "%a %d %b %Y %H:%M:%S" def __init__(self, config): super(LinuxBootTest, self).__init__("testWrapper") @@ -21,14 +28,15 @@ def __init__(self, config): self.reset_after_fail = True self.dont_retry = False self.logged = dict() + self.subtests = [] def id(self): return self.__class__.__name__ def setUp(self): - lib.common.test_msg("\n==================== Begin %s ====================" % self.__class__.__name__) + lib.common.test_msg("\n==================== Begin %s Time: %s ====================" % (self.__class__.__name__, now_short(self._format))) def tearDown(self): - lib.common.test_msg("\n==================== End %s ======================" % self.__class__.__name__) + lib.common.test_msg("\n==================== End %s Time: %s ======================" % (self.__class__.__name__, now_short(self._format))) def wan_setup(self): None @@ -49,10 +57,22 @@ def wlan_cleanup(self): None def testWrapper(self): - if not board.isalive(): - self.result_grade = "SKIP" - self.skipTest("Board is not alive") - raise + self.start_time = time.time() + + for d in self.config.devices: + dev = getattr(self.config, d) + dev.test_to_log = self + dev.test_prefix = d.encode("utf8") + + for c in board.consoles: + c.test_to_log = self + c.test_prefix = 'console-%s' % str(board.consoles.index(c) + 1) + + if not c.isalive(): + self.result_grade = "SKIP" + print("\n\n=========== Test skipped! Board is not alive... =============") + self.skipTest("Board is not alive") + raise try: if wan and hasattr(self, 'wan_setup'): @@ -70,11 +90,15 @@ def testWrapper(self): while retry >= 0: try: self.runTest() + board.touch() retry = -1 except Exception as e: retry = retry - 1 if(retry > 0): - print(e.get_trace()) + if hasattr(e, 'get_trace'): + print(e.get_trace()) + else: + print("Exception has no trace, type = %s" % type(e)) print("\n\n----------- Test failed! Retrying in 5 seconds... -------------") time.sleep(5) else: @@ -91,21 +115,36 @@ def testWrapper(self): self.result_grade = "Unexp OK" else: self.result_grade = "OK" + + self.stop_time = time.time() + self.logged['test_time'] = float(self.stop_time - self.start_time) except unittest2.case.SkipTest: + self.stop_time = time.time() + self.logged['test_time'] = float(self.stop_time - self.start_time) self.result_grade = "SKIP" print("\n\n=========== Test skipped! Moving on... =============") raise except Exception as e: + self.stop_time = time.time() + self.logged['test_time'] = float(self.stop_time - self.start_time) if hasattr(self, 'expected_failure') and self.expected_failure: self.result_grade = "Exp FAIL" else: self.result_grade = "FAIL" - print("\n\n=========== Test failed! Running recovery ===========") + print("\n\n=========== Test failed! Running recovery Time: %s ===========" % now_short(self._format)) if e.__class__.__name__ == "TIMEOUT": print(e.get_trace()) else: print(e) traceback.print_exc(file=sys.stdout) + + import os + if 'BFT_DEBUG' in os.environ: + print(self) + for device in self.config.devices: + d = getattr(self.config, device) + print(d) + self.recover() raise @@ -115,3 +154,29 @@ def recover(self): sys.exit(1) print("ERROR: No default recovery!") raise "No default recovery!" + + + _log_to_file = None + + def x_log_to_file(self, value): + pass + + def get_log_to_file(self): + return self._log_to_file + + def set_log_to_file(self, value): + # we have to call this because the property method calls are + # not calling the decorator.. work around for now + if self._log_to_file is not None: + self.x_log_to_file(value.replace(self._log_to_file, '')) + else: + self.x_log_to_file(value) + + self._log_to_file = value + + log_to_file = property(get_log_to_file, set_log_to_file) + + def get_device_by_feature(self, feature): + for device in self.config.devices: + if 'feature' in device and feature in devices['feature']: + return getattr(self, device) diff --git a/tests/lsmod.py b/boardfarm/tests/lsmod.py similarity index 100% rename from tests/lsmod.py rename to boardfarm/tests/lsmod.py diff --git a/tests/netperf_bidir.py b/boardfarm/tests/netperf_bidir.py similarity index 100% rename from tests/netperf_bidir.py rename to boardfarm/tests/netperf_bidir.py diff --git a/tests/netperf_reverse_test.py b/boardfarm/tests/netperf_reverse_test.py similarity index 95% rename from tests/netperf_reverse_test.py rename to boardfarm/tests/netperf_reverse_test.py index b60968e8..abf48534 100644 --- a/tests/netperf_reverse_test.py +++ b/boardfarm/tests/netperf_reverse_test.py @@ -18,7 +18,7 @@ class NetperfReverseTest(netperf_test.NetperfTest): '''Setup Netperf and Ran Reverse Throughput.''' def runTest(self): # setup port forwarding to lan netperf server - lan_priv_ip = lan.get_interface_ipaddr("eth1") + lan_priv_ip = lan.get_interface_ipaddr(lan.iface_dut) board.uci_forward_traffic_redirect("tcp", "12865", lan_priv_ip) # setup port for data socket separate from control port board.uci_forward_traffic_redirect("tcp", "12866", lan_priv_ip) diff --git a/tests/netperf_rfc2544.py b/boardfarm/tests/netperf_rfc2544.py similarity index 100% rename from tests/netperf_rfc2544.py rename to boardfarm/tests/netperf_rfc2544.py diff --git a/tests/netperf_stress_test.py b/boardfarm/tests/netperf_stress_test.py similarity index 98% rename from tests/netperf_stress_test.py rename to boardfarm/tests/netperf_stress_test.py index a400e259..70de7f4b 100644 --- a/tests/netperf_stress_test.py +++ b/boardfarm/tests/netperf_stress_test.py @@ -5,11 +5,8 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import unittest2 import lib import netperf_test -import pexpect -import sys import time from netperf_test import install_netperf diff --git a/tests/netperf_test.py b/boardfarm/tests/netperf_test.py similarity index 59% rename from tests/netperf_test.py rename to boardfarm/tests/netperf_test.py index 1ef042ca..74f4baf5 100644 --- a/tests/netperf_test.py +++ b/boardfarm/tests/netperf_test.py @@ -5,12 +5,8 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import unittest2 import lib import rootfs_boot -import pexpect -import sys -import time import os from devices import board, wan, lan, wlan, prompt @@ -18,30 +14,36 @@ def install_netperf(device): # Check version device.sendline('\nnetperf -V') try: - device.expect('Netperf version 2.4', timeout=10) + device.expect('Netperf version 2', timeout=10) device.expect(device.prompt) except: # Install Netperf - device.sendline('apt-get update') - device.expect(device.prompt, timeout=60) - device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install netperf') - device.expect(device.prompt, timeout=60) - device.sendline('/etc/init.d/netperf restart') - device.expect('Restarting') - device.expect(device.prompt) + device.sendline('apt-get update') + device.expect(device.prompt, timeout=120) + device.sendline('apt-get install netperf') + device.expect(device.prompt, timeout=120) class NetperfTest(rootfs_boot.RootFSBootTest): - '''Setup Netperf and Ran Throughput.''' + '''Setup Netperf and Run Throughput.''' + @lib.common.run_once def lan_setup(self): super(NetperfTest, self).lan_setup() install_netperf(lan) + @lib.common.run_once def wan_setup(self): super(NetperfTest, self).wan_setup() install_netperf(wan) + lib.common.test_msg("Starting netserver on wan...") + wan.sendline("kill -9 `pidof netserver`") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") + def recover(self): lan.sendcontrol('c') + lib.common.test_msg("Recover..kill netserver on wan") + kill_netserver(wan); # if you are spawning a lot of connections, sometimes it # takes too long to wait for the connection to be established @@ -66,53 +68,97 @@ def run_netperf(self, device, ip, opts="", timeout=60): self.run_netperf_cmd(device, ip, opts=opts) return self.run_netperf_parse(device) + def kill_netserver(self, device): + device.sendline("kill -9 `pidof netserver`") + device.expect(prompt) + def runTest(self): super(NetperfTest, self).runTest() - board.sendline('mpstat -P ALL 30 1') - speed = self.run_netperf(lan, "192.168.0.1 -c -C -l 30") - board.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') + board.arm.sendline('mpstat -P ALL 30 1') + board.arm.expect('Linux') + + speed = self.run_netperf(lan, "%s -c -C -l 30" % wan.gw) + + board.sendcontrol('c') + board.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){9}\r\n',timeout=60) idle_cpu = float(board.match.group(1)) avg_cpu = 100 - float(idle_cpu) lib.common.test_msg("Average cpu usage was %s" % avg_cpu) + self.kill_netserver(wan) self.result_message = "Setup Netperf and Ran Throughput (Speed = %s 10^6bits/sec, CPU = %s)" % (speed, avg_cpu) +########################### + def run_netperf_tcp(device, run_time, pkt_size, direction="up"): + if direction == "up": - cmd = "netperf -H 192.168.0.1 -c -C -l %s -- -m %s -M %s -D" % (run_time, pkt_size, pkt_size) + cmd = "netperf -H %s -c -C -l %s -- -m %s -M %s -D" % (wan.gw, run_time, pkt_size, pkt_size) else: - cmd = "netperf -H 192.168.0.1 -c -C -l %s -t TCP_MAERTS -- -m %s -M %s -D" % (run_time, pkt_size, pkt_size) + cmd = "netperf -H %s -c -C -l %s -t TCP_MAERTS -- -m %s -M %s -D" % (wan.gw, run_time, pkt_size, pkt_size) device.sendline(cmd) device.expect('TEST.*\r\n') device.expect(device.prompt, timeout=run_time+4) class Netperf_UpTCP256(rootfs_boot.RootFSBootTest): '''Netperf upload throughput (TCP, packets size 256 Bytes).''' + def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=256) + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) class Netperf_UpTCP512(rootfs_boot.RootFSBootTest): '''Netperf upload throughput (TCP, packets size 512 Bytes).''' def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=512) + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) class Netperf_UpTCP1024(rootfs_boot.RootFSBootTest): '''Netperf upload throughput (TCP, packets size 1024 Bytes).''' def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=1024) + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) class Netperf_DownTCP256(rootfs_boot.RootFSBootTest): '''Netperf download throughput (TCP, packets size 256 Bytes).''' def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=256, direction="down") + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) class Netperf_DownTCP512(rootfs_boot.RootFSBootTest): '''Netperf download throughput (TCP, packets size 512 Bytes).''' def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=512, direction="down") + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) class Netperf_DownTCP1024(rootfs_boot.RootFSBootTest): '''Netperf download throughput (TCP, packets size 1024 Bytes).''' def runTest(self): + lib.common.test_msg("Starting netserver on wan...") + wan.sendline('/usr/bin/netserver') + wan.expect("Starting netserver with host") run_netperf_tcp(device=lan, run_time=15, pkt_size=1024, direction="down") + wan.sendline("kill -9 `pidof netserver`") + wan.expect(prompt) + diff --git a/tests/netperf_udp_test.py b/boardfarm/tests/netperf_udp_test.py similarity index 90% rename from tests/netperf_udp_test.py rename to boardfarm/tests/netperf_udp_test.py index 67d25ea3..716f56d8 100644 --- a/tests/netperf_udp_test.py +++ b/boardfarm/tests/netperf_udp_test.py @@ -5,12 +5,8 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import unittest2 import lib import netperf_test -import pexpect -import sys -import time from devices import board, wan, lan, wlan, prompt @@ -22,7 +18,7 @@ def runTest(self): self.run_netperf(lan, "192.168.0.1 -c -C -l 30 -t UDP_STREAM -- -m 1460 -M 1460") # setup port forwarding to lan netperf server - lan_priv_ip = lan.get_interface_ipaddr("eth1") + lan_priv_ip = lan.get_interface_ipaddr(lan.iface_dut) board.uci_forward_traffic_redirect("tcp", "12865", lan_priv_ip) # setup port for data socket separate from control port board.uci_forward_traffic_redirect("udp", "12866", lan_priv_ip) diff --git a/tests/network_restart.py b/boardfarm/tests/network_restart.py similarity index 100% rename from tests/network_restart.py rename to boardfarm/tests/network_restart.py diff --git a/tests/nmap.py b/boardfarm/tests/nmap.py similarity index 67% rename from tests/nmap.py rename to boardfarm/tests/nmap.py index 56d1236a..176aa741 100644 --- a/tests/nmap.py +++ b/boardfarm/tests/nmap.py @@ -7,6 +7,7 @@ import random import re +import pexpect import rootfs_boot from devices import board, wan, lan, wlan, prompt @@ -16,9 +17,12 @@ class Nmap_LAN(rootfs_boot.RootFSBootTest): def recover(self): lan.sendcontrol('c') def runTest(self): - lan.sendline('nmap -sS -A -v -p 1-10000 192.168.1.1') + lan.sendline('nmap -sS -A -v -p 1-10000 %s' % board.get_interface_ipaddr(board.lan_iface)) lan.expect('Starting Nmap') - lan.expect('Nmap scan report', timeout=660) + for i in range(12): + if 0 == lan.expect(['Nmap scan report', pexpect.TIMEOUT], timeout=100): + break + board.touch() lan.expect(prompt, timeout=60) open_ports = re.findall("(\d+)/tcp\s+open", lan.before) msg = "Found %s open TCP ports on LAN interface: %s." % \ @@ -30,14 +34,24 @@ class Nmap_WAN(rootfs_boot.RootFSBootTest): def recover(self): wan.sendcontrol('c') def runTest(self): - wan_ip_addr = board.get_interface_ipaddr('eth0') + wan_ip_addr = board.get_interface_ipaddr(board.wan_iface) wan.sendline('\nnmap -sS -A -v %s' % wan_ip_addr) wan.expect('Starting Nmap', timeout=5) - wan.expect('Nmap scan report', timeout=120) + wan.expect(pexpect.TIMEOUT, timeout=60) + board.touch() + wan.expect(pexpect.TIMEOUT, timeout=60) + board.touch() + wan.expect(pexpect.TIMEOUT, timeout=60) + board.touch() + wan.expect('Nmap scan report', timeout=60) wan.expect(prompt, timeout=60) open_ports = re.findall("(\d+)/tcp\s+open", wan.before) msg = "Found %s open TCP ports on WAN interface." % len(open_ports) self.result_message = msg + print("open ports = %s" % open_ports) + if hasattr(board, 'wan_open_ports'): + print("allowing open ports %s" % board.wan_open_ports) + open_ports = set(open_ports) - set(board.wan_open_ports) assert len(open_ports) == 0 class UDP_Stress(rootfs_boot.RootFSBootTest): diff --git a/boardfarm/tests/oe.py b/boardfarm/tests/oe.py new file mode 100644 index 00000000..335269ed --- /dev/null +++ b/boardfarm/tests/oe.py @@ -0,0 +1,46 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import rootfs_boot +from devices import board, wan, lan, wlan, prompt + +class OEVersion(rootfs_boot.RootFSBootTest): + '''Record OE version''' + def runTest(self): + board.sendline('cat /etc/os-release') + # PRETTY_NAME=RDK (A Yocto Project 1.6 based Distro) 2.0 (krogoth) + if 0 == board.expect(["cat: can't open '/etc/os-release': No such file or directory", 'PRETTY_NAME=([^\s]*) \(A Yocto Project (?:[^\s]*?)\s?based Distro\) ([^\s]*) \(([^\)]*)\)']): + self.skipTest("Skipping, not not an OE based distro") + + index = 1 + bsp_type = board.match.group(index) + index += 1 + if len(board.match.groups()) == 4: + oe_version = board.match.group(index) + index += 1 + else: + oe_version = "Unknown" + bsp_version = board.match.group(index) + index += 1 + oe_version_string = board.match.group(index) + index += 1 + + board.expect(prompt) + + print("#########################################") + print("bsp-type = %s" % bsp_type) + print("oe-version = %s" % oe_version) + print("bsp-version = %s" % bsp_version) + print("oe-version-string = %s" % oe_version_string) + print("#########################################") + + self.result_message="BSP = %s, BSP version = %s, OE version = %s, OE version string = %s" % \ + (bsp_type, bsp_version, oe_version, oe_version_string) + self.logged['bsp-type'] = bsp_type + self.logged['oe-version'] = oe_version + self.logged['bsp-version'] = bsp_version + self.logged['oe-version-string'] = oe_version_string diff --git a/tests/openwrt_version.py b/boardfarm/tests/openwrt_version.py similarity index 100% rename from tests/openwrt_version.py rename to boardfarm/tests/openwrt_version.py diff --git a/tests/opkg.py b/boardfarm/tests/opkg.py similarity index 99% rename from tests/opkg.py rename to boardfarm/tests/opkg.py index cc30bc00..e351af5c 100644 --- a/tests/opkg.py +++ b/boardfarm/tests/opkg.py @@ -5,8 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import time - import rootfs_boot from devices import board, wan, lan, wlan, prompt diff --git a/tests/opkg_conf.py b/boardfarm/tests/opkg_conf.py similarity index 99% rename from tests/opkg_conf.py rename to boardfarm/tests/opkg_conf.py index 4e12237f..e8079709 100644 --- a/tests/opkg_conf.py +++ b/boardfarm/tests/opkg_conf.py @@ -5,8 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import time - import rootfs_boot from devices import board, wan, lan, wlan, prompt diff --git a/tests/perfperpkt_test.py b/boardfarm/tests/perfperpkt_test.py similarity index 98% rename from tests/perfperpkt_test.py rename to boardfarm/tests/perfperpkt_test.py index 0697ea90..14fca36e 100644 --- a/tests/perfperpkt_test.py +++ b/boardfarm/tests/perfperpkt_test.py @@ -5,12 +5,9 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import unittest2 + import lib import iperf_test -import pexpect -import sys -import time from devices import board, wan, lan, wlan, prompt diff --git a/tests/ping.py b/boardfarm/tests/ping.py similarity index 80% rename from tests/ping.py rename to boardfarm/tests/ping.py index 8037ab5d..402ae097 100644 --- a/tests/ping.py +++ b/boardfarm/tests/ping.py @@ -16,8 +16,8 @@ def runTest(self): msg = 'No WAN Device defined, skipping ping WAN test.' lib.common.test_msg(msg) self.skipTest(msg) - board.sendline('\nping -c5 192.168.0.1') - board.expect('5 packets received', timeout=10) + board.sendline('\nping -c5 %s' % wan.gw) + board.expect('5 (packets )?received', timeout=15) board.expect(prompt) def recover(self): board.sendcontrol('c') @@ -26,14 +26,14 @@ class RouterPingInternet(rootfs_boot.RootFSBootTest): '''Router can ping internet address by IP.''' def runTest(self): board.sendline('\nping -c2 8.8.8.8') - board.expect('2 packets received', timeout=10) + board.expect('2 (packets )?received', timeout=15) board.expect(prompt) class RouterPingInternetName(rootfs_boot.RootFSBootTest): '''Router can ping internet address by name.''' def runTest(self): board.sendline('\nping -c2 www.google.com') - board.expect('2 packets received', timeout=10) + board.expect('2 (packets )?received', timeout=15) board.expect(prompt) class LanDevPingRouter(rootfs_boot.RootFSBootTest): @@ -43,9 +43,10 @@ def runTest(self): msg = 'No LAN Device defined, skipping ping test from LAN.' lib.common.test_msg(msg) self.skipTest(msg) - lan.sendline('\nping -i 0.2 -c 5 192.168.1.1') + router_ip = board.get_interface_ipaddr(board.lan_iface) + lan.sendline('\nping -i 0.2 -c 5 %s' % router_ip) lan.expect('PING ') - lan.expect('5 received', timeout=15) + lan.expect('5 (packets )?received', timeout=15) lan.expect(prompt) class LanDevPingWanDev(rootfs_boot.RootFSBootTest): @@ -59,9 +60,9 @@ def runTest(self): msg = 'No WAN Device defined, skipping ping WAN test.' lib.common.test_msg(msg) self.skipTest(msg) - lan.sendline('\nping -i 0.2 -c 5 192.168.0.1') + lan.sendline('\nping -i 0.2 -c 5 %s' % wan.gw) lan.expect('PING ') - lan.expect('5 received', timeout=15) + lan.expect('5 (packets )?received', timeout=15) lan.expect(prompt) def recover(self): lan.sendcontrol('c') @@ -74,7 +75,7 @@ def runTest(self): lib.common.test_msg(msg) self.skipTest(msg) lan.sendline('\nping -c2 8.8.8.8') - lan.expect('2 received', timeout=10) + lan.expect('2 (packets )?received', timeout=10) lan.expect(prompt) def recover(self): lan.sendcontrol('c') diff --git a/tests/ping6.py b/boardfarm/tests/ping6.py similarity index 79% rename from tests/ping6.py rename to boardfarm/tests/ping6.py index dae6bf7e..50ee2533 100644 --- a/tests/ping6.py +++ b/boardfarm/tests/ping6.py @@ -11,9 +11,9 @@ class LanDevPing6Router(rootfs_boot.RootFSBootTest): '''Device on LAN can ping6 router.''' def runTest(self): - lan.sendline('\nping6 -i 0.2 -c 20 4aaa::1') + lan.sendline('\nping6 -c 20 4aaa::1') lan.expect('PING ') - lan.expect(' ([0-9]+) received') + lan.expect(' ([0-9]+) (packets )?received') n = int(lan.match.group(1)) lan.expect(prompt) assert n > 0 @@ -22,9 +22,9 @@ class LanDevPing6WanDev(rootfs_boot.RootFSBootTest): '''Device on LAN can ping6 through router.''' def runTest(self): # Make Lan-device ping Wan-Device - lan.sendline('\nping6 -i 0.2 -c 20 5aaa::6') + lan.sendline('\nping6 -c 20 5aaa::6') lan.expect('PING ') - lan.expect(' ([0-9]+) received') + lan.expect(' ([0-9]+) (packets )?received') n = int(lan.match.group(1)) lan.expect(prompt) assert n > 0 diff --git a/tests/qdisc.py b/boardfarm/tests/qdisc.py similarity index 100% rename from tests/qdisc.py rename to boardfarm/tests/qdisc.py diff --git a/tests/rootfs_boot.py b/boardfarm/tests/rootfs_boot.py similarity index 55% rename from tests/rootfs_boot.py rename to boardfarm/tests/rootfs_boot.py index 5c766ef2..e2c1b343 100644 --- a/tests/rootfs_boot.py +++ b/boardfarm/tests/rootfs_boot.py @@ -8,52 +8,100 @@ import time import linux_boot import lib -from devices import board, wan, lan, wlan, prompt + +from devices import board, wan, lan, prompt class RootFSBootTest(linux_boot.LinuxBootTest): '''Flashed image and booted successfully.''' def boot(self, reflash=True): - if not wan: - msg = 'No WAN Device defined, skipping flash.' + # start tftpd server on appropriate device + tftp_servers = [ x['name'] for x in self.config.board['devices'] if 'tftpd-server' in x.get('options', "") ] + tftp_device = None + # start all tftp servers for now + for tftp_server in tftp_servers: + # This is a mess, just taking the last tftpd-server? + tftp_device = getattr(self.config, tftp_server) + + # start dhcp servers + for device in self.config.board['devices']: + if 'options' in device and 'no-dhcp-sever' in device['options']: + continue + if 'options' in device and 'dhcp-server' in device['options']: + getattr(self.config, device['name']).setup_dhcp_server() + + if not wan and len(tftp_servers) == 0: + msg = 'No WAN Device or tftp_server defined, skipping flash.' lib.common.test_msg(msg) self.skipTest(msg) - wan.configure(kind="wan_device") + # This still needs some clean up, the fall back is to assuming the + # WAN provides the tftpd server, but it's not always the case + if wan: + wan.configure(kind="wan_device", config=self.config.board) + if tftp_device is None: + tftp_device = wan + + tftp_device.start_tftp_server() + + prov = getattr(self.config, 'provisioner', None) + if prov is not None: + prov.tftp_device = tftp_device + board.reprovision(prov) + + if hasattr(prov, 'prov_gateway'): + gw = prov.prov_gateway if wan.gw in prov.prov_network else prov.prov_ip + + for nw in [prov.cm_network, prov.mta_network, prov.open_network]: + wan.sendline('ip route add %s via %s' % (nw, gw)) + wan.expect(prompt) + + # TODO: don't do this and sort out two interfaces with ipv6 + wan.disable_ipv6('eth0') + + if hasattr(prov, 'prov_gateway_v6'): + wan.sendline('ip -6 route add default via %s' % str(prov.prov_gateway_v6)) + wan.expect(prompt) + + wan.sendline('ip route') + wan.expect(prompt) + wan.sendline('ip -6 route') + wan.expect(prompt) + if lan: lan.configure(kind="lan_device") - # start tftpd server on appropriate device - if self.config.board.get('wan_device', None) is not None: - wan.start_tftp_server() - else: - tftp_servers = [ x['name'] for x in self.config.board['devices'] if 'tftpd-server' in x.get('options', "") ] - # start all tftp servers for now - for tftp_server in tftp_servers: - tftp_device = getattr(self.config, tftp_server) - tftp_device.start_tftp_server() - + # tftp_device is always None, so we can set it from config + board.tftp_server = tftp_device.ipaddr + # then these are just hard coded defaults + board.tftp_port = 22 + # but are user/password used for tftp, they are likely legacy and just need to go away + board.tftp_username = "root" + board.tftp_password = "bigfoot1" board.reset() rootfs = None # Reflash only if at least one or more of these # variables are set, or else there is nothing to do in u-boot - if reflash and (self.config.META_BUILD or self.config.ROOTFS or\ + meta_interrupt = False + if self.config.META_BUILD and not board.flash_meta_booted: + meta_interrupt = True + if reflash and (meta_interrupt or self.config.ROOTFS or\ self.config.KERNEL or self.config.UBOOT): # Break into U-Boot, set environment variables board.wait_for_boot() - board.setup_uboot_network() + board.setup_uboot_network(tftp_device.gw) if self.config.META_BUILD: for attempt in range(3): try: - board.flash_meta(self.config.META_BUILD) + board.flash_meta(self.config.META_BUILD, wan, lan) break except Exception as e: print(e) - wan.restart_tftp_server() + tftp_device.restart_tftp_server() board.reset(break_into_uboot=True) - board.setup_uboot_network() + board.setup_uboot_network(tftp_device.gw) else: raise Exception('Error during flashing...') if self.config.UBOOT: @@ -67,11 +115,13 @@ def boot(self, reflash=True): if self.config.KERNEL: board.flash_linux(self.config.KERNEL) # Boot from U-Boot to Linux - board.boot_linux(rootfs=rootfs) + board.boot_linux(rootfs=rootfs, bootargs=self.config.bootargs) if hasattr(board, "pre_boot_linux"): board.pre_boot_linux(wan=wan, lan=lan) board.linux_booted = True board.wait_for_linux() + if self.config.META_BUILD and board.flash_meta_booted: + board.flash_meta(self.config.META_BUILD, wan, lan) linux_booted_seconds_up = board.get_seconds_uptime() # Retry setting up wan protocol if self.config.setup_device_networking: @@ -88,19 +138,6 @@ def boot(self, reflash=True): board.wait_for_network() board.wait_for_mounts() - if self.config.setup_device_networking: - # Router mac addresses are likely to change, so flush arp - if lan: - lan.ip_neigh_flush() - wan.ip_neigh_flush() - - # Clear default routes perhaps left over from prior use - if lan: - lan.sendline('\nip -6 route del default') - lan.expect(prompt) - wan.sendline('\nip -6 route del default') - wan.expect(prompt) - # Give other daemons time to boot and settle if self.config.setup_device_networking: for i in range(5): @@ -123,26 +160,30 @@ def boot(self, reflash=True): board.expect(prompt) # we can't have random messsages messages - board.sendline("echo \"1 1 1 7\" > /proc/sys/kernel/printk") - board.expect(prompt) + board.set_printk() if hasattr(self.config, 'INSTALL_PKGS') and self.config.INSTALL_PKGS != "": for pkg in self.config.INSTALL_PKGS.split(' '): if len(pkg) > 0: board.install_package(pkg) + if board.has_cmts: + from docsis_lib.docsis import check_valid_docsis_ip_networking + check_valid_docsis_ip_networking(board) + # Try to verify router has stayed up (and, say, not suddenly rebooted) end_seconds_up = board.get_seconds_uptime() print("\nThe router has been up %s seconds." % end_seconds_up) if self.config.setup_device_networking: assert end_seconds_up > linux_booted_seconds_up - assert end_seconds_up > 30 self.logged['boot_time'] = end_seconds_up - lan_ip = board.get_interface_ipaddr(board.lan_iface) - if lan and self.config.setup_device_networking: - lan.start_lan_client(gw=lan_ip) + if board.routing and lan and self.config.setup_device_networking: + if wan is not None: + lan.start_lan_client(wan_gw=wan.gw) + else: + lan.start_lan_client() reflash = False reboot = False @@ -154,6 +195,15 @@ def runTest(self): def recover(self): if self.__class__.__name__ == "RootFSBootTest": + try: + if board.linux_booted: + board.sendline('ps auxfw || ps w') + board.expect(prompt) + board.sendline('iptables -S') + board.expect(prompt) + except: + pass + board.close() lib.common.test_msg("Unable to boot, skipping remaining tests...") return diff --git a/tests/samba.py b/boardfarm/tests/samba.py similarity index 87% rename from tests/samba.py rename to boardfarm/tests/samba.py index 040bc7f6..4d3047e2 100644 --- a/tests/samba.py +++ b/boardfarm/tests/samba.py @@ -5,9 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import random -import re - import rootfs_boot from devices import board, wan, lan, wlan, prompt @@ -25,10 +22,10 @@ def runTest(self): board.sendline('smbclient -N -L 127.0.0.1') board.expect('boardfarm-test') board.expect(prompt) - lan.sendline('smbclient -N -L 192.168.1.1') + lan.sendline('smbclient -N -L %s' % board.get_interface_ipaddr(board.lan_iface)) lan.expect('boardfarm-test') lan.expect(prompt) - lan.sendline('mkdir -p /mnt/samba; mount -o guest //192.168.1.1/boardfarm-test /mnt/samba') + lan.sendline('mkdir -p /mnt/samba; mount -o guest //%s/boardfarm-test /mnt/samba' % board.get_interface_ipaddr(board.lan_iface)) lan.expect(prompt) lan.sendline('echo boardafarm-testing-string > /mnt/samba/test') lan.expect(prompt) diff --git a/boardfarm/tests/screenshot_gui.py b/boardfarm/tests/screenshot_gui.py new file mode 100644 index 00000000..b8395aad --- /dev/null +++ b/boardfarm/tests/screenshot_gui.py @@ -0,0 +1,105 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import rootfs_boot +import lib +from devices import board, wan, lan, prompt + +from pyvirtualdisplay import Display +import pexpect +import os + +class RunBrowserViaProxy(rootfs_boot.RootFSBootTest): + '''Bootstrap firefox running via localproxy''' + def start_browser(self): + try: + x,y=self.config.get_display_backend_size() + # try to start vnc server + self.display = Display(backend=self.config.default_display_backend, + rfbport=self.config.default_display_backend_port, + visible=0, + size=(x, y)) + self.display.start() + + if "BFT_DEBUG" in os.environ: + print("Connect to VNC display running on localhost:"+self.config.default_display_backend_port) + raw_input("Press any key after connecting to display....") + except: + # fallback xvfb + self.display = Display(visible=0, size=(1366, 768)) + self.display.start() + + try: + if lan.http_proxy is not None: + proxy = lan.http_proxy + elif lan.ipaddr is not None: + ip = lan.ipaddr + lan.sendline('cat /proc/net/vlan/config') + lan.expect('%s.*\|\s([0-9]+).*\|' % lan.iface_dut) + port = 8000 + int(lan.match.group(1)) + lan.expect(prompt) + proxy = "%s:%s" % (ip, port) + else: + # no proxy, use message below + assert False + except Exception as e: + print(e) + raise Exception("No reasonable http proxy found, please add one to the board config") + + board.enable_mgmt_gui(board, wan) + + print("Using proxy %s" % proxy) + driver = lib.common.get_webproxy_driver(proxy, self.config) + + return driver + + def runTest(self): + driver = self.start_browser() + + print("Browser is running, connect and debug") + print("Press Control-] to exit interactive mode") + board.interact() + + self.recover() + + def recover(self): + try: + self.display.stop() + except: + pass + try: + self.display.sendstop() + except: + pass + try: + self.display.popen.kill() + except: + pass + +class ScreenshotGUI(RunBrowserViaProxy): + '''Starts Firefox via a proxy to the LAN and takes a screenshot''' + def runTest(self): + driver = self.start_browser() + + print("taking ss of http://%s" % board.lan_gateway) + driver.get("http://%s" % board.lan_gateway) + + # wait for possible redirects to settle down + url = driver.current_url + for i in range(10): + board.expect(pexpect.TIMEOUT, timeout=5) + if url == driver.current_url: + break + url = driver.current_url + board.expect(pexpect.TIMEOUT, timeout=10) + + # take screenshot + driver.save_screenshot(self.config.output_dir + os.sep + 'lan_portal.png') + + driver.close() + + self.recover() diff --git a/boardfarm/tests/selftest.py b/boardfarm/tests/selftest.py new file mode 100644 index 00000000..49c04188 --- /dev/null +++ b/boardfarm/tests/selftest.py @@ -0,0 +1,424 @@ +import rootfs_boot +import lib +import hashlib +import random +import string +import os +import tempfile +from lib import SnmpHelper + +from devices import board, wan, lan, wlan, common + +''' + This file can bu used to add unit tests that + tests/validate the behavior of new/modified + components. +''' + +class selftest_test_copy_file_to_server(rootfs_boot.RootFSBootTest): + ''' + Copy a file to /tmp on the WAN device using common.copy_file_to_server + ''' + def runTest(self): + if not wan: + msg = 'No WAN Device defined, skipping copy file to WAN test.' + lib.common.test_msg(msg) + self.skipTest(msg) + + if not hasattr(wan, 'ipaddr'): + msg = 'WAN device is not running ssh server, can\'t copy with this function' + lib.common.test_msg(msg) + self.skipTest(msg) + text_file = tempfile.NamedTemporaryFile() + self.fname = fname = text_file.name + + letters = string.ascii_letters + fcontent = ''.join(random.choice(letters) for i in range(50)) + + text_file.write(fcontent) + text_file.flush() + + fmd5 = hashlib.md5(open(fname,'rb').read()).hexdigest() + print("File orginal md5sum: %s"% fmd5) + + wan_ip = wan.get_interface_ipaddr("eth0") + + cmd = "cat %s | ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -x %s@%s \"cat - > %s\""\ + % (fname, wan.username, wan_ip, fname) + # this must fail as the command does not echo the filename + try: + common.copy_file_to_server(cmd, wan.password, "/tmp") + except: + print("Copy failed as expected") + pass + + cmd = "cat %s | ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -x %s@%s \"cat - > %s; echo %s\""\ + % (fname, wan.username, wan_ip, fname, fname) + # this should pass + try: + common.copy_file_to_server(cmd, wan.password, "/tmp") + except: + assert 0,"copy_file_to_server failed, Test failed!!!!" + + # is the destination file identical to the source file + wan.sendline("md5sum %s"% fname) + wan.expect(fmd5) + wan.expect(wan.prompt) + + print("Test passed") + +class selftest_test_create_session(rootfs_boot.RootFSBootTest): + ''' + tests the create_session function in devices/__init__.py + ''' + session = None + def runTest(self): + if not wan: + msg = 'No WAN Device defined, skipping test_create_session.' + lib.common.test_msg(msg) + self.skipTest(msg) + + wan_ip = wan.get_interface_ipaddr("eth0") + + import devices + # this should fail, as "DebianBoxNonExistent" is not (yet) a device + try: + kwargs ={ + 'name':"wan_test_calls_fail", + 'ipaddr':wan_ip, + 'port':22, + 'color': 'magenta' + } + self.session = devices.get_device("DebianBoxNonExistent", **kwargs) + except: + pass + else: + assert self.session is None,"Test Failed on wrong class name" + print("Failed to create session on wrong class name (expected) PASS") + + # this must fail, as "169.254.12.18" is not a valid ip + try: + kwargs ={ + 'name':"wan_test_ip_fail", + 'ipaddr':"169.254.12.18", + 'port':22, + 'color': 'cyan' + } + self.session = devices.get_device("DebianBox", **kwargs) + except: + pass + else: + assert self.session is None,"Test Failed on wrong IP" + print("Failed to create session on wrong IP (expected) PASS") + + # this must fail, as 50 is not a valid port + try: + kwargs ={ + 'name':"wan_test_port_fail", + 'ipaddr':wan_ip, + 'port':50, + 'color': 'red' + } + self.session = devices.get_device("DebianBox", **kwargs) + except: + pass + else: + assert self.session is None,"Test Failed on wrong port" + print("Failed to create session on wrong port (expected) PASS") + + # this must fail, close but no cigar + try: + kwargs ={ + 'name':"wan_test_type_fail", + 'ipaddr':wan_ip, + 'port':50, + 'color': 'red' + } + self.session = devices.get_device("debina", **kwargs) + except: + pass + else: + assert self.session is None,"Test Failed on misspelled class name" + print("Failed to create session on misspelled class name (expected) PASS") + + # this should pass + try: + kwargs ={ + 'name':"correct_wan_parms", + 'ipaddr':wan_ip, + 'port':'22', + 'color': 'yellow' + } + self.session = devices.get_device("debian", **kwargs) + except: + assert 0, "Failed to create session, Test FAILED!" + else: + assert self.session is not None,"Test Failed on correct paramters!!" + + print("Session created successfully") + + # is the session really logged onto the wan? + + wan.sendline() + wan.expect(wan.prompt) + wan.sendline("ip a") + wan.expect_exact("ip a") + wan.expect(wan.prompt) + w = wan.before + + self.session.sendline() + self.session.expect(self.session.prompt) + self.session.sendline("ip a") + self.session.expect_exact("ip a") + self.session.expect(self.session.prompt) + s = self.session.before + + assert w == s, "Interfaces differ!!! Test Failed" + + self.session.sendline("exit") + + print("Test passed") + + def recover(self): + if self.session is not None: + self.session.sendline("exit") + +class selftest_testing_linuxdevice_functions(rootfs_boot.RootFSBootTest): + ''' + tests the linux functions moved to devices/linux.py + ''' + def runTest(self): + from devices import lan, debian, linux + if lan.model == "debian": + # check that lan is derived from LinuxDevice + assert(issubclass(debian.DebianBox, linux.LinuxDevice)) + + #get the mac address of the interface + lan_mac = lan.get_interface_macaddr(lan.iface_dut) + assert lan_mac != None, "Failed getting lan mac address" + print("lan mac address: %s" % lan_mac) + + #check the system uptime + uptime = lan.get_seconds_uptime() + assert uptime != None, "Failed getting system uptime" + print("system uptime is: %s" % uptime) + + #ping ip using function ping from linux.py + ping_check = lan.ping("8.8.8.8") + print("ping status is %s" % ping_check) + + #disable ipv6 + ipv6_disable = lan.disable_ipv6(lan.iface_dut) + #enable ipv6 + ipv6_enable = lan.enable_ipv6(lan.iface_dut) + board.set_printk() + print("Test passed") + + #remove neighbour table entries + lan.ip_neigh_flush() + + #set the link state up + lan.set_link_state(lan.iface_dut, "up") + + #Checking the interface status + link = lan.is_link_up(lan.iface_dut) + assert link != None, "Failed to check the link is up" + + #add sudo when the username is root + lan.sudo_sendline("ping -c5 '8.8.8.8'") + lan.expect(lan.prompt, timeout=50) + + #add new user name in linux + lan.add_new_user("test", "test") + lan.sendline("userdel test") + lan.expect(lan.prompt) + + text_file = tempfile.NamedTemporaryFile() + letters = string.ascii_letters + fcontent = ''.join(random.choice(letters) for i in range(50)) + + text_file.write(fcontent) + text_file.flush() + + fmd5 = hashlib.md5(open(text_file.name, 'rb').read()).hexdigest() + print("File orginal md5sum: %s"% fmd5) + print('copying file to lan at /tmp/dst.txt') + lan.copy_file_to_server(text_file.name, "/tmp/dst.txt") + print('Copy Done. Verify the integrity of the file') + lan.sendline('md5sum /tmp/dst.txt') + lan.expect(fmd5) + lan.expect(lan.prompt) + + '''FUnctions moved from openwrt to linux ''' + #Wait until network interfaces have IP Addresses + board.wait_for_network() + print "Waited until network interfaces has ip address" + + #Check the available memory of the device + memory_avail = board.get_memfree() + print 'Available memory of the device:{}'.format(memory_avail) + + #Getting the vmstat + vmstat_out = board.get_proc_vmstat() + assert vmstat_out is not None, 'virtual machine status is None' + print "Got the vmstat{}".format(vmstat_out) + + #Get the total number of connections in the network + nw_count = board.get_nf_conntrack_conn_count() + assert nw_count is not None , 'connections are empty' + print 'Get the total number of connections in the network{}'.format(nw_count) + + #Getting the DNS server upstream + ip_addr = board.get_dns_server_upstream() + assert ip_addr is not None, 'Getting nameserver ip is None' + print "Got the DNS server upstream{}".format(ip_addr) + print('Test Passed') + +class SnmpMibsUnitTest(object): + """ + Unit test for the SnmpMibs class. + Check for correct and incorrect mibs. + Default assumes the .mib files are in $USER/.snmp + DEBUG: + BFT_DEBUG=y shows the compiled dictionary + BFT_DEBUG=yy VERY verbose, shows the compiled dictionary and + mibs/oid details + """ + error_mibs = ['SsnmpEngineMaxMessageSize', # mispelled MUST fail + 'nonExistenMib', # this one MUST fail + 'ifCounterDiscontinuityTimeQ'] # mispelled MUST fail + + mibs = ['docsDevSwAdminStatus', + 'snmpEngineMaxMessageSize', + error_mibs[0], + 'docsDevServerDhcp', + 'ifCounterDiscontinuityTime', + error_mibs[1], + 'docsBpi2CmtsMulticastObjects', + error_mibs[2]] + + mib_files = ['DOCS-CABLE-DEVICE-MIB', 'DOCS-IETF-BPI2-MIB'] # this is the list of mib/txt files to be compiled + srcDirectories = ['/tmp/boardfarm-docsis/mibs'] # this needs to point to the mibs directory location + snmp_obj = None # will hold an instance of the SnmpMibs class + + def __init__(self,mibs_location=None, files=None, mibs=None, err_mibs=None): + """ + Takes: + mibs_location: where the .mib files are located (can be a list of dirs) + files: the name of the .mib/.txt files (without the extension) + mibs: e.g. sysDescr, sysObjectID, etc + err_mibs: wrong mibs (just for testing that the compiler rejects invalid mibs) + """ + + # where the .mib files are located + if mibs_location: + self.srcDirectories = mibs_location + + if type(self.srcDirectories) != list: + self.srcDirectories = [self.srcDirectories] + + for d in self.srcDirectories: + if not os.path.exists(str(d)): + msg = 'No mibs directory {} found test_SnmpHelper.'.format(str(self.srcDirectories)) + raise Exception(msg) + + if files: + self.mib_files = files + + self.snmp_obj = SnmpHelper.SnmpMibs(self.mib_files, self.srcDirectories) + + if mibs: + self.mibs = mibs + self.error_mibs = err_mibs + + if type(self.mibs) != list: + self.mibs = [self.mibs] + + def unitTest(self): + """ + Compiles the ASN1 and gets the oid of the given mibs + Asserts on failure + """ + + if 'y' in self.snmp_obj.dbg: + print(self.snmp_obj.mib_dict) + for k in self.snmp_obj.mib_dict: + print(k, ":", self.snmp_obj.mib_dict[k]) + + print("Testing get mib oid") + + for i in self.mibs: + try: + oid = self.snmp_obj.get_mib_oid(i) + print('mib: %s - oid=%s' % (i, oid)) + except Exception as e: + #we shoudl NOT find only the errored mibs, all other mibs MUST be found + assert(i in self.error_mibs), "Failed to get oid for mib: " + i + print("Failed to get oid for mib: %s (expected)" % i) + if (self.error_mibs is not None): + self.error_mibs.remove(i) + + # the unit test must find all the errored mibs! + if (self.error_mibs is not None): + assert (self.error_mibs == []), "The test missed the following mibs: %s"%str(self.error_mibs) + return True + +class selftest_test_SnmpHelper(rootfs_boot.RootFSBootTest): + ''' + Tests the SnmpHelper module: + 1. compiles and get the oid of some sample mibs + 2. performs an snmp get from the lan to the wan + using hte compiled oids + ''' + + def runTest(self): + + from lib.installers import install_snmp, install_snmpd + from lib.common import snmp_mib_get + + wrong_mibs = ['PsysDescr', 'sys123ObjectID', 'sysServiceS'] + linux_mibs = ['sysDescr',\ + 'sysObjectID',\ + 'sysServices',\ + 'sysName',\ + 'sysServices',\ + 'sysUpTime'] + + test_mibs = [linux_mibs[0], wrong_mibs[0],\ + linux_mibs[1], wrong_mibs[1],\ + linux_mibs[2], wrong_mibs[2]] + + + unit_test = SnmpMibsUnitTest(mibs_location = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), + os.pardir, + 'resources', + 'mibs')), + files = ['SNMPv2-MIB'], + mibs = test_mibs, + err_mibs = wrong_mibs) + assert (unit_test.unitTest()) + + install_snmpd(wan) + + lan.sendline('echo "nameserver 8.8.8.8" >> /etc/resolv.conf') + lan.expect(lan.prompt) + + install_snmp(lan) + wan_iface_ip = wan.get_interface_ipaddr(wan.iface_dut) + + for mib in linux_mibs: + try: + result = snmp_mib_get(lan, + unit_test.snmp_obj, + str(wan_iface_ip), + mib, + '0', + community='public') + + print('snmpget({})@{}={}'.format(mib, wan_iface_ip, result)) + except Exception as e: + print('Failed on snmpget {} '.format(mib)) + print(e) + raise e + + print("Test passed") diff --git a/boardfarm/tests/snmp.py b/boardfarm/tests/snmp.py new file mode 100644 index 00000000..f792ebed --- /dev/null +++ b/boardfarm/tests/snmp.py @@ -0,0 +1,24 @@ +# Copyright (c) 2015 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import rootfs_boot +from devices import board, wan, lan, wlan, prompt + +class SNMPSysDescrWAN(rootfs_boot.RootFSBootTest): + '''Runs SNMP sysDescr on WAN iface''' + + def runTest(self): + wan.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install snmp') + wan.expect(prompt) + + wan_ip = board.get_interface_ipaddr(board.wan_iface) + + wan.sendline('snmpget -v2c -c public %s 1.3.6.1.2.1.1.1.0' % wan_ip) + wan.expect('iso.3.6.1.2.1.1.1.0 = STRING: ') + wan.expect(prompt) + + self.result_message = wan.before diff --git a/boardfarm/tests/socat.py b/boardfarm/tests/socat.py new file mode 100644 index 00000000..3e42f5b9 --- /dev/null +++ b/boardfarm/tests/socat.py @@ -0,0 +1,151 @@ +import re +import rootfs_boot +from devices import board, wan, lan, prompt + +from random import randint +import ipaddress +from faker import Factory +import pexpect +import re +import time +import random + +fake_generator = Factory.create() + +class SoCat(rootfs_boot.RootFSBootTest): + '''Super simple simulatation of HTTP traffic''' + + all_ips = [] + all_conns = [] + + conns = 100 + + socat_recv = "TCP-LISTEN" + socat_send = "TCP" + payload = '"GET / HTTP/1.0\\r\\n\\r\\n"' + + def startSingleFlow(self, mintime=1, maxtime=60): + while True: + random_ip = fake_generator.ipv4() + random_port = randint(1024, 65535) + if not ipaddress.ip_address(random_ip.decode()).is_private: + if (ipaddress.ip_address(random_ip.decode()), random_port) not in self.all_ips: + break + else: + print("Skipping ip addr: %s" % random_ip) + print("Connecting to %s:%s" % (random_ip, random_port)) + + self.all_ips.append((random_ip, random_port)) + + # start listners + wan.sendline('ip addr add %s/32 dev %s' % (random_ip, wan.iface_dut)) + wan.expect(prompt) + + random_rate = randint(1,1024) + random_size = randint(int(1*random_rate*mintime), int(1024*random_rate*maxtime)) + + args = (self.socat_recv, random_port, random_ip, self.payload, random_rate) + wan.sendline("nohup socat %s:%s,bind=%s system:'(echo -n %s; head /dev/zero) | pv -L %sk' &" % args) + print("nohup socat %s:%s,bind=%s system:'(echo -n %s; head /dev/zero) | pv -L %sk' &" % args) + wan.expect(prompt) + + args = (self.payload, random_size, random_rate, self.socat_send, random_ip, random_port) + lan.sendline("nohup socat system:'(echo -n %s; head -c %s /dev/zero) | pv -L %sk' %s:%s:%s &" % args) + print("nohup socat system:'(echo -n %s; head -c %s /dev/zero) | pv -L %sk' %s:%s:%s &" % args) + lan.expect(prompt) + + self.all_conns.append((random_size, random_rate, random_ip, random_port)) + return (random_size, random_rate, random_ip, random_port) + + def runTest(self): + random.seed(99) + + for d in [wan, lan]: + d.sendline('apt-get update && apt-get -o Dpkg::Options::="--force-confnew" -y install socat pv') + d.expect(prompt) + + max_time = 0 + single_max = 45 + + board.collect_stats(stats=['mpstat']) + + # TODO: query interfaces but this is OK for now + for i in range(self.conns): + board.get_nf_conntrack_conn_count() + board.touch() + print("Starting connection %s" % i) + sz, rate, ip, port = self.startSingleFlow(maxtime=single_max) + print("started flow to %s:%s sz = %s, rate = %sk" % (ip, port, sz, rate)) + + max_time = max(max_time, sz / ( rate * 1024)) + self.check_and_clean_ips() + + print("waiting max time of %ss" % max_time) + + start = time.time() + while time.time() - start < max_time + 5: + lan.sendline('wait') + lan.expect_exact('wait') + lan.expect([pexpect.TIMEOUT] + prompt, timeout=5) + lan.sendcontrol('c') + lan.expect(prompt) + self.check_and_clean_ips() + board.get_nf_conntrack_conn_count() + board.touch() + + self.recover() + + def cleanup_ip(self, ip): + wan.sendline('ip addr del %s/32 dev %s' % (ip, wan.iface_dut)) + wan.expect_exact('ip addr del %s/32 dev %s' % (ip, wan.iface_dut)) + wan.expect(prompt) + wan.sendline('pkill -9 -f socat.*bind=%s' % ip) + wan.expect(prompt) + lan.sendline('pkill -9 -f socat.*%s:%s' % (self.socat_send, ip)) + lan.expect(prompt) + + def check_and_clean_ips(self): + if 'TCP' in self.socat_send: + c = 'TCP' + else: + c = 'UDP' + lan.sendline("echo SYNC; ps aux | grep socat | sed -e 's/.*%s/%s/g' | tr '\n' ' '" % (c, c)) + lan.expect_exact("SYNC\r\n") + lan.expect(prompt) + seen_ips = re.findall('%s:([^:]*):' % self.socat_send, lan.before) + + if len(self.all_ips) > 0: + ips_to_cleanup = set(zip(*self.all_ips)[0]) - set(seen_ips) + for done_ip in ips_to_cleanup: + self.cleanup_ip(done_ip) + self.all_ips = [e for e in self.all_ips if e[0] != done_ip ] + + def recover(self): + wan.sendcontrol('c') + wan.expect(prompt) + wan.sendline('killall -9 socat pv') + wan.expect_exact('killall -9 socat pv') + wan.expect(prompt) + + for d in [wan, lan]: + d.sendcontrol('c') + d.expect(prompt) + d.sendline('pgrep -f d1:ad2:id20') + d.expect_exact('pgrep -f d1:ad2:id20') + d.expect(prompt) + d.sendline('pkill -9 -f d1:ad2:id20') + d.expect_exact('pkill -9 -f d1:ad2:id20') + d.expect(prompt) + d.sendline('killall -9 socat') + d.expect_exact('killall -9 socat') + d.expect(prompt) + + for ip, port in self.all_ips: + self.cleanup_ip(ip) + + # this needs to be here because we need to make sure mpstat is cleaned up + board.parse_stats(dict_to_log=self.logged) + print("mpstat cpu usage = %s" % self.logged['mpstat']) + self.result_message = "BitTorrent test with %s connections, cpu usage = %s" % (self.conns, self.logged['mpstat']) + + diff --git a/tests/ssh_forward_port.py b/boardfarm/tests/ssh_forward_port.py similarity index 95% rename from tests/ssh_forward_port.py rename to boardfarm/tests/ssh_forward_port.py index e9cb1dbb..0c167fd8 100644 --- a/tests/ssh_forward_port.py +++ b/boardfarm/tests/ssh_forward_port.py @@ -5,12 +5,10 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import time -import unittest2 import rootfs_boot import lib import pexpect -import sys + from devices import board, wan, lan, wlan, prompt class SshWanDetect(rootfs_boot.RootFSBootTest): diff --git a/tests/status.py b/boardfarm/tests/status.py similarity index 98% rename from tests/status.py rename to boardfarm/tests/status.py index a74b2c8d..1d58c823 100644 --- a/tests/status.py +++ b/boardfarm/tests/status.py @@ -7,6 +7,7 @@ import re import rootfs_boot +import pexpect from devices import board, wan, lan, wlan, prompt class Logread(rootfs_boot.RootFSBootTest): @@ -30,7 +31,7 @@ class TopCheck(rootfs_boot.RootFSBootTest): '''Ran "top" to see current processes.''' def runTest(self): board.sendline('\ntop -b -n 1') - board.expect('Mem:', timeout=5) + board.expect(pexpect.TIMEOUT, timeout=2) try: board.expect(prompt, timeout=2) except: diff --git a/tests/sysupgrade.py b/boardfarm/tests/sysupgrade.py similarity index 98% rename from tests/sysupgrade.py rename to boardfarm/tests/sysupgrade.py index 2baaccac..0300632c 100644 --- a/tests/sysupgrade.py +++ b/boardfarm/tests/sysupgrade.py @@ -5,8 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import time -import unittest2 import rootfs_boot import lib from devices import board, wan, lan, wlan, prompt diff --git a/boardfarm/tests/tcpdump.py b/boardfarm/tests/tcpdump.py new file mode 100644 index 00000000..6573a60f --- /dev/null +++ b/boardfarm/tests/tcpdump.py @@ -0,0 +1,29 @@ +# Copyright (c) 2018 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. + +import pexpect +import rootfs_boot +from devices import board, wan, lan, wlan, prompt + +class TCPDumpWANandLAN(rootfs_boot.RootFSBootTest): + '''Captures traces for WAN and LAN devices''' + + opts = "" + + def runTest(self): + for d in [wan, lan]: + d.sendline('tcpdump -i %s -w /tmp/tcpdump.pcap %s' % (d.iface_dut, self.opts)) + + board.expect(pexpect.TIMEOUT, timeout=15) + + for d in [wan, lan]: + d.sendcontrol('c') + + # TODO: copy dumps to results/ dir for logging + +class TCPDumpWANandLANfilterICMP(TCPDumpWANandLAN): + opts = "icmp" diff --git a/tests/ubus.py b/boardfarm/tests/ubus.py similarity index 89% rename from tests/ubus.py rename to boardfarm/tests/ubus.py index a19349b3..8425fe5d 100644 --- a/tests/ubus.py +++ b/boardfarm/tests/ubus.py @@ -5,7 +5,6 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import re import rootfs_boot import json import time @@ -109,11 +108,3 @@ def runTest(self): ubus_system_reboot(session_id) board.wait_for_linux() - -class UBusTestKrouter(rootfs_boot.RootFSBootTest): - '''Krouter UBus tests''' - def runTest(self): - ubus_call("00000000000000000000000000000000", "krouter", "add_krouter_endpoint", { "macaddr": "00:11:22:33:44:55" }) - session_id = ubus_login_session() - ret = ubus_call(session_id, "krouter", "is_krouter_endpoint", { "macaddr": "00:11:22:33:44:55" }) - print ret diff --git a/tests/uci_wireless.py b/boardfarm/tests/uci_wireless.py similarity index 100% rename from tests/uci_wireless.py rename to boardfarm/tests/uci_wireless.py diff --git a/tests/uname.py b/boardfarm/tests/uname.py similarity index 100% rename from tests/uname.py rename to boardfarm/tests/uname.py diff --git a/tests/vmstat.py b/boardfarm/tests/vmstat.py similarity index 68% rename from tests/vmstat.py rename to boardfarm/tests/vmstat.py index 1afe1357..05931e14 100644 --- a/tests/vmstat.py +++ b/boardfarm/tests/vmstat.py @@ -5,20 +5,14 @@ # This file is distributed under the Clear BSD license. # The full text can be found in LICENSE in the root directory. -import re import rootfs_boot from devices import board, wan, lan, wlan, prompt class ProcVmstat(rootfs_boot.RootFSBootTest): '''Check /proc/vmstat stats.''' def runTest(self): - # Log every field value found - board.sendline('\ncat /proc/vmstat') - board.expect('cat /proc/vmstat') - board.expect(prompt) - results = re.findall('(\w+) (\d+)', board.before) - for key, value in results: - self.logged[key] = int(value) + self.logged.update(board.get_proc_vmstat()) + # Display extra info board.sendline('cat /proc/slabinfo /proc/buddyinfo /proc/meminfo') board.expect('cat /proc/') diff --git a/tests/webbrowse.py b/boardfarm/tests/webbrowse.py similarity index 95% rename from tests/webbrowse.py rename to boardfarm/tests/webbrowse.py index 8f33ec09..fba822ae 100644 --- a/tests/webbrowse.py +++ b/boardfarm/tests/webbrowse.py @@ -8,10 +8,13 @@ import random import rootfs_boot from devices import board, wan, lan, wlan, prompt +from lib import installers class RandomWebBrowse(rootfs_boot.RootFSBootTest): '''Created light web traffic.''' def runTest(self): + installers.apt_install(lan, "wget") + urls = ['www.amazon.com', 'www.apple.com', 'www.baidu.com', @@ -55,3 +58,5 @@ def runTest(self): lan.expect(prompt) lan.sendline("rm -rf /tmp/webbrowse-test") lan.expect(prompt) + + board.touch() diff --git a/tests/webgui.py b/boardfarm/tests/webgui.py similarity index 91% rename from tests/webgui.py rename to boardfarm/tests/webgui.py index 8783844e..1b80e7d3 100644 --- a/tests/webgui.py +++ b/boardfarm/tests/webgui.py @@ -18,7 +18,8 @@ def runTest(self): class WebGUI_Access(rootfs_boot.RootFSBootTest): '''Router webpage available to LAN-device at http://192.168.1.1/.''' def runTest(self): - url = 'http://192.168.1.1/' + ip = "192.168.1.1" + url = 'http://%s/' % ip lan.sendline('\ncurl -v %s' % url) lan.expect('<html') lan.expect('<body') @@ -37,10 +38,11 @@ def runTest(self): class Webserver_Download(rootfs_boot.RootFSBootTest): '''Downloaded small file from router webserver in reasonable time.''' def runTest(self): + ip = "192.168.1.1" board.sendline('\nhead -c 1000000 /dev/urandom > /www/deleteme.txt') board.expect('head ', timeout=5) board.expect(prompt) - lan.sendline('\ncurl -m 25 http://192.168.1.1/deleteme.txt > /dev/null') + lan.sendline('\ncurl -m 25 http://%s/deleteme.txt > /dev/null' % ip) lan.expect('Total', timeout=5) lan.expect('100 ', timeout=10) lan.expect(prompt, timeout=10) diff --git a/tests/webui_tests.py b/boardfarm/tests/webui_tests.py similarity index 93% rename from tests/webui_tests.py rename to boardfarm/tests/webui_tests.py index 749f73f0..a1cd851d 100644 --- a/tests/webui_tests.py +++ b/boardfarm/tests/webui_tests.py @@ -14,6 +14,8 @@ class WebTest(rootfs_boot.RootFSBootTest): '''Login to LuCI''' def setUp(self): + ip = "192.168.1.1" + super(WebTest, self).setUp() if not lan: msg = 'No LAN Device defined, skipping web test.' @@ -30,8 +32,8 @@ def setUp(self): # Create a driver self.driver = lib.common.phantom_webproxy_driver('http://' + lan.name + ':8080') - self.driver.get("http://192.168.1.1/cgi-bin/luci") - self.assertIn('192.168.1.1', self.driver.current_url) + self.driver.get("http://%s/cgi-bin/luci" % ip) + self.assertIn(ip, self.driver.current_url) self.assertIn('LuCI', self.driver.title) self.driver.find_element_by_name('luci_password').send_keys('password') self.driver.find_element_by_class_name('cbi-button-apply').submit() diff --git a/boardfarm/tests/wifi.py b/boardfarm/tests/wifi.py new file mode 100644 index 00000000..cdfa18f1 --- /dev/null +++ b/boardfarm/tests/wifi.py @@ -0,0 +1,10 @@ + +import rootfs_boot +from devices import wlan + +class WifiScan(rootfs_boot.RootFSBootTest): + '''Simple test to run a wifi scan''' + + def runTest(self): + scan_output = wlan.scan() + return scan_output diff --git a/testsuites.cfg b/boardfarm/testsuites.cfg similarity index 84% rename from testsuites.cfg rename to boardfarm/testsuites.cfg index a5e90a86..29a02281 100644 --- a/testsuites.cfg +++ b/boardfarm/testsuites.cfg @@ -124,3 +124,22 @@ RebootHard MemoryUse RebootHard MemoryUse + +[selftest] +selftest_test_copy_file_to_server +selftest_test_create_session +selftest_testing_linuxdevice_functions +selftest_test_SnmpHelper + +[travisci] +RootFSBootTest +KernelModules +MemoryUse +InterfacesShow +LanDevPingRouter +RouterPingWanDev +RouterPingInternet +RouterPingInternetName +LanDevPingWanDev +@selftest +iPerf3Test diff --git a/testsuites.py b/boardfarm/testsuites.py similarity index 71% rename from testsuites.py rename to boardfarm/testsuites.py index 68d41610..937d4cbf 100644 --- a/testsuites.py +++ b/boardfarm/testsuites.py @@ -10,7 +10,14 @@ import config import devices.configreader tmp = devices.configreader.TestsuiteConfigReader() -tmp.read(config.testsuite_config_files) + +config_files = config.testsuite_config_files +for ovrly_name, ovrly in config.layerconfs: + if hasattr(ovrly, 'testsuite_config_files'): + config_files += ovrly.testsuite_config_files + +tmp.read(config_files) + list_tests = tmp.section # Create long or complicated test suites at run time. diff --git a/boardfarm/zephyr/README.md b/boardfarm/zephyr/README.md new file mode 100644 index 00000000..4e7e4c9e --- /dev/null +++ b/boardfarm/zephyr/README.md @@ -0,0 +1,32 @@ +Zephyr Integration with boardfarm: +=================================== +Zephyr is an add-on for Jira that allows us to integrate test management into the one system. + +Folder and file structure follows as below for the integration of Zephyr with boardfarm + +1. boardfarm_tc_meta_file.csv + Meta file which has the mapping between Boardfarm test cases with JIRA test cases + +2. zapi_configuration.json + Configuration file which has the basic configuration settings for Zephyr, main values as follows, + + 1. metafile : "zephyr/boardfarm_tc_meta_file.csv" + Meta file location which has the mapping between boardfarm test cases with JIRA cases + 2. project : "RDKB" + JIRA projet name in which JIRA tickets has been created + 3. release" : "7.6.1" + JIRA relase version + 4. cycle : "Demo-zephyr-cycle" + Zephyr cycle name for the test cases execution. Set of all testcase execution result will be updated in a single cycle in Zephyr + 5. build : "DemoBuild" + Specify the build name, boardfarm testcases are testing against. + 6. Jira_url : "JIRAURL" + 7. user : "JIRAuser" + 8. passwd : "JIRApasswd", + Username and password to access JIRA + +3. zapi.py + Rest Api for Zephyr Functionlities + +4. zephyr_reporter.py + Code to update the reault data from boardfarm execution to Zephyr diff --git a/boardfarm/zephyr/__init__.py b/boardfarm/zephyr/__init__.py new file mode 100644 index 00000000..81167843 --- /dev/null +++ b/boardfarm/zephyr/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2019 +# +# All rights reserved. +# +# This file is distributed under the Clear BSD license. +# The full text can be found in LICENSE in the root directory. diff --git a/boardfarm/zephyr/boardfarm_tc_meta_file.csv b/boardfarm/zephyr/boardfarm_tc_meta_file.csv new file mode 100644 index 00000000..df0bc6b9 --- /dev/null +++ b/boardfarm/zephyr/boardfarm_tc_meta_file.csv @@ -0,0 +1 @@ +Jira ID,TestScript Name,Jira_tittle diff --git a/boardfarm/zephyr/zapi.py b/boardfarm/zephyr/zapi.py new file mode 100644 index 00000000..c28c4f71 --- /dev/null +++ b/boardfarm/zephyr/zapi.py @@ -0,0 +1,151 @@ +#!/usr/bin/python2 +""""Methods to interact with the Zephyr API on top of Jira""" +import re +from requests import get, put, post +from simplejson import loads + + +STATUS_CODE_DICT = {'SCHEDULED': -1, + 'PASS': 1, + 'FAIL': 2, + 'WORK IN PROGRESS': 3, + 'TEST SUSPENDED': 4, + 'ACCEPTED FAILED': 5, + 'NOT TESTED': 6, + 'BLOCKED': 7, + 'NA': 8, + 'DEPRECATED': 9, + 'PASSED WITH REMARKS': 10} + + +class Zapi(object): + """Zephyr API interface""" + + def __init__(self, + project_id=None, + version_id=None, + environment="", + build=None, + usr=None, + pwd=None, + jira_url=None): + self._jira_url = jira_url + self._zapi_url = self._jira_url + 'rest/zapi/latest/' + self._zapi_hdr = {'Content-type': 'application/json'} + self._proj_id = project_id + self._vers_id = version_id + self._environment = environment + self._usr = usr + self._pwd = pwd + self._cycl_id = None + self._build = build + + def create_cycle(self, name): + """Creata a test cycle for the given projectid, versionid""" + data = {"name": name, + "projectId": self._proj_id, + "versionId": self._vers_id, + "environment": self._environment, + "build": self._build} + req_url = self._zapi_url + 'cycle' + response = post(req_url, + json=data, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + data = loads(response.text) + return data['id'] + + def get_cycle_id(self, cycle_name): + """Retrieve the cyccle id for a given Zephyr cycle name""" + req_url = "{}cycle?projectID={}&versionId={}".format( + self._zapi_url, self._proj_id, self._vers_id) + response = get(req_url, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + data = loads(response.text) + pattern = re.compile('-?\\d') + for k in dict(data).keys(): + match = pattern.match(k) + cycle_id = 0 + if match: + cycle_id = int(k) + if cycle_id > 0 and data[str(k)]['name'] == cycle_name: + return k + return '' + + def get_or_create_cycle(self, name): + """Return the cycleId for a given cycle name + If not found the cycle is created""" + cycle_id = self.get_cycle_id(name) + if not cycle_id: + cycle_id = self.create_cycle(name) + self._cycl_id = cycle_id + + def create_execution(self, test_id, assignee=None): + """Create a Zephyr execution of the given test in the given project and + release""" + payload = {'projectId': self._proj_id, + 'cycleId': self._cycl_id, + 'issueId': test_id, + 'versionId': self._vers_id, + 'assigneeType': 'assignee', + 'assignee': assignee or self._usr} + req_url = self._zapi_url + 'execution' + response = post(req_url, + json=payload, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + data = loads(response.text) + execution_id = dict(data).keys()[0] + #execution_id = "442290" + if response.status_code <> 200: + print("WARNING: " + response.text) + print(req_url) + print(payload) + return execution_id + + def get_executions(self, test_id=None, assignee=None): + """Create a Zephyr execution of the given test in the given project and + release""" + payload = {'projectId': self._proj_id, + 'cycleId': self._cycl_id, + 'issueId': test_id, + 'versionId': self._vers_id + } + req_url = self._zapi_url + 'execution' + response = get(req_url, + params=payload, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + data = loads(response.text) + executions = data.get('executions') or [] + return executions + + def set_execution_field(self, execution_id, field, value): + """Set the execution status of a given test's executionid""" + data = {field: value} + req_url = self._zapi_url + 'execution/' + execution_id + '/' + response = post(req_url, + params=data, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + if response.status_code <> 200: + print("WARNING: " + response.text) + print(req_url) + print(data) + return response + + def set_execution(self, exec_status, execution_id, comment="", status_code_dict=STATUS_CODE_DICT): + """Set the execution status of a given test's executionid""" + data = {"status": status_code_dict[exec_status], + "comment": comment} + req_url = self._zapi_url + 'execution/' + execution_id + '/execute' + response = put(req_url, + json=data, + headers=self._zapi_hdr, + auth=(self._usr, self._pwd)) + if response.status_code <> 200: + print("WARNING: " + response.text) + print(req_url) + print(data) + return response diff --git a/boardfarm/zephyr/zephyr_reporter.py b/boardfarm/zephyr/zephyr_reporter.py new file mode 100644 index 00000000..98649139 --- /dev/null +++ b/boardfarm/zephyr/zephyr_reporter.py @@ -0,0 +1,179 @@ +#!/usr/bin/python2 +import argparse +import os +import csv +import datetime +import json +from jira import JIRA +import zapi +import requests + +COLUMN_SCRIPT_NAME="TestScript Name" +COLUMN_JIRA_TEST_ID="Jira ID" + +def parse_arguments(): + """Parses imput arguments and returns them to the main routine. + Also takes care of prompting help""" + parser = argparse.ArgumentParser( + description='Post TDK Framework execution results to Zephyr (Jira)', + epilog="i.e. %(prog)s") + parser.add_argument("--report", "-rf", + help="The TDK Framework output file. \ + Default: output.xml", + default="output.xml") + parser.add_argument("--metafile", "-mf", + help="The csv file containing TDK tests and test case IDs \ + Default: tdktests.csv", + default="tdktests.csv") + parser.add_argument("--project", "-pr", + help="The Jira project where to update the results \ + Default: RDK-B", + default="RDKB") + parser.add_argument("--release", "-r", + help="The release version in Jira", + default="7.6.1") + parser.add_argument("--environment", "-e", required=False, + help="A string that identifies the environment.", + default="Lab 5C") + parser.add_argument("--cycle", "-c", required=False, + help="The name of the test cycle. \ + When not given, the cycle gets the name of the build", + default=None) + parser.add_argument("--build", "-b", required=False, + help="The build (software version) under test. \ + A cycle with this name is created if not otherwise \ + specified", default = "DemoBuild") + parser.add_argument("--user", "-u", required=False, + help="The Jira user that is publishing the results", default = USR) + parser.add_argument("--passwd", "-p", required=False, + help="The Jira password of the given user", default = PWD) + parser.add_argument("--updateautomationstatus", "-a", required=False, + help="When True it marks the test automation status \ + in Jira", + action="store_true") + + args = parser.parse_args() + return args + +def get_jira_release_id(rel_name, jira, proj): + """Return the ID of the release in a given project""" + versions = jira.project_versions(proj) + for version in reversed(versions): + if version.name == rel_name: + return version.id + + raise Exception("Failed to get version id for release %s" % rel_name) + + +def update_automation_status(issue): + """Update the Jira custom field to track that the test is automated""" + #if issue.fields.customfield_11640.value != 'Automated test': + # issue.update(fields={'customfield_11640': {'value': 'Automated test'}}) + #return + + +def get_test_id_from_meta_file(meta_file, test_name): + reader = csv.DictReader(open(meta_file)) + test_id = "" + for row in reader: + if row["TestScript Name"] == test_name: + test_id = row[COLUMN_JIRA_TEST_ID] + return test_id + +def parse_zapi_config(): + data = [] + if 'BFT_OVERLAY' in os.environ: + for overlay in os.environ['BFT_OVERLAY'].split(' '): + overlay = os.path.realpath(overlay) + zdir = os.path.join(os.path.abspath(overlay), 'zephyr') + if os.path.exists(zdir): + data.append(json.load(open(os.path.join(zdir, 'zapi_configuration.json')))) + data[-1]['metafile'] = os.path.join(zdir, 'boardfarm_tc_meta_file.csv') + + # TODO: opensource zephyr for boardfarm tests? + if os.path.exists('zephyr/zapi_configuration.json'): + data.append(json.load(open('zephyr/zapi_configuration.json'))) + + return data + +def update_zephyr(test_cases_list): + args=parse_zapi_config() + if len(args) == 0: + print("Zephyr is not configured, skipping...") + return + print('Starting Zephyr Execution....') + + for z in args: + if "JIRA_URL" == z['jira_url'] or "JIRAPASSWORD" == z['passwd']: + # This is not configure, skip to next + continue + + """"Main routine""" + + jira = JIRA(basic_auth=(z["user"], z["passwd"]), + options={'server': z["jira_url"]}) + + proj = jira.project(z["project"]) + verid = get_jira_release_id(z['release'], jira, proj) + cycleName = z["cycle"] + cycleName = cycleName + "_" + str((datetime.datetime.now()).strftime("%Y%m%d%H%M%S")) + + + reporter = zapi.Zapi(project_id=proj.id, + version_id=verid, + environment=str(z["environment"]), + build=z["build"], + jira_url=z["jira_url"], + usr=z["user"], + pwd=z["passwd"]) + if z["cycle"] is None: + z["cycle"] = z["build"] + reporter.get_or_create_cycle(str(cycleName)) + + result = "" + + for i in range(len(test_cases_list)): + test_name = test_cases_list[i][0] + print("Test_name :" + test_name) + test_id = get_test_id_from_meta_file(z["metafile"], test_name) + + if test_id: + print("Found Test ID in Meta file : " + test_id) + issue = jira.issue(test_id) + else: + continue + + if z["updateautomationstatus"]: + update_automation_status(issue) + + exec_id = reporter.create_execution(str(issue.id)) + result = test_cases_list[i][1] + print("Test case Result: " + result) + log_data = "sample log data" + if result == 'FAIL': + result = 'FAIL' + if result == 'OK': + result = 'PASS' + if result == 'None': + result = 'FAIL' + if result == 'SKIP': + result = 'NOT TESTED' + if result == 'Exp FAIL': + result = 'FAIL' + + if 'status_codes' in z: + ret = reporter.set_execution(result, + exec_id, + log_data, + status_code_dict=z['status_codes']) + else: + ret = reporter.set_execution(result, + exec_id, + log_data) + + if ret.status_code != requests.codes.ok: + raise Exception("Error = %s, when trying to set execution status" % ret) + +if __name__ == "__main__": + ARGS = parse_arguments() + main(ARGS) diff --git a/boardfarm_config_example.json b/boardfarm_config_example.json deleted file mode 100644 index 75e18e32..00000000 --- a/boardfarm_config_example.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "rpi3-1": { - "board_type": "rpi3", - "conn_cmd": "cu -l /dev/ttyUSB4 -s 115200", - "devices": [ - { - "type": "debian", - "name": "wan", - "pre_cmd_host": "docker build -t bft:node bft-node", - "cmd": "docker run --name wan --privileged -it bft:node /bin/bash", - "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' wan) dev enx00249b14dc6e", - "post_cmd": "ip link set enx00249b14dc6e name eth1", - "cleanup_cmd": "docker stop wan; docker rm wan", - "color": "cyan", - "options": "tftpd-server" - }, - { - "type": "debian", - "name": "lan", - "pre_cmd_host": "docker build -t bft:node bft-node", - "cmd": "docker run --name lan --privileged -it bft:node /bin/bash", - "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' lan) dev enx0050b61bfde4", - "post_cmd": "ip link set enx0050b61bfde4 name eth1", - "cleanup_cmd": "docker stop lan; docker rm lan", - "color": "blue" - } - ], - "connection_type": "local_serial", - "notes": "Rpi3 device with docker containers attached to WAN/LAN" - } -} diff --git a/config.py b/config.py deleted file mode 100644 index f8fa0113..00000000 --- a/config.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import os - -# Boardfarm configuration describes test stations - see boardfarm doc. -# Can be local or remote file. -boardfarm_config_location = os.environ.get('BFT_CONFIG', 'boardfarm_config_example.json') - -# Test Suite config files. Standard python config file format. -testsuite_config_files = [os.path.join(os.path.dirname(os.path.realpath(__file__)), 'testsuites.cfg'), ] - -# Logstash server - a place to send JSON-format results to -# when finished. Set to None or name:port, e.g. 'logstash.mysite.com:1300' -logging_server = None - -# Elasticsearch server. Data in JSON-format can be directly sent here. -# Set to None or to a valid host, see documentation: -# https://elasticsearch-py.readthedocs.org/en/master/api.html#elasticsearch -elasticsearch_server = os.environ.get('BFT_ELASTICSERVER', None) - -# Code change server like gerrit, github, etc... Used only in display -# of the results html file to list links to code changes tested. -code_change_server = None diff --git a/deploy-boardfarm-nodes.sh b/deploy-boardfarm-nodes.sh new file mode 120000 index 00000000..f1140f90 --- /dev/null +++ b/deploy-boardfarm-nodes.sh @@ -0,0 +1 @@ +boardfarm/deploy-boardfarm-nodes.sh \ No newline at end of file diff --git a/devices/__init__.py b/devices/__init__.py deleted file mode 100644 index 109b0ac1..00000000 --- a/devices/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -''' - - This directory contains classes for connecting to and controlling - devices over a network. - -''' -board = None -lan = None -wan = None -wlan = None -wlan2g = None -wlan5g = None -prompt = None -def initialize_devices(configuration): - # Init random global variables. To Do: clean these. - global power_ip, power_outlet - conn_cmd = configuration.board.get('conn_cmd') - power_ip = configuration.board.get('powerip', None) - power_outlet = configuration.board.get('powerport', None) - # Init devices - global board, lan, wan, wlan, wlan2g, wlan5g, prompt - board = configuration.console - lan = configuration.lan - wan = configuration.wan - wlan = configuration.wlan - wlan2g = configuration.wlan2g - wlan5g = configuration.wlan5g - - for device in configuration.devices: - globals()[device] = getattr(configuration, device) - - board.root_type = None - # Next few lines combines all the prompts into one list of unique prompts. - # It lets test writers use "some_device.expect(prompt)" - prompt = [] - for d in (board, lan, wan, wlan): - prompt += getattr(d, "prompt", []) - prompt = list(set(prompt)) diff --git a/devices/base.py b/devices/base.py deleted file mode 100644 index 50344829..00000000 --- a/devices/base.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import pexpect -from termcolor import colored -from datetime import datetime -import re - - -class BaseDevice(pexpect.spawn): - - prompt = ['root\\@.*:.*#', ] - - def get_interface_ipaddr(self, interface): - self.sendline("\nifconfig %s" % interface) - self.expect('addr:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(Bcast|P-t-P):', timeout=5) - ipaddr = self.match.group(1) - self.expect(self.prompt) - return ipaddr - - def get_logfile_read(self): - if hasattr(self, "_logfile_read"): - return self._logfile_read - else: - return None - - def expect_prompt(self, timeout=30): - self.expect(self.prompt, timeout=timeout) - - def check_output(self, cmd, timeout=30): - '''Send a string to device, then return the output - between that string and the next prompt.''' - self.sendline("\n" + cmd) - self.expect_exact(cmd, timeout=5) - try: - self.expect(self.prompt, timeout=timeout) - except Exception as e: - self.sendcontrol('c') - raise Exception("Command did not complete within %s seconds. Prompt was not seen." % timeout) - return self.before - - def write(self, string): - self._logfile_read.write(string) - self.log += string - - def set_logfile_read(self, value): - class o_helper(): - def __init__(self, out, color): - self.color = color - self.out = out - self.log = "" - self.start = datetime.now() - def write(self, string): - if self.color is not None: - self.out.write(colored(string, self.color)) - else: - self.out.write(string) - td = datetime.now()-self.start - ts = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 - # check for the split case - if len(self.log) > 1 and self.log[-1] == '\r' and string[0] == '\n': - tmp = '\n [%s]' % ts - tmp += string[1:] - string = tmp - self.log += re.sub('\r\n', '\r\n[%s] ' % ts, string) - def flush(self): - self.out.flush() - - if value is not None: - self._logfile_read = o_helper(value, getattr(self, "color", None)) - - def get_log(self): - return self._logfile_read.log - - logfile_read = property(get_logfile_read, set_logfile_read) - log = property(get_log) - - # perf related - def parse_sar_iface_pkts(self, wan, lan): - self.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){6}\r\n') - idle = float(self.match.group(1)) - self.expect("Average.*rxmcst/s.*\r\n") - - wan_pps = None - client_pps = None - if lan is None: - exp = [wan] - else: - exp = [wan,lan] - - for x in range(0, len(exp)): - i = self.expect(exp) - if i == 0: # parse wan stats - self.expect("(\d+.\d+)\s+(\d+.\d+)") - wan_pps = float(self.match.group(1)) + float(self.match.group(2)) - if i == 1: - self.expect("(\d+.\d+)\s+(\d+.\d+)") - client_pps = float(self.match.group(1)) + float(self.match.group(2)) - - return idle, wan_pps, client_pps - - def check_perf(self): - self.sendline('uname -r') - self.expect('uname -r') - self.expect(self.prompt) - - self.kernel_version = self.before - - self.sendline('\nperf --version') - i = self.expect(['not found', 'perf version']) - self.expect(self.prompt) - - if i == 0: - return False - - return True - - def check_output_perf(self, cmd, events): - perf_args = self.perf_args(events) - - self.sendline("perf stat -a -e %s time %s" % (perf_args, cmd)) - - def parse_perf(self, events): - mapping = self.parse_perf_board() - ret = [] - - for e in mapping: - if e['name'] not in events: - continue - self.expect("(\d+) %s" % e['expect']) - e['value'] = int(self.match.group(1)) - ret.append(e) - - return ret - - # end perf related diff --git a/devices/board_decider.py b/devices/board_decider.py deleted file mode 100644 index bb5f2c99..00000000 --- a/devices/board_decider.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import openwrt_router -import qcom_akronite_nand -import qcom_akronite_nor -import qcom_dakota_nor -import qcom_dakota_nand -import qcom_mips -import marvell -import rpi - -def board(model, **kwargs): - ''' - Depending on the given model, return an object of the appropriate class type. - - Different boards are flashed with different commands, have - different memory addresses, and must be handled differently. - ''' - if model in ("db120", "ap135", "ap143", "ap147", "ap152", "ap151", - "ap151-16M", "ap143", "ap152-8M", "tew-823dru"): - return qcom_mips.QcomMipsRouter(model, **kwargs) - - if model in ("ipq8066", "db149", "ap145", "ap148", "ap148-osprey", - "ap148-beeliner", "ap160-1", "ap160-2", "ap161"): - return qcom_akronite_nand.QcomAkroniteRouterNAND(model, **kwargs) - - if model in ("ap148-nor"): - return qcom_akronite_nor.QcomAkroniteRouterNOR(model, **kwargs) - - if model in ("dk01-nor", "dk04-nor"): - return qcom_dakota_nor.QcomDakotaRouterNOR(model, **kwargs) - - if model in ("dk07-nand", "dk04-nand", "ea8300"): - return qcom_dakota_nand.QcomDakotaRouterNAND(model, **kwargs) - - if model in ("wrt3200acm"): - return marvell.WRT3200ACM(model, **kwargs) - - if model in ("rpi3"): - return rpi.RPI(model, **kwargs) - - # Default for all other models - print("\nWARNING: Unknown board model '%s'." % model) - print("Please check spelling, or write an appropriate class " - "to handle that kind of board.") - return openwrt_router.OpenWrtRouter(model, **kwargs) diff --git a/devices/debian.py b/devices/debian.py deleted file mode 100644 index 1e55db7c..00000000 --- a/devices/debian.py +++ /dev/null @@ -1,409 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import sys -import time -import pexpect -import base -import atexit - -from termcolor import colored, cprint - - -class DebianBox(base.BaseDevice): - ''' - A linux machine running an ssh server. - ''' - - prompt = ['root\\@.*:.*#', '/ # ', ".*:~ #" ] - - def __init__(self, - name, - color, - username, - password, - port, - output=sys.stdout, - reboot=False, - location=None, - pre_cmd_host=None, - cmd=None, - post_cmd_host=None, - post_cmd=None, - cleanup_cmd=None): - if name is not None: - pexpect.spawn.__init__(self, - command="ssh", - args=['%s@%s' % (username, name), - '-p', port, - '-o', 'StrictHostKeyChecking=no', - '-o', 'UserKnownHostsFile=/dev/null']) - self.name = name - else: - name = None - if pre_cmd_host is not None: - sys.stdout.write("\tRunning pre_host_cmd.... ") - sys.stdout.flush() - phc = pexpect.spawn(command='bash', args=['-c', pre_cmd_host]) - phc.expect(pexpect.EOF, timeout=120) - print("\tpre_host_cmd done") - - if cleanup_cmd is not None: - self.cleanup_cmd = cleanup_cmd - atexit.register(self.run_cleanup_cmd) - - pexpect.spawn.__init__(self, command="bash", args=['-c', cmd]) - - self.color = color - self.output = output - self.username = username - self.password = password - self.port = port - self.location = location - try: - i = self.expect(["yes/no", "assword:", "Last login"] + self.prompt, timeout=30) - except pexpect.TIMEOUT as e: - raise Exception("Unable to connect to %s." % name) - except pexpect.EOF as e: - if hasattr(self, "before"): - print(self.before) - raise Exception("Unable to connect to %s." % name) - if i == 0: - self.sendline("yes") - i = self.expect(["Last login", "assword:"]) - if i == 1: - self.sendline(password) - else: - pass - # if we did initially get a prompt wait for one here - if i < 3: - self.expect(self.prompt) - - if name is None: - self.sendline('hostname') - self.expect('hostname') - self.expect(self.prompt) - name = self.name = self.before.strip() - - cprint("%s device console = %s" % (name, colored(color, color)), None, attrs=['bold']) - - if post_cmd_host is not None: - sys.stdout.write("\tRunning post_cmd_host.... ") - sys.stdout.flush() - phc = pexpect.spawn(command='bash', args=['-c', post_cmd_host]) - i = phc.expect([pexpect.EOF, pexpect.TIMEOUT, 'password']) - if i > 0: - print("\tpost_cmd_host did not complete, it likely failed\n") - else: - print("\tpost_cmd_host done") - - if post_cmd is not None: - self.sendline(post_cmd) - self.expect(self.prompt) - - if reboot: - self.reset() - - self.logfile_read = output - - def run_cleanup_cmd(self): - sys.stdout.write("Running cleanup_cmd on %s..." % self.name) - sys.stdout.flush() - cc = pexpect.spawn(command='bash', args=['-c', self.cleanup_cmd]) - cc.expect(pexpect.EOF, timeout=120) - print("cleanup_cmd done.") - - def reset(self): - self.sendline('reboot') - self.expect(['going down','disconnected']) - try: - self.expect(self.prompt, timeout=10) - except: - pass - time.sleep(15) # Wait for the network to go down. - for i in range(0, 20): - try: - pexpect.spawn('ping -w 1 -c 1 ' + self.name).expect('64 bytes', timeout=1) - except: - print(self.name + " not up yet, after %s seconds." % (i + 15)) - else: - print("%s is back after %s seconds, waiting for network daemons to spawn." % (self.name, i + 14)) - time.sleep(15) - break - self.__init__(self.name, self.color, - self.output, self.username, - self.password, self.port, - reboot=False) - - def get_ip_addr(self, interface): - self.sendline("\nifconfig %s" % interface) - self.expect('addr:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(Bcast|P-t-P):', timeout=5) - ipaddr = self.match.group(1) - self.expect(self.prompt) - return ipaddr - - def ip_neigh_flush(self): - self.sendline('\nip -s neigh flush all') - self.expect('flush all') - self.expect(self.prompt) - - def turn_on_pppoe(self): - self.sendline('apt-get -o Dpkg::Options::="--force-confnew" -y install pppoe') - self.expect(self.prompt) - self.sendline('cat > /etc/ppp/pppoe-server-options << EOF') - self.sendline('noauth') - self.sendline('ms-dns 8.8.8.8') - self.sendline('ms-dns 8.8.4.4') - self.sendline('EOF') - self.expect(self.prompt) - self.sendline('pppoe-server -k -I eth1 -L 192.168.2.1 -R 192.168.2.10 -N 4') - self.expect(self.prompt) - - def turn_off_pppoe(self): - self.sendline("\nkillall pppoe-server pppoe pppd") - self.expect("pppd") - self.expect(self.prompt) - - def start_tftp_server(self): - # set WAN ip address, for now this will always be this address for the device side - self.sendline('ifconfig eth1 down') - self.expect(self.prompt) - - # install packages required - self.sendline('apt-get -o DPkg::Options::="--force-confnew" -qy install tftpd-hpa') - - # set WAN ip address, for now this will always be this address for the device side - self.sendline('ifconfig eth1 192.168.0.1') - self.expect(self.prompt) - - #configure tftp server - self.sendline('/etc/init.d/tftpd-hpa stop') - self.expect('Stopping') - self.expect(self.prompt) - self.sendline('rm -rf /tftpboot') - self.expect(self.prompt) - self.sendline('rm -rf /srv/tftp') - self.expect(self.prompt) - self.sendline('mkdir -p /srv/tftp') - self.expect(self.prompt) - self.sendline('ln -sf /srv/tftp/ /tftpboot') - self.expect(self.prompt) - self.sendline('mkdir -p /tftpboot/tmp') - self.expect(self.prompt) - self.sendline('chmod a+w /tftpboot/tmp') - self.expect(self.prompt) - self.sendline('mkdir -p /tftpboot/crashdump') - self.expect(self.prompt) - self.sendline('chmod a+w /tftpboot/crashdump') - self.expect(self.prompt) - self.sendline('sed /TFTP_OPTIONS/d -i /etc/default/tftpd-hpa') - self.expect(self.prompt) - self.sendline('echo TFTP_OPTIONS=\\"-4 --secure --create\\" >> /etc/default/tftpd-hpa') - self.expect(self.prompt) - self.sendline('sed /TFTP_DIRECTORY/d -i /etc/default/tftpd-hpa') - self.expect(self.prompt) - self.sendline('echo TFTP_DIRECTORY=\\"/srv/tftp\\" >> /etc/default/tftpd-hpa') - self.expect(self.prompt) - self.sendline('/etc/init.d/tftpd-hpa restart') - self.expect(self.prompt) - - def restart_tftp_server(self): - self.sendline('\n/etc/init.d/tftpd-hpa restart') - self.expect('Restarting') - self.expect(self.prompt) - - def configure(self, kind): - if kind == "wan_device": - self.setup_as_wan_gateway() - elif kind == "lan_device": - self.setup_as_lan_device() - - def setup_as_wan_gateway(self): - self.sendline('killall iperf ab hping3') - self.expect(self.prompt) - self.sendline('\nsysctl net.ipv6.conf.all.disable_ipv6=0') - self.expect('sysctl ') - self.expect(self.prompt) - - # potential cleanup so this wan device works - self.sendline('iptables -t nat -X') - self.expect(self.prompt) - self.sendline('iptables -t nat -F') - self.expect(self.prompt) - - # install packages required - self.sendline('apt-get -o DPkg::Options::="--force-confnew" -qy install isc-dhcp-server procps iptables lighttpd') - self.expect(self.prompt) - - # set WAN ip address - self.sendline('ifconfig eth1 192.168.0.1') - self.expect(self.prompt) - self.sendline('ifconfig eth1 up') - self.expect(self.prompt) - - # start openssh server if not running: - self.sendline('/etc/init.d/ssh start') - self.expect(self.prompt) - self.sendline('sed "s/PermitRootLogin.*/PermitRootLogin yes/g" -i /etc/ssh/sshd_config') - self.expect(self.prompt) - self.sendline('/etc/init.d/ssh reload') - self.expect(self.prompt) - - # configure DHCP server - self.sendline('/etc/init.d/isc-dhcp-server stop') - self.expect(self.prompt) - self.sendline('sed s/INTERFACES=.*/INTERFACES=\\"eth1\\"/g -i /etc/default/isc-dhcp-server') - self.expect(self.prompt) - self.sendline('cat > /etc/dhcp/dhcpd.conf << EOF') - self.sendline('ddns-update-style none;') - self.sendline('option domain-name "bigfoot-test";') - self.sendline('option domain-name-servers 8.8.8.8, 8.8.4.4;') - self.sendline('default-lease-time 600;') - self.sendline('max-lease-time 7200;') - self.sendline('subnet 192.168.0.0 netmask 255.255.255.0 {') - self.sendline(' range 192.168.0.10 192.168.0.100;') - self.sendline(' option routers 192.168.0.1;') - self.sendline('}') - self.sendline('EOF') - self.expect(self.prompt) - self.sendline('/etc/init.d/isc-dhcp-server start') - self.expect(['Starting ISC DHCP server.*dhcpd.', 'Starting isc-dhcp-server.*']) - self.expect(self.prompt) - - # configure routing - self.sendline('sysctl net.ipv4.ip_forward=1') - self.expect(self.prompt) - wan_ip_uplink = self.get_ip_addr("eth0") - self.sendline('iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source %s' % wan_ip_uplink) - self.expect(self.prompt) - - self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_timestamps') - self.expect(self.prompt) - self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_sack') - self.expect(self.prompt) - - self.sendline('ifconfig eth1') - self.expect(self.prompt) - - self.turn_off_pppoe() - - def setup_as_lan_device(self, gw="192.168.1.1"): - # potential cleanup so this wan device works - self.sendline('killall iperf ab hping3') - self.expect(self.prompt) - self.sendline('\niptables -t nat -X') - self.expect('iptables -t') - self.expect(self.prompt) - self.sendline('sysctl net.ipv6.conf.all.disable_ipv6=0') - self.expect(self.prompt) - self.sendline('sysctl net.ipv4.ip_forward=1') - self.expect(self.prompt) - self.sendline('iptables -t nat -F; iptables -t nat -X') - self.expect(self.prompt) - self.sendline('iptables -F; iptables -X') - self.expect(self.prompt) - self.sendline('iptables -t nat -A PREROUTING -p tcp --dport 222 -j DNAT --to-destination %s:22' % gw) - self.expect(self.prompt) - self.sendline('iptables -t nat -A POSTROUTING -o eth1 -p tcp --dport 22 -j MASQUERADE') - self.expect(self.prompt) - self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_timestamps') - self.expect(self.prompt) - self.sendline('echo 0 > /proc/sys/net/ipv4/tcp_sack') - self.expect(self.prompt) - self.sendline('pkill --signal 9 -f dhclient.*eth1') - self.expect(self.prompt) - - def start_lan_client(self, gw="192.168.1.1"): - self.sendline('\nifconfig eth1 up') - self.expect('ifconfig eth1 up') - self.expect(self.prompt) - self.sendline("dhclient -r eth1") - self.expect(self.prompt) - self.sendline('\nifconfig eth1 0.0.0.0') - self.expect(self.prompt) - self.sendline('rm /var/lib/dhcp/dhclient.leases') - self.expect(self.prompt) - for attempt in range(3): - try: - self.sendline('dhclient -v eth1') - self.expect('DHCPOFFER', timeout=30) - self.expect(self.prompt) - break - except: - self.sendcontrol('c') - else: - raise Exception("Error: Device on LAN couldn't obtain address via DHCP.") - self.sendline('ifconfig eth1') - self.expect(self.prompt) - self.sendline('route del default') - self.expect(self.prompt) - self.sendline('route del default') - self.expect(self.prompt) - self.sendline('route del default') - self.expect(self.prompt) - self.sendline('route add default gw %s' % gw) - self.expect(self.prompt) - # Setup HTTP proxy, so board webserver is accessible via this device - self.sendline('apt-get -qy install tinyproxy curl apache2-utils nmap') - self.expect('Reading package') - self.expect(self.prompt, timeout=150) - self.sendline('curl --version') - self.expect(self.prompt) - self.sendline('ab -V') - self.expect(self.prompt) - self.sendline('nmap --version') - self.expect(self.prompt) - self.sendline("sed -i 's/^Port 8888/Port 8080/' /etc/tinyproxy.conf") - self.expect(self.prompt) - self.sendline("sed -i 's/^#Allow 10.0.0.0/Allow 10.0.0.0/' /etc/tinyproxy.conf") - self.expect(self.prompt) - self.sendline('/etc/init.d/tinyproxy restart') - self.expect('Restarting') - self.expect(self.prompt) - # Write a useful ssh config for routers - self.sendline('mkdir -p ~/.ssh') - self.sendline('cat > ~/.ssh/config << EOF') - self.sendline('Host %s' % gw) - self.sendline('StrictHostKeyChecking no') - self.sendline('UserKnownHostsFile=/dev/null') - self.sendline('') - self.sendline('Host krouter') - self.sendline('Hostname %s' % gw) - self.sendline('StrictHostKeyChecking no') - self.sendline('UserKnownHostsFile=/dev/null') - self.sendline('EOF') - self.expect(self.prompt) - # Copy an id to the router so people don't have to type a password to ssh or scp - self.sendline('nc %s 22 -w 1' % gw) - self.expect_exact('nc %s 22 -w 1' % gw) - if 0 == self.expect(['SSH'] + self.prompt, timeout=5): - self.sendline('[ -e /root/.ssh/id_rsa ] || ssh-keygen -N "" -f /root/.ssh/id_rsa') - self.expect(self.prompt) - self.sendline('scp ~/.ssh/id_rsa.pub %s:/etc/dropbear/authorized_keys' % gw) - self.expect_exact('scp ~/.ssh/id_rsa.pub %s:/etc/dropbear/authorized_keys' % gw) - try: - # When resetting, no need for password - self.expect("root@%s's password:" % gw, timeout=5) - self.sendline('password') - except: - pass - self.expect(self.prompt) - -if __name__ == '__main__': - # Example use - dev = DebianBox('10.0.0.173', - 'blue', - username="root", - password="bigfoot1", - port="22") - dev.sendline('echo Hello') - dev.expect('Hello', timeout=4) - dev.expect(dev.prompt) - dev.reset() - dev.sendline('echo Hello') - dev.expect('Hello', timeout=4) - dev.expect(dev.prompt) diff --git a/devices/local_cmd.py b/devices/local_cmd.py deleted file mode 100644 index 877be0fb..00000000 --- a/devices/local_cmd.py +++ /dev/null @@ -1,17 +0,0 @@ -import pexpect - -class LocalCmd(): - ''' - Set connection_type to local_cmd, ignores all output for now - ''' - def __init__(self, device=None, conn_cmd=None, **kwargs): - self.device = device - self.conn_cmd = conn_cmd - - def connect(self): - pexpect.spawn.__init__(self.device, - command='/bin/bash', - args=['-c', self.conn_cmd]) - - def close(): - self.device.sendcontrol('c') diff --git a/devices/power.py b/devices/power.py deleted file mode 100644 index 829ffd63..00000000 --- a/devices/power.py +++ /dev/null @@ -1,215 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -try: - from urllib.request import urlopen - from urllib.error import HTTPError -except: - from urllib2 import urlopen, HTTPError - -import pexpect -import dlipower -import time - -try: - from ouimeaux.environment import Environment as WemoEnv - from ouimeaux.device.switch import Switch as WemoSwitch -except: - WemoEnv = None - WemoSwitch = None - -def get_power_device(ip_address, username=None, password=None, outlet=None): - ''' - Try to determine the type of network-controlled power switch - at a given IP address. Return a class that can correctly - interact with that type of switch. - ''' - - if ip_address is None: - if outlet is not None and "wemo://" in outlet: - if WemoEnv is None: - print("Please install ouimeaux: pip install ouimeaux") - else: - return WemoPowerSwitch(outlet=outlet) - else: - return HumanButtonPusher() - - try: - data = urlopen("http://" + ip_address).read().decode() - except HTTPError as e: - data = e.read().decode() - except Exception as e: - print(e) - raise Exception("\nError connecting to %s" % ip_address) - if '<title>Power Controller' in data: - return DLIPowerSwitch(ip_address, outlet=outlet, username=username, password=password) - if 'Sentry Switched CDU' in data: - return SentrySwitchedCDU(ip_address, outlet=outlet) - if '<title>APC ' in data: - return APCPower(ip_address, outlet=outlet) - else: - raise Exception("No code written to handle power device found at %s" % ip_address) - - -class PowerDevice(): - ''' - At minimum, power devices let users reset an outlet over a network. - ''' - - def __init__(self, ip_address, username=None, password=None): - self.ip_address = ip_address - self.username = username - self.password = password - # Maybe verify connection is working here - - def reset(self, outlet): - '''Turn an outlet OFF, maybe wait, then back ON.''' - raise Exception('Code not written to reset with this type of power device at %s' % self.ip_address) - - -class SentrySwitchedCDU(PowerDevice): - ''' - Power Unit from Server Technology. - ''' - def __init__(self, - ip_address, - outlet, - username='admn', - password='bigfoot1'): - PowerDevice.__init__(self, ip_address, username, password) - self.outlet = outlet - # Verify connection - try: - pcon = self.__connect() - pcon.sendline('status .a%s' % self.outlet) - i = pcon.expect(['Command successful', 'User/outlet -- name not found']) - if i == 1: - raise Exception('\nOutlet %s not found' % self.outlet) - pcon.close() - except Exception as e: - print(e) - print("\nError with power device %s" % ip_address) - raise Exception("Error with power device %s" % ip_address) - - def __connect(self): - pcon = pexpect.spawn('telnet %s' % self.ip_address) - pcon.expect('Sentry Switched CDU Version 7', timeout=15) - pcon.expect('Username:') - pcon.sendline(self.username) - pcon.expect('Password:') - pcon.sendline(self.password) - i = pcon.expect(['Switched CDU:', 'Critical Alert']) - if i == 0: - return pcon - else: - print("\nCritical failure in %s, skipping PDU\n" % self.power_ip) - raise Exception("critical failure in %s" % self.power_ip) - - def reset(self, retry_attempts=2): - print("\n\nResetting board %s %s" % (self.ip_address, self.outlet)) - for attempt in range(retry_attempts): - try: - pcon = self.__connect() - pcon.sendline('reboot .a%s' % self.outlet) - pcon.expect('Command successful') - pcon.close() - return - except Exception as e: - print(e) - continue - raise Exception("\nProblem resetting outlet %s." % self.outlet) - -class HumanButtonPusher(PowerDevice): - ''' - Tell a person to physically reboot the router. - ''' - def __init__(self): - PowerDevice.__init__(self, None) - def reset(self): - print("\n\nUser power-cycle the device now!\n") - -class APCPower(PowerDevice): - '''Resets an APC style power control port''' - def __init__(self, - ip_address, - outlet, - username='apc', - password='apc'): - PowerDevice.__init__(self, ip_address, username, password) - self.outlet = outlet - def reset(self): - pcon = pexpect.spawn('telnet %s' % self.ip_address) - pcon.expect("User Name :") - pcon.send(self.username + "\r\n") - pcon.expect("Password :") - pcon.send(self.password + "\r\n") - pcon.expect("> ") - pcon.send("1" + "\r\n") - pcon.expect("> ") - pcon.send("2" + "\r\n") - pcon.expect("> ") - pcon.send("1" + "\r\n") - pcon.expect("> ") - pcon.send(self.outlet + "\r\n") - pcon.expect("> ") - pcon.send("1" + "\r\n") - pcon.expect("> ") - pcon.send("6" + "\r\n") - pcon.send("YES") - pcon.send("" + "\r\n") - pcon.expect("> ") - -class DLIPowerSwitch(PowerDevice): - '''Resets a DLI based power switch''' - def __init__(self, - ip_address, - outlet, - username, - password): - PowerDevice.__init__(self, ip_address, username, password) - self.switch = dlipower.PowerSwitch(hostname=ip_address, userid=username, password=password) - self.outlet = outlet - - def reset(self, outlet=None): - if outlet is None: - outlet = self.outlet - self.switch.cycle(outlet) - -class WemoPowerSwitch(PowerDevice): - ''' - Controls a wemo switch given an ipaddress. Run the following command to list devices: - - $ python ./devices/power.py - ''' - def __init__(self, outlet): - addr = 'http://' + outlet.replace("wemo://", "") + ":49153/setup.xml" - self.switch = WemoSwitch(addr) - def reset(self): - self.switch.off() - time.sleep(5) - self.switch.on() - -if __name__ == "__main__": - print("Gathering info about power outlets...") - - if WemoEnv is not None: - env = WemoEnv() - env.start() - scan_time = 10 - print("Scanning for WeMo switches for %s seconds..." % scan_time) - env.discover(scan_time) - if len(env.list_switches()) > 0: - print("Found the following switches:"); - for switch_name in env.list_switches(): - switch = env.get_switch(switch_name) - print("%s ip address is %s" % (switch_name, switch.host)) - print("The switches above can be added by ip address" - " for example use the") - print("following to use %s" % switch_name) - print("\twemo://%s" % switch.host) - else: - print("No WeMo switches found") diff --git a/library.py b/library.py deleted file mode 100644 index 6e0dab6b..00000000 --- a/library.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import os - -from termcolor import cprint - -def print_bold(msg): - cprint(msg, None, attrs=['bold']) - -def print_board_info(x): - for key in sorted(x): - print_bold(" %s: %s" % (key, x[key])) - -def process_test_results(raw_test_results): - full_results = {'test_results': [], - 'tests_pass': 0, - 'tests_fail': 0, - 'tests_skip': 0, - 'tests_total': 0, - } - for i, x in enumerate(raw_test_results): - message = None - name = x.__class__.__name__ - grade = None - try: - grade = x.result_grade - except: - pass - if grade == "OK" or grade == "Unexp OK": - full_results['tests_pass'] += 1 - elif grade == "FAIL" or grade == "Exp FAIL": - full_results['tests_fail'] += 1 - elif grade == "SKIP" or grade is None: - full_results['tests_skip'] += 1 - try: - # Use only first line of docstring result message - message = x.__doc__.split('\n')[0] - except: - print_bold("WARN: Please add docstring to %s." % x) - try: - message = x.result_message - except: - pass - if hasattr(x, 'long_result_message'): - long_message = x.long_result_message - else: - long_message = "" - full_results['test_results'].append({"name": name, "message": message, "long_message": long_message, "grade": grade}) - full_results['tests_total'] = len(raw_test_results) - return full_results - -def send_results_to_myqsl(testsuite, output_dir): - ''' - Send url of results to a MySQL database. Only do this if we are on - a build server (use the build environment variables). - ''' - dir = output_dir.replace(os.getcwd(), '').strip(os.sep) - build_id = os.environ.get('image_build_id', '') - build_url = os.environ.get('BUILD_URL', '') - if '' not in (build_id, testsuite, build_url): - from devices import mysql - build_url = build_url.replace("https://", "") + "artifact/openwrt/%s/results.html" % dir - title = 'Board Farm Results (suite: %s)' % testsuite - reporter = mysql.MySqlReporter() - reporter.insert_data(build_id, build_url, title) diff --git a/requirements.txt b/requirements.txt index 40a66ace..756563ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,25 @@ argparse -elasticsearch>=1.0.0 -unittest2 -pexpect==3.1 -junitxml beautifulsoup4 -termcolor -selenium -future +boto3 +cdrouter +countrycode dlipower +easysnmp +elasticsearch>=1.0.0 +Faker +future +ipaddress +jira +matplotlib +netaddr +pexpect +pymongo +pyserial +pyvirtualdisplay +requests +selenium +simplejson +termcolor +unittest2 +zeep +pysmi diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..1eac7b3a --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages + +setup(name='boardfarm', + version='1.0.0', + description='Automated testing of network devices', + author='Various', + url='https://github.com/lgirdk/boardfarm', + packages=find_packages(), + package_data={'': ['*.txt','*.json','*.cfg','*.md','*.tcl']}, + include_package_data=True, + entry_points = { + 'console_scripts': ['bft=boardfarm.bft:main'], + } + ) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index b0589b21..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. -import lib - -# Import from every file -import os -import glob -test_files = glob.glob(os.path.dirname(__file__)+"/*.py") -for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): - try: - exec("from %s import *" % x) - except Exception as e: - print(e) - print("Warning: could not import from file %s." % x) diff --git a/tests/connection_stress.py b/tests/connection_stress.py deleted file mode 100644 index 08329eed..00000000 --- a/tests/connection_stress.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import rootfs_boot -import time -from devices import board, wan, lan, wlan, prompt - -class Connection_Stress(rootfs_boot.RootFSBootTest): - '''Measured CPU use while creating thousands of connections.''' - def runTest(self): - num_conn = 5000 - # Wan device: Create small file in web dir - fname = 'small.txt' - cmd = '\nhead -c 10000 /dev/urandom > /var/www/%s' % fname - wan.sendline(cmd) - wan.expect(prompt) - # Lan Device: download small file a lot - concurrency = 25 - url = 'http://192.168.0.1/%s' % fname - # Start CPU monitor - board.sendline('\nmpstat -P ALL 10000 1') - # Lan Device: download small file a lot - lan.sendline('\nab -dn %s -c %s %s' % (num_conn, concurrency, url)) - lan.expect('Benchmarking', timeout=5) - lan.expect('Requests per second:\s+(\d+)') - reqs_per_sec = int(lan.match.group(1)) - lan.expect(prompt) - # Stop CPU monitor - board.sendcontrol('c') - board.expect('Average:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') - idle_cpu = float(board.match.group(1)) - avg_cpu = 100 - float(idle_cpu) - board.expect(prompt) - msg = "ApacheBench measured %s connections/second, CPU use = %s%%." % (reqs_per_sec, avg_cpu) - self.result_message = msg - time.sleep(5) # Give router a few seconds to recover - def recover(self): - board.sendcontrol('c') - board.expect(prompt) - lan.sendcontrol('c') - time.sleep(2) # Give router a few seconds to recover diff --git a/tests/iperf3_test.py b/tests/iperf3_test.py deleted file mode 100644 index b7b2f9b7..00000000 --- a/tests/iperf3_test.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import re -import rootfs_boot -from lib import installers -from devices import board, wan, lan, wlan, prompt - -class iPerf3Test(rootfs_boot.RootFSBootTest): - '''iPerf3 generic performance tests''' - def runTest(self): - installers.install_iperf3(wan) - installers.install_iperf3(lan) - - wan.sendline('iperf3 -s') - wan.expect('-----------------------------------------------------------') - wan.expect('-----------------------------------------------------------') - - time = 60 - - lan.sendline('iperf3 -c 192.168.0.1 -P5 -t %s -i 0' % time) - lan.expect(prompt, timeout=time+5) - - sender = re.findall('SUM.*Bytes\s*(.*/sec).*sender', lan.before)[-1] - if 'Mbits' in sender: - s_rate = float(sender.split()[0]) - elif 'Kbits' in sender: - s_rate = float(sender.split()[0]/1024) - else: - raise Exception("Unknown rate in sender results") - - recv = re.findall('SUM.*Bytes\s*(.*/sec).*receiver', lan.before)[-1] - if 'Mbits' in recv: - r_rate = float(recv.split()[0]) - elif 'Kbits' in recv: - r_rate = float(recv.split()[0]/1024) - else: - raise Exception("Unknown rate in recv results") - - self.result_message = "Sender rate = %si MBits/sec, Receiver rate = %s Mbits/sec\n", (s_rate, r_rate) - self.logged['s_rate'] = s_rate - self.logged['r_rate'] = r_rate - - self.recovery() - - def recovery(self): - for d in [wan, lan]: - d.sendcontrol('c') - d.sendcontrol('c') - d.expect(prompt) diff --git a/tests/lib/common.py b/tests/lib/common.py deleted file mode 100644 index 4b636d4f..00000000 --- a/tests/lib/common.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 - -import json -import junitxml -import pexpect -import sys -import subprocess -import time -import unittest2 -import urllib2 -import os -import signal -from termcolor import cprint - -from selenium import webdriver -from selenium.webdriver.common.proxy import * - -ubootprompt = ['ath>', '\(IPQ\) #', 'ar7240>'] -linuxprompt = ['root\\@.*:.*#', '@R7500:/# '] -prompts = ubootprompt + linuxprompt + ['/.* # ', ] - -def run_once(f): - def wrapper(*args, **kwargs): - if not wrapper.has_run: - wrapper.has_run = True - return f(*args, **kwargs) - wrapper.has_run = False - return wrapper - -def spawn_ssh_pexpect(ip, user='root', pw='bigfoot1', prompt=None, port="22", via=None, color=None, o=sys.stdout): - if via: - p = via.sendline("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ - % (user, ip, port)) - p = via - else: - p = pexpect.spawn("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ - % (user, ip, port)) - - i = p.expect(["yes/no", "assword:", "Last login"], timeout=30) - if i == 0: - p.sendline("yes") - i = self.expect(["Last login", "assword:"]) - if i == 1: - p.sendline(pw) - else: - pass - - if prompt is None: - p.prompt = "%s@.*$" % user - else: - p.prompt = prompt - - p.expect(p.prompt) - - from termcolor import colored - class o_helper(): - def __init__(self, color): - self.color = color - def write(self, string): - o.write(colored(string, color)) - def flush(self): - o.flush() - - if color is not None: - p.logfile_read = o_helper(color) - else: - p.logfile_read = o - - return p - -def clear_buffer(console): - try: - console.read_nonblocking(size=2000, timeout=1) - except: - pass - -def phantom_webproxy_driver(ipport): - ''' - Use this if you started web proxy on a machine connected to router's LAN. - ''' - service_args = [ - '--proxy=' + ipport, - '--proxy-type=http', - ] - print("Attempting to setup Phantom.js via proxy %s" % ipport) - driver = webdriver.PhantomJS(service_args=service_args) - driver.set_window_size(1024, 768) - driver.set_page_load_timeout(30) - return driver - -def firefox_webproxy_driver(ipport): - ''' - Use this if you started web proxy on a machine connected to router's LAN. - ''' - proxy = Proxy({ - 'proxyType': 'MANUAL', - 'httpProxy': ipport, - 'ftpProxy': ipport, - 'sslProxy': ipport, - 'noProxy': '' - }) - print("Attempting to open firefox via proxy %s" % ipport) - profile = webdriver.FirefoxProfile() - profile.set_preference('network.http.phishy-userpass-length', 255) - driver = webdriver.Firefox(proxy=proxy, firefox_profile=profile) - caps = webdriver.DesiredCapabilities.FIREFOX - proxy.add_to_capabilities(caps) - #driver = webdriver.Remote(desired_capabilities=caps) - driver.implicitly_wait(30) - driver.set_page_load_timeout(30) - return driver - -def test_msg(msg): - cprint(msg, None, attrs=['bold']) diff --git a/tests/lib/installers.py b/tests/lib/installers.py deleted file mode 100644 index 34d3fc37..00000000 --- a/tests/lib/installers.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -def apt_install(device, name, timeout=120): - device.sendline('apt-get install -q -y %s' % name) - device.expect('Reading package') - device.expect(device.prompt, timeout=timeout) - -def apt_update(device, timeout=120): - device.sendline('apt-get update') - device.expect('Reading package') - device.expect(device.prompt, timeout=timeout) - -def install_iperf(device): - '''Install iPerf benchmark tool if not present.''' - device.sendline('\niperf -v') - try: - device.expect('iperf version', timeout=10) - device.expect(device.prompt) - except: - device.expect(device.prompt) - device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf') - device.expect(device.prompt, timeout=60) - -def install_iperf3(device): - '''Install iPerf benchmark tool if not present.''' - device.sendline('\niperf3 -v') - try: - device.expect('iperf 3', timeout=5) - device.expect(device.prompt) - except: - device.expect(device.prompt) - device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf3') - device.expect(device.prompt, timeout=60) - -def install_lighttpd(device): - '''Install lighttpd web server if not present.''' - device.sendline('\nlighttpd -v') - try: - device.expect('lighttpd/1', timeout=8) - device.expect(device.prompt) - except: - device.expect(device.prompt) - apt_install(device, 'lighttpd') - -def install_netperf(device): - '''Install netperf benchmark tool if not present.''' - device.sendline('\nnetperf -V') - try: - device.expect('Netperf version 2.4', timeout=10) - device.expect(device.prompt) - except: - device.expect(device.prompt) - device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install netperf') - device.expect(device.prompt, timeout=60) - device.sendline('/etc/init.d/netperf restart') - device.expect('Restarting') - device.expect(device.prompt) - -def install_endpoint(device): - '''Install endpoint if not present.''' - device.sendline('\npgrep endpoint') - try: - device.expect('pgrep endpoint') - device.expect('[0-9]+\r\n', timeout=5) - device.expect(device.prompt) - except: - device.expect(device.prompt) - device.sendline('wget http://downloads.ixiacom.com/products/ixchariot/endpoint_library/8.00/pelinux_amd64_80.tar.gz') - device.expect(device.prompt, timeout=120) - device.sendline('tar xvzf pelinux_amd64_80.tar.gz') - device.expect('endpoint.install', timeout=90) - device.expect(device.prompt, timeout=60) - device.sendline('./endpoint.install accept_license') - device.expect('Installation of endpoint was successful.', timeout=90) - device.expect(device.prompt, timeout=60) - -def install_hping3(device): - '''Install hping3 if not present.''' - device.sendline('\nhping3 --version') - try: - device.expect('hping3 version', timeout=5) - device.expect(device.prompt) - except: - device.expect(device.prompt) - apt_install(device, 'hping3') diff --git a/tests/lib/streamboost.py b/tests/lib/streamboost.py deleted file mode 100644 index 0cbc55c0..00000000 --- a/tests/lib/streamboost.py +++ /dev/null @@ -1,317 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import re -import time - -from devices import prompt -from common import clear_buffer - -def stop(console): - console.sendline('\nstreamboost stop') - console.expect(['StreamBoost: Executing stop all','streamboost: not found']) - console.expect(prompt) - -def start(console): - console.sendline('\nstreamboost start') - console.expect('StreamBoost: Executing start all') - console.expect(prompt) - -def verify_enabled(console): - '''Check if streamboost is enabled, throw exception if it is not.''' - console.sendline('\nuci show appflow.tccontroller.enable_streamboost') - console.expect('enable_streamboost=1', timeout=6) - console.expect(prompt) - -def verify_disabled(console): - '''Check if StreamBoost is disabled, throw exception if it is not.''' - console.sendline('\nuci show appflow.tccontroller.enable_streamboost') - console.expect('enable_streamboost=0', timeout=6) - console.expect(prompt) - -def is_enabled(console): - '''Return True if StreamBoost is enabled.''' - console.sendline('\nuci show appflow.tccontroller.enable_streamboost') - sb_enabled = console.expect(['enable_streamboost=0\r', 'enable_streamboost=1\r'], timeout=6) - console.expect(prompt) - if sb_enabled == 0: - return False - if sb_enabled == 1: - return True - -def disable(console): - '''Disable StreamBoost.''' - console.sendline('\nuci set appflow.tccontroller.enable_streamboost=0') - console.expect('streamboost=0', timeout=6) - console.expect(prompt) - console.sendline('uci commit appflow; luci-reload appflow') - console.expect('Reloading appflow...') - console.expect(prompt) - verify_disabled(console) - -def enable(console): - '''Enable StreamBoost.''' - console.sendline('\nuci set appflow.tccontroller.enable_streamboost=1') - console.expect('streamboost=1', timeout=6) - console.expect(prompt) - console.sendline('uci commit appflow; luci-reload appflow') - console.expect('Reloading appflow...') - console.expect(prompt) - # SB takes a few seconds to fully start - time.sleep(4) - verify_enabled(console) - -def enable_if_not(console): - '''Enable StreamBoost if it is not already enabled''' - if is_enabled(console): - return "StreamBoost is already enabled." - else: - enable(console) - return "StreamBoost now set to enabled." - -def disable_if_not(console): - '''Disable StreamBoost if it is not already enabled''' - if not is_enabled(console): - return "StreamBoost is already disabled." - else: - disable(console) - return "StreamBoost now set to disabled." - -def disable_http_auth(console): - '''Turn off HTTP Basic Auth on Ozker.''' - try: - # Display current setting, if found - console.sendline('\ngrep OZKER /etc/appflow/streamboost.sys.conf') - console.expect('OZKER_BASIC_AUTH', timeout=4) - console.expect(prompt) - except: - pass - # Remove current setting, if present - console.sendline("sed -i '/OZKER_BASIC_AUTH/d' /etc/appflow/streamboost.sys.conf") - console.expect(prompt) - console.sendline("sed -i '/OZKER_BASIC_AUTH/d' /var/run/appflow/streamboost.user.conf") - console.expect(prompt) - # Excplicitly disable - console.sendline('echo "OZKER_BASIC_AUTH=no">>/etc/appflow/streamboost.sys.conf') - console.expect(prompt) - console.sendline('echo "OZKER_BASIC_AUTH=no">>/var/run/appflow/streamboost.user.conf') - console.expect(prompt) - -def disable_monit(console): - '''Monit will restart Daemons that go down. - Disable monit to prevent it from starting daemons.''' - console.sendline('echo "SB_DISABLE_MONIT=yes">>/etc/appflow/streamboost.sys.conf') - console.expect(prompt) - -def enable_monit(console): - '''Monit is enabled by default, but if it sees a - certain variable, it will not restart daemons.''' - console.sendline("sed -i '/SB_DISABLE_MONIT/d' /etc/appflow/streamboost.sys.conf") - console.expect(prompt) - -def get_status(console, logread_if_manydown=False, monit=False, now=False): - ''' - Parse 'streamboost status' to - return a dictionary like: - {"redis-server" : "UP", - "policy-reader": "UP, - ...} - ''' - sbdaemon_status = {} - monit_status = {} - num_down = 0 - num_tries = 8 - for i in range(num_tries): - clear_buffer(console) - cmd='streamboost status' - if monit: - cmd='streamboost status_monit' - console.sendline("\n" + cmd) - try: - console.expect(cmd) - console.expect(prompt, timeout=60) - output = console.before - if monit: - m = re.search('status=(\d+)',output) - monit_status = {'code': int(m.group(1))} - result = re.findall('\[\s+(\w+)\s+\]\s([-\w]+)\s', output) - sbdaemon_status = dict([(x[1].lower(), x[0]) for x in result]) - num_down = len([x for x in sbdaemon_status if sbdaemon_status[x] == 'DOWN']) - if not now and ("does not exist" in output or "try again later" in output or num_down > 1): - print("\nStreamBoost not ready? Trying again, sleeping 15s...") - time.sleep(15) - else: - break - except: - console.sendcontrol('c') - if num_down > 1 and logread_if_manydown: - print("\nToo many daemons down. Dumping syslog...") - print("===== Begin Logread =====") - console.sendline('\nlogread') - console.expect('logread') - console.expect('OpenWrt') - console.expect(prompt) - print("\n===== End Logread =====") - if monit: - sbdaemon_status.update(monit_status) - return sbdaemon_status - -def verify_running(console, monit=False): - ''' - Fail if any streamboost daemon is DOWN - besides the bandwidth daemons. - ''' - ignore_list = ('aperture', 'bandwidth', 'bwestd') - status = get_status(console, monit=monit) - num_up = len([x for x in status if status[x] == 'UP']) - num_down = len([x for x in status if status[x] == 'DOWN' and x not in ignore_list]) - assert num_down == 0 and num_up > 7 - -def set_bw_limits(console, up_limit, down_limit): - ''' - Set bandwidth limits in uci, restart Streamboost, then verify new settings. - up_limit and down_limit must have units of Bytes. - ''' - # Check limits - console.sendline('\nuci show appflow.tccontroller | grep limit=') - console.expect('uplimit') - console.expect(prompt) - # Set new limits - console.sendline('uci set appflow.tccontroller.uplimit=%s' % up_limit) - console.expect(prompt) - console.sendline('uci set appflow.tccontroller.downlimit=%s' % down_limit) - console.expect(prompt) - console.sendline('uci commit appflow') - console.expect(prompt) - for i in range(2): - try: - console.sendline('\nluci-reload appflow') - console.expect('Reloading appflow') - console.expect(prompt) - break - except: - continue - time.sleep(2) # give streamboost chance to fully boot - # Check limits - console.sendline('redis-cli get settings:bw:up') - console.expect('"\d+"') - console.expect(prompt) - console.sendline('redis-cli get settings:bw:down') - console.expect('"\d+"') - console.expect(prompt) - console.sendline('uci show appflow | grep limit=') - console.expect('tccontroller') - console.expect(prompt) - uplimit = int(re.search('uplimit=(\d+)\r', console.before).group(1)) - downlimit = int(re.search('downlimit=(\d+)\r', console.before).group(1)) - print("\nStreamboost bandwidth limits now at %.1f Mbps upload, %.1f Mbps download." % (uplimit/131072., downlimit/131072.)) - if (uplimit != up_limit) or (downlimit != down_limit): - print("Warning: Settings now in uci do not agree with intended settings.") - -def print_redis_stats_size(console): - '''Print size of a few important things in redis database.''' - console.sendline('\nredis-cli info memory') - console.expect('Memory') - console.expect(prompt) - console.sendline('redis-cli llen eventdb:events') - console.expect('integer') - console.expect(prompt) - console.sendline('redis-cli lrange eventdb:events 0 -1 | wc -c') - console.expect('\d+') - console.expect(prompt) - console.sendline('redis-cli llen eventdb:features') - console.expect('integer') - console.expect(prompt) - console.sendline('redis-cli lrange eventdb:features 0 -1 | wc -c') - console.expect('\d+') - console.expect(prompt) - -def run_aperture(console): - '''Run bandwidth measurementt and return results in Mbps.''' - console.sendline('\nstreamboost measure') - console.expect('streamboost measure') - try: - console.expect(prompt, timeout=180) - except Exception as e: - print("\nAperture failed to finish after 3 minutes.") - print("Sending CTRL-C.") - console.sendcontrol('c') - console.expect(prompt) - return None, None - try: - up_result = re.search(r'uplimit=([0-9]+)\r\n', console.before).group(1) - down_result = re.search(r'downlimit=([0-9]+)\r\n', console.before).group(1) - up_result_mbps = int(up_result)*8.0/(1000.*1000.) - down_result_mbps = int(down_result)*8.0/(1000.*1000.) - return up_result_mbps, down_result_mbps - except Exception as e: - return None, None - -def check_detection_files_version(console): - '''Find the version of the Flow detection file yaml.''' - console.sendline("") - if console.model in ('dlink-dgl5500', 'zyxel-nbg6716'): - console.sendline("drflocs -D -k /etc/ssl/private/client_key.pem -w /tmp/run/appflow/wopr.yaml.enc | grep timestamp") - console.expect('timestamp:') - else: - console.sendline("opkg list | grep '[wopr|p0f]-db\|policy-redis'") - console.expect('wopr-db -') - console.expect(prompt) - console.sendline("grep timestamp /etc/appflow/wopr.yaml") - console.expect(prompt) - -def get_wopr_version(console): - '''Return version number of Application detection config file.''' - console.sendline("\ngrep timestamp /etc/appflow/wopr.yaml") - console.expect("timestamp: '([_\d]+)'") - version = console.match.group(1) - console.expect(prompt) - return version - -def get_detected_flows(console, duration=10, sleep=2): - ''' - Return dictionary with keys are detected flow names, and values are downloaded bytes. - Poll every 'delay' seconds for a duration of seconds. - ''' - # Fist create dict where key=name, value=list of down_bytes - final_result = {} - for i in range(duration/sleep): - console.sendline('\ncurl http://127.0.0.1/cgi-bin/ozker/api/flows') - console.expect('"flows":') - console.expect(prompt) - result = re.findall('"down_bytes":(\d+),"up_bytes":\d+,"name":"([_a-z0-9]+)"', console.before) - detected_flows = {} - if result: - detected_flows = dict([(x[1],int(x[0])) for x in result]) - for k in detected_flows: - if k not in final_result: - final_result[k] = [] - final_result[k].append(detected_flows[k]) - time.sleep(sleep) - # Modify to dict so that key=flowname, value=total bytes downloaded - # This formula is fancy. Example: [0, 2, 4, 0, 1, 6] = 10. - # It has to be, because flows can stop and "start over" at zero. - for n in final_result: - nums = final_result[n] - final_result[n] = sum([nums[i+1]-nums[i] for i in range(len(nums)-1) if nums[i+1]>nums[i]]) - return final_result - - -def get_pid(console, name): - try: - cmd="top -b -n 1 | grep " + name + " | grep -v grep | awk '{print $1}'" - console.sendline(cmd) - console.expect('(\d+)\r\n', timeout=5) - pid = int(console.match.group(1)) - console.expect(prompt) - return pid - except: - return -1 - -def kill_pid(console, pid): - cmd="kill " + str(pid) - console.sendline(cmd) - console.expect(prompt) diff --git a/tests/wifi_cycle.py b/tests/wifi_cycle.py deleted file mode 100644 index 125691f2..00000000 --- a/tests/wifi_cycle.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import string -import time - -import rootfs_boot -from devices import board, wan, lan, wlan, prompt -from lib.wifi import * - -def wifi_cycle(board, num_times=5, wlan_iface="ath0"): - '''Enable and disable wifi some number of times.''' - if wifi_on(board): - disable_wifi(board, wlan_iface) - wifi_name = randomSSIDName() - board.sendline('uci set wireless.@wifi-iface[0].ssid=%s' % wifi_name) - board.expect(prompt) - board.sendline('uci set wireless.@wifi-iface[0].encryption=psk2') - board.expect(prompt) - board.sendline('uci set wireless.@wifi-iface[0].key=%s' % randomSSIDName()) - board.expect(prompt) - board.sendline('echo "7 7 7 7" > /proc/sys/kernel/printk') - board.expect(prompt) - for i in range(1, num_times+1): - enable_wifi(board) - wait_wifi_up(board, wlan_iface=wlan_iface) - disable_wifi(board, wlan_iface=wlan_iface) - print("\n\nEnabled and disabled WiFi %s times." % i) - board.sendline('echo "1 1 1 7" > /proc/sys/kernel/printk') - board.expect(prompt) - -class WiFiOnOffCycle(rootfs_boot.RootFSBootTest): - '''Enabled and disabled wifi once.''' - def runTest(self): - wlan_iface = wifi_interface(board) - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - - wifi_cycle(board, num_times=1, wlan_iface=wlan_iface) - -class WiFiOnOffCycle5(rootfs_boot.RootFSBootTest): - '''Enabled and disabled wifi 5 times.''' - def runTest(self): - wlan_iface = wifi_interface(board) - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - - wifi_cycle(board, num_times=5, wlan_iface=wlan_iface) - -class WiFiOnOffCycle20(rootfs_boot.RootFSBootTest): - '''Enabled and disabled wifi 20 times.''' - def runTest(self): - wlan_iface = wifi_interface(board) - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - - wifi_cycle(board, num_times=20, wlan_iface=wlan_iface) - # Leave with wifi enabled - enable_wifi(board) - wait_wifi_up(board, wlan_iface=wlan_iface) diff --git a/tests/wifi_memuse.py b/tests/wifi_memuse.py deleted file mode 100644 index 71767fd2..00000000 --- a/tests/wifi_memuse.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import lib -import rootfs_boot -import time -import re - -import lib.wifi -from devices import board, wan, lan, wlan, prompt - -class WiFiMemUse(rootfs_boot.RootFSBootTest): - '''Measured WiFi memory use when enabled.''' - def recover(self): - board.sendcontrol('c') - def runTest(self): - # Disable WiFi - board.sendline('\nwifi detect > /etc/config/wireless') - board.expect('wifi detect') - board.expect(prompt) - board.sendline('uci commit wireless; wifi') - board.expect(prompt) - # One of these commands should be available - board.sendline('iwconfig || iwinfo') - board.expect(prompt) - memfree_wifi_off = board.get_memfree() - # Enable WiFi - lib.wifi.enable_all_wifi_interfaces(board) - time.sleep(90) # give time to start and settle - board.sendline('iwconfig || iwinfo') - board.expect(['ESSID', 'IEEE']) - board.expect(prompt) - memfree_wifi_on = board.get_memfree() - mem_used = (int(memfree_wifi_off)-int(memfree_wifi_on)) / 1000 - self.result_message = 'Enabling all WiFi interfaces uses %s MB.' % (mem_used) - self.logged['mem_used'] = mem_used - -class TurnOnWifi(rootfs_boot.RootFSBootTest): - '''Turn on all WiFi interfaces.''' - def runTest(self): - wlan_iface = lib.wifi.wifi_interface(board) - lib.wifi.enable_wifi(board) - lib.wifi.wait_wifi_up(board, wlan_iface=wlan_iface) - board.sendline('\nifconfig') - board.expect('HWaddr') - board.expect(prompt) diff --git a/tests/wifi_vap_test.py b/tests/wifi_vap_test.py deleted file mode 100644 index d5663db8..00000000 --- a/tests/wifi_vap_test.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import wlan_set_ssid -from lib.wifi import * -from devices import board, wan, lan, wlan, prompt -import time - -class WlanVAP(wlan_set_ssid.WlanSetSSID): - '''Test multiple VAPs up and down''' - def runTest(self): - enable_wifi(board, index=0) - # TODO: make sure we have a radio - enable_wifi(board, index=1) - - for i in range(1, 16): - wifi_add_vap(board, "wifi0", randomSSIDName()) - - for i in range(1, 16): - wifi_add_vap(board, "wifi1", randomSSIDName()) - - for i in range(0, 20): - board.sendline('wifi down') - board.expect('wifi down') - board.expect(prompt, timeout=480) - board.sendline('wifi up') - board.expect('wifi up') - board.expect(prompt, timeout=480) - - # expect 32 vaps to be present - for i in range(0, 5): - try: - time.sleep(10) - board.sendline('ifconfig -a | grep ath | wc -l') - board.expect('32') - board.expect(prompt) - except: - if i == 4: - assert False - else: - break - - for i in range(1, 16): - wifi_del_vap(board, -1) - - for i in range(1, 16): - wifi_del_vap(board, -1) diff --git a/tests/wlan_associate.py b/tests/wlan_associate.py deleted file mode 100644 index c23622fa..00000000 --- a/tests/wlan_associate.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import unittest2 -import rootfs_boot -import lib -import sys -import pexpect -import time -import wlan_set_ssid -import re -from devices import board, wan, lan, wlan, prompt -from lib.wifi import * - -class WlanAssociate(wlan_set_ssid.WlanSetSSID): - '''Wifi device connected and had internet access.''' - def wlan_setup(self): - wlan.sendline('\napt-get install -qy usbutils wireless-tools') - wlan.expect('Reading package') - wlan.expect(prompt) - wlan.sendline('killall wpa_supplicant') - wlan.expect(prompt) - - def recover(self): - wlan.sendcontrol('c') - wlan.sendcontrol('c') - - @lib.common.run_once - def runTest(self): - super(WlanAssociate, self).runTest() - wlan_iface = wifi_interface(board) - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - if wlan is None: - self.skipTest("No wlan VM, skipping test..") - - #Determine if we are using a beeliner x86 host. If not, default to usb drivers. - #Would like to push this out to a library. - wlan.sendline('lspci |grep -q Atheros; echo $?') - wlan.expect('(\d+)\r\n') - check_atheros = int(wlan.match.group(1)) - - if check_atheros is not 0: - print("Creating realtek interface.") - wlan.sendline('\napt-get install -qy firmware-realtek') - wlan.expect('Reading package') - wlan.expect(prompt) - wlan.sendline('rmmod rtl8192cu 8812au') - wlan.expect(prompt) - wlan.sendline('modprobe rtl8192cu') - wlan.expect(prompt) - wlan.sendline('modprobe 8812au') - wlan.expect(prompt) - else: - #Check if modules are alerady loaded. If not, load them. - print("Found Atheros hardware, creating interface.") - wlan.sendline('lsmod |grep -q ath_hal; echo $?') - wlan.expect('(\d+)\r\n') - check_loaded = int(wlan.match.group(1)) - - if check_loaded is not 0: - #rc.wlan takes care of insmod, wlanconfig creates wlan0. Both should be in the path. - wlan.sendline('rc.wlan up') - wlan.expect(prompt) - wlan.sendline('wlanconfig wlan0 create wlandev wifi0 wlanmode sta') - wlan.expect(prompt) - - wlan.sendline('rfkill unblock all') - wlan.expect(prompt) - - wlan.sendline('ifconfig wlan0') - wlan.expect('HWaddr') - wlan.expect(prompt) - - wlan.sendline('ifconfig wlan0 down') - wlan.expect(prompt) - - wlan.sendline('ifconfig wlan0 up') - wlan.expect(prompt) - - # wait until the wifi can see the SSID before even trying to join - # not sure how long we should really give this, or who's fault it is - for i in range(0, 20): - try: - wlan.sendline('iwlist wlan0 scan | grep ESSID:') - wlan.expect(self.config.ssid) - wlan.expect(prompt) - except: - lib.common.test_msg("can't see ssid %s, scanning again (%s tries)" % (self.config.ssid, i)) - else: - break - - time.sleep(10) - - for i in range(0, 2): - try: - wlan.sendline('iwconfig wlan0 essid %s' % self.config.ssid) - wlan.expect(prompt) - - # give it some time to associate - time.sleep(20) - - # make sure we assocaited - wlan.sendline('iwconfig wlan0') - wlan.expect('Access Point: ([0-9A-F]{2}[:-]){5}([0-9A-F]{2})') - wlan.expect(prompt) - except: - lib.common.test_msg("Can't associate with ssid %s, trying again (%s tries) " % (self.config.ssid, i)) - else: - break - - # get ip on wlan - wlan.sendline('killall dhclient') - wlan.expect(prompt) - time.sleep(10) - wlan.sendline('dhclient wlan0') - wlan.expect(prompt) - - # for reference - wlan.sendline('ifconfig wlan0') - wlan.expect(prompt) - - # make sure dhcp worked, and for reference of IP - wlan_ip = wlan.get_interface_ipaddr("wlan0") - - # add route to wan - wlan.sendline('ip route add 192.168.0.0/24 via 192.168.1.1') - wlan.expect(prompt) - wlan.sendline('ip route show') - wlan.expect(prompt) - - wlan.sendline('ping 192.168.1.1 -c3') - wlan.expect('3 packets transmitted') - wlan.expect(prompt) - wlan.sendline('curl 192.168.1.1 --connect-timeout 5 > /dev/null 2>&1; echo $?') - wlan.expect('(\d+)\r\n') - curl_success = int(wlan.match.group(1)) - - msg = "Attempt to curl router returns %s\n" % (curl_success) - lib.common.test_msg(msg) - assert (curl_success == 0) diff --git a/tests/wlan_set_ssid.py b/tests/wlan_set_ssid.py deleted file mode 100644 index a713e8c4..00000000 --- a/tests/wlan_set_ssid.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import rootfs_boot -import lib -import sys -import time -import traceback -from devices import board, wan, lan, wlan, prompt -from lib.wifi import * - -class WlanSetSSID(rootfs_boot.RootFSBootTest): - '''Wifi device came up and was able to set SSID.''' - def wlan_setup(self): - wlan.sendline('\napt-get install -qy firmware-realtek usbutils wireless-tools') - wlan.expect('Reading package') - wlan.expect(prompt) - - @lib.common.run_once - def runTest(self): - wlan_iface = wifi_interface(board) - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - - self.config.ssid = randomSSIDName() - - disable_wifi(board) - uciSetWifiSSID(board, self.config.ssid) - enable_wifi(board) - - # verfiy we have an interface here - if wlan_iface == "ath0": - board.sendline('iwconfig %s' % wlan_iface) - board.expect('%s.*IEEE 802.11.*ESSID.*%s' % (wlan_iface, self.config.ssid)) - else: - board.sendline('iwinfo %s info' % wlan_iface) - board.expect('%s.*ESSID.*%s' % (wlan_iface, self.config.ssid)) - board.expect(prompt) - - # wait for AP to set rate, which means it's done coming up - for i in range(20): - try: - essid, channel, rate, freq = wifi_get_info(board, wlan_iface) - info = "Rate = %s Mb/s, Freq = %s Ghz" % (rate, freq) - time.sleep(5) - if wlan_iface == "ath0": - assert float(rate) > 0 - elif wlan_iface == "wlan0": - assert channel > 0 - lib.common.test_msg("%s\n" % info) - self.result_message = self.__doc__ + " (%s)" % info - except Exception as e: - traceback.print_exc(file=sys.stdout) - if i < 10: - pass - else: - break diff --git a/tests/wlan_set_ssid_wpa2psk.py b/tests/wlan_set_ssid_wpa2psk.py deleted file mode 100644 index d873bd05..00000000 --- a/tests/wlan_set_ssid_wpa2psk.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2015 -# -# All rights reserved. -# -# This file is distributed under the Clear BSD license. -# The full text can be found in LICENSE in the root directory. - -import rootfs_boot -import lib -import sys -import time -import traceback -from devices import board, wan, lan, wlan, prompt -from lib.wifi import * - -class WlanSetSSID_WPA2PSK(rootfs_boot.RootFSBootTest): - '''Wifi device came up and was able to set SSID.''' - def wlan_setup(self): - wlan.sendline('\napt-get install -qy firmware-realtek usbutils wireless-tools wpasupplicant') - wlan.expect('Reading package') - wlan.expect(prompt) - - @lib.common.run_once - def runTest(self): - wlan_iface = wifi_interface(board) - wlan_security = "wpa2-psk" - vap_iface = "0" - if wlan_iface is None: - self.skipTest("No wifi interfaces detected, skipping..") - - self.config.ssid = randomSSIDName() - - disable_wifi(board) - uciSetWifiSecurity(board, vap_iface, wlan_security) - uciSetChannel(board, "0", "153") - uciSetWifiSSID(board, self.config.ssid) - enable_wifi(board) - - # verfiy we have an interface here - if wlan_iface == "ath0": - board.sendline('iwconfig %s' % wlan_iface) - board.expect('%s.*IEEE 802.11.*ESSID.*%s' % (wlan_iface, self.config.ssid)) - else: - board.sendline('iwinfo %s info' % wlan_iface) - board.expect('%s.*ESSID.*%s' % (wlan_iface, self.config.ssid)) - board.expect(prompt) - - # wait for AP to set rate, which means it's done coming up - for i in range(20): - try: - essid, channel, rate, freq = wifi_get_info(board, wlan_iface) - info = "Rate = %s Mb/s, Freq = %s Ghz" % (rate, freq) - time.sleep(5) - if wlan_iface == "ath0": - assert float(rate) > 0 - elif wlan_iface == "wlan0": - assert channel > 0 - lib.common.test_msg("%s\n" % info) - self.result_message = self.__doc__ + " (%s)" % info - except Exception as e: - traceback.print_exc(file=sys.stdout) - if i < 10: - pass - else: - break