├── requirements.txt ├── requirements-develop.txt ├── MANIFEST.in ├── offline-demo ├── Makefile ├── run-demo.sh ├── dev1-testcases │ ├── testcases-lag-status.json │ ├── testcases-mlag-status.json │ ├── testcases-optic-inventory.json │ ├── testcases-mlag-interface-status.json │ ├── testcases-cabling.json │ └── testcases-interface-status.json ├── dev1-show-outputs │ ├── show-mlag.json │ ├── show-lldp-neighbors.json │ ├── show-mlag-interfaces.json │ ├── show-lacp-neighbor.json │ └── show-inventory.json ├── README.md ├── conftest.py ├── test_00_optic_inventory.py ├── test_01_interface_status.py ├── test_02_cabling.py ├── test_04_mlag_status.py ├── test_03_lag_status.py ├── test_05_mlag_interface_status.py └── reports │ └── dev1.html ├── online-demo ├── nrfu-dev.sh ├── nrfu-snapshot.py ├── README.md ├── conftest.py ├── test_02_cabling.py ├── test_00_optic_inventory.py ├── test_01_interface_status.py ├── test_03_lag_status.py ├── test_05_mlag_interface_status.py └── test_04_mlag_status.py ├── Makefile ├── tox.ini ├── nrfupytesteos ├── __init__.py ├── conftest.py ├── nrfu_exc.py ├── nrfu_bgp_neighbor_status.py ├── eos_device.py ├── nrfu_mlag_status.py ├── nrfu_mlag_interface_status.py ├── nrfu_optic_inventory.py ├── nrfu_cabling.py ├── nrfu_lag_status.py └── nrfu_interface_status.py ├── setup.py ├── .gitignore ├── README.md └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | pyeapi 2 | paramiko 3 | first -------------------------------------------------------------------------------- /requirements-develop.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-html 3 | pytest-cov 4 | flake8 5 | tox -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include requirements-develop.txt 3 | include README.md -------------------------------------------------------------------------------- /offline-demo/Makefile: -------------------------------------------------------------------------------- 1 | demo: 2 | ./run-demo.sh dev1 3 | 4 | clean: 5 | @rm -rf __pycache__ report.html stampie.log assets htmlcov reports -------------------------------------------------------------------------------- /online-demo/nrfu-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | hostname=$1 4 | shift 5 | set -x 6 | 7 | pytest -v --tb=no \ 8 | --html=${hostname}/report.html --self-contained-html \ 9 | --nrfu-device ${hostname} \ 10 | --nrfu-testcasedir ${hostname} "$@" 11 | -------------------------------------------------------------------------------- /offline-demo/run-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | device=$1 5 | shift 6 | 7 | pytest -v --tb=no \ 8 | --nrfu-device ${device} \ 9 | --nrfu-testcasedir ${device}-testcases \ 10 | --html reports/${device}.html \ 11 | --self-contained-html \ 12 | "$@" 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE = $(python setup.py --name) 2 | 3 | develop: 4 | @ python setup.py develop 5 | @ pip install -r requirements-develop.txt 6 | 7 | clean: 8 | @ rm -rf .tox 9 | @ $(MAKE) -C tests clean 10 | @ python setup.py clean 11 | @ rm -rf *.egg-info .pytest_cache 12 | @ find . -name '*.pyc' | xargs rm 13 | -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-lag-status.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-lag-status", 4 | "dut": "dev1", 5 | "params": { 6 | "name": "Port-Channel2000" 7 | }, 8 | "expected": { 9 | "interfaces": [ 10 | "Ethernet51", 11 | "Ethernet52" 12 | ] 13 | } 14 | } 15 | ] -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-mlag-status.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-mlag-status", 4 | "dut": "dev1", 5 | "params": { 6 | "peer_link": "Port-Channel2000", 7 | "peer_ip": "10.127.253.146", 8 | "vlan_name": "MLAG_CTRLVLAN", 9 | "vlan_id": 4094, 10 | "vlan_ipif": "10.127.253.147/31" 11 | }, 12 | "expected": { 13 | "state": "up" 14 | } 15 | } 16 | ] -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36,flake8 3 | 4 | [base] 5 | deps = 6 | -r{toxinidir}/requirements.txt 7 | -r{toxinidir}/requirements-develop.txt 8 | 9 | [testenv] 10 | whitelist_externals = make 11 | 12 | 13 | [testenv:flake8] 14 | deps = flake8 15 | commands = 16 | flake8 nrfupytesteos 17 | flake8 tests 18 | 19 | [flake8] 20 | # Ignore the following pep8 violations 21 | # E501: 80 character line length limit 22 | # E128: continuation line under-indented for visual indent 23 | ignore = E501, E128 -------------------------------------------------------------------------------- /offline-demo/dev1-show-outputs/show-mlag.json: -------------------------------------------------------------------------------- 1 | { 2 | "localInterface": "Vlan4094", 3 | "systemId": "76:83:ef:ed:66:5d", 4 | "domainId": "MLAG_CTRLVLAN", 5 | "peerLink": "Port-Channel2000", 6 | "dualPrimaryDetectionState": "disabled", 7 | "localIntfStatus": "up", 8 | "peerLinkStatus": "up", 9 | "peerAddress": "192.168.255.2", 10 | "configSanity": "consistent", 11 | "portsErrdisabled": false, 12 | "state": "active", 13 | "reloadDelay": 600, 14 | "reloadDelayNonMlag": 600, 15 | "negStatus": "connected", 16 | "mlagPorts": { 17 | "Disabled": 0, 18 | "Active-partial": 0, 19 | "Inactive": 18, 20 | "Configured": 0, 21 | "Active-full": 1 22 | } 23 | } -------------------------------------------------------------------------------- /nrfupytesteos/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from nrfupytesteos.eos_device import Device # noqa: F401 16 | -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-optic-inventory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-optic-inventory", 4 | "dut": "dummy", 5 | "params": { 6 | "interface": "Ethernet50/1", 7 | "role": "firewall-backup-extern" 8 | }, 9 | "expected": { 10 | "optic": "QSFP28-LR4-100G" 11 | } 12 | }, 13 | { 14 | "test-case": "test-optic-inventory", 15 | "dut": "dummy", 16 | "params": { 17 | "interface": "Ethernet49/1", 18 | "role": "firewall-backup-extern" 19 | }, 20 | "expected": { 21 | "optic": "QSFP28-LR4-100G" 22 | } 23 | }, 24 | { 25 | "test-case": "test-optic-inventory", 26 | "dut": "dummy", 27 | "params": { 28 | "interface": "Ethernet12", 29 | "role": "firewall-backup-extern" 30 | }, 31 | "expected": { 32 | "optic": "" 33 | } 34 | } 35 | ] -------------------------------------------------------------------------------- /offline-demo/dev1-show-outputs/show-lldp-neighbors.json: -------------------------------------------------------------------------------- 1 | { 2 | "tablesLastChangeTime": 1562101353.381394, 3 | "tablesAgeOuts": 0, 4 | "tablesInserts": 7, 5 | "lldpNeighbors": [ 6 | { 7 | "ttl": 120, 8 | "neighborDevice": "switch-21.bld1", 9 | "neighborPort": "Ethernet3/1", 10 | "port": "Ethernet49/1" 11 | }, 12 | { 13 | "ttl": 120, 14 | "neighborDevice": "switch-22.bld1", 15 | "neighborPort": "Ethernet3/1", 16 | "port": "Ethernet50/1" 17 | }, 18 | { 19 | "ttl": 120, 20 | "neighborDevice": "switch-2106.bld1", 21 | "neighborPort": "Ethernet59/1", 22 | "port": "Ethernet59/1" 23 | }, 24 | { 25 | "ttl": 120, 26 | "neighborDevice": "switch-2106.bld1", 27 | "neighborPort": "Ethernet60/1", 28 | "port": "Ethernet60/1" 29 | } 30 | ], 31 | "tablesDeletes": 3, 32 | "tablesDrops": 0 33 | } -------------------------------------------------------------------------------- /offline-demo/dev1-show-outputs/show-mlag-interfaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "interfaces": { 3 | "300": { 4 | "localInterface": "Port-Channel300", 5 | "peerInterface": "Port-Channel300", 6 | "peerInterfaceStatus": "down", 7 | "localInterfaceStatus": "down", 8 | "status": "inactive", 9 | "localInterfaceDescription": "server-A" 10 | }, 11 | "301": { 12 | "localInterface": "Port-Channel301", 13 | "peerInterface": "Port-Channel301", 14 | "peerInterfaceStatus": "up", 15 | "localInterfaceStatus": "up", 16 | "status": "active-full", 17 | "localInterfaceDescription": "server-B" 18 | }, 19 | "302": { 20 | "localInterface": "Port-Channel302", 21 | "peerInterface": "Port-Channel302", 22 | "peerInterfaceStatus": "up", 23 | "localInterfaceStatus": "up", 24 | "status": "active-full", 25 | "localInterfaceDescription": "server-C" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-mlag-interface-status.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-mlag-interface-status", 4 | "dut": "switch-A", 5 | "params": { 6 | "mlag": "300", 7 | "interface": "Port-Channel300", 8 | "peer_interface": "Port-Channel300" 9 | }, 10 | "expected": { 11 | "state": "down" 12 | } 13 | }, 14 | { 15 | "test-case": "test-mlag-interface-status", 16 | "dut": "switch-A", 17 | "params": { 18 | "mlag": "301", 19 | "interface": "Port-Channel301", 20 | "peer_interface": "Port-Channel301" 21 | }, 22 | "expected": { 23 | "state": "up" 24 | } 25 | }, 26 | { 27 | "test-case": "test-mlag-interface-status", 28 | "dut": "switch-A", 29 | "params": { 30 | "mlag": "302", 31 | "interface": "Port-Channel302", 32 | "peer_interface": "Port-Channel302" 33 | }, 34 | "expected": { 35 | "state": "up" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /offline-demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo NRFU 2 | 3 | This directory contains a demonstration of using the NRFU EOS pytest functions by 4 | dynamically loading test-cases based on pytest command line arguments: 5 | 6 | --nrfu-device : the name of the device 7 | --nrfu-testcasedir : the path to the device testcase files 8 | 9 | This demonstrations uses EOS show output that was captured and stored into JSON 10 | files rather than going directly to the EOS device; this was done for the purpose of 11 | demonstrations and dev-testing so that there is no dependencies on actual running systems. 12 | 13 | For an example of using NRFU EOS pytest with an actual device, refer to the 14 | [online-demo](../online-demo) directory. 15 | 16 | # Run 17 | 18 | To run the demonstration: 19 | 20 | ```shell script 21 | $ ./run-demo.sh dev1 22 | ``` 23 | 24 | You can also pass any pytest command line arguements, for example, just run a 25 | single test functions: 26 | 27 | ```shell script 28 | $ ./run-demo.sh dev1 test_01_optic_inventory.py 29 | ``` 30 | 31 | Or pass specific flags like filtering test based on test-case name: 32 | 33 | ```shell script 34 | $ ./run-demo.sh dev1 -k "up and Eth" test_01_interface_status.py 35 | $ ./run-demo.sh dev1 -k "up and not Eth" test_01_interface_status.py 36 | ``` -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from setuptools import setup, find_packages 18 | 19 | package_version = '0.1.0' 20 | package_name = 'nrfu-pytest-eos' 21 | 22 | 23 | def requirements(filename='requirements.txt'): 24 | return open(filename.strip()).readlines() 25 | 26 | 27 | with open("README.md", "r") as fh: 28 | long_description = fh.read() 29 | 30 | 31 | setup( 32 | name=package_name, 33 | version=package_version, 34 | description='Network Ready for Use - PyTest - EOS library', 35 | long_description=long_description, 36 | long_description_content_type="text/markdown", 37 | author='Jeremy Schulman', 38 | packages=find_packages(), 39 | install_requires=requirements(), 40 | ) 41 | -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-cabling.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-cabling", 4 | "dut": "switch-2105.bld1", 5 | "params": { 6 | "interface": "Ethernet49/1", 7 | "role": "spine-leaf" 8 | }, 9 | "expected": { 10 | "remote-hostname": "switch-21.bld1", 11 | "remote-interface": "Ethernet3/1" 12 | } 13 | }, 14 | { 15 | "test-case": "test-cabling", 16 | "dut": "switch-2105.bld1", 17 | "params": { 18 | "interface": "Ethernet50/1", 19 | "role": "spine-leaf" 20 | }, 21 | "expected": { 22 | "remote-hostname": "switch-22.bld1", 23 | "remote-interface": "Ethernet3/1" 24 | } 25 | }, 26 | { 27 | "test-case": "test-cabling", 28 | "dut": "switch-2105.bld1", 29 | "params": { 30 | "interface": "Ethernet59/1", 31 | "role": "peer-pair" 32 | }, 33 | "expected": { 34 | "remote-hostname": "switch-2106.bld1", 35 | "remote-interface": "Ethernet59/1" 36 | } 37 | }, 38 | { 39 | "test-case": "test-cabling", 40 | "dut": "switch-2105.bld1", 41 | "params": { 42 | "interface": "Ethernet60/1", 43 | "role": "peer-pair" 44 | }, 45 | "expected": { 46 | "remote-hostname": "switch-2106.bld1", 47 | "remote-interface": "Ethernet60/1" 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /nrfupytesteos/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | from nrfupytesteos import Device 17 | 18 | __all__ = [ 19 | 'pytest_addoption', 20 | 'device', 21 | 'Device' 22 | ] 23 | 24 | 25 | def pytest_addoption(parser): 26 | parser.addoption("--nrfu-device", 27 | required=True, 28 | help='device name or IP address') 29 | 30 | parser.addoption("--nrfu-testcasedir", 31 | required=True, 32 | help='directory storing device test-case files') 33 | 34 | parser.addoption("--ssh-config", help='path to SSH config file') 35 | 36 | 37 | @pytest.fixture(scope='session') 38 | def device(request): 39 | device_name = request.config.getoption("--nrfu-device") 40 | ssh_config_file = request.config.getoption('--ssh-config') 41 | return Device(device_name, ssh_config_file=ssh_config_file) 42 | -------------------------------------------------------------------------------- /offline-demo/dev1-show-outputs/show-lacp-neighbor.json: -------------------------------------------------------------------------------- 1 | { 2 | "portChannels": { 3 | "Port-Channel2000": { 4 | "interfaces": { 5 | "Ethernet51": { 6 | "partnerPortPriority": 32768, 7 | "partnerPortState": { 8 | "collecting": true, 9 | "distributing": true, 10 | "synchronization": true, 11 | "defaulted": false, 12 | "timeout": false, 13 | "activity": true, 14 | "expired": false, 15 | "aggregation": true 16 | }, 17 | "partnerSystemId": "8000,76-83-ef-ed-66-5d", 18 | "partnerOperKey": "0x000a", 19 | "actorPortStatus": "bundled", 20 | "partnerPortId": 15 21 | }, 22 | "Ethernet52": { 23 | "partnerPortPriority": 32768, 24 | "partnerPortState": { 25 | "collecting": true, 26 | "distributing": true, 27 | "synchronization": true, 28 | "defaulted": false, 29 | "timeout": false, 30 | "activity": true, 31 | "expired": false, 32 | "aggregation": true 33 | }, 34 | "partnerSystemId": "8000,76-83-ef-ed-66-5d", 35 | "partnerOperKey": "0x000a", 36 | "actorPortStatus": "bundled", 37 | "partnerPortId": 32783 38 | } 39 | } 40 | } 41 | }, 42 | "orphanPorts": {} 43 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network Ready for Use - Test Automation 2 | 3 | Network Ready for Use (NRFU) testing ansers the question: 4 | 5 | _*Does the actual operating states of the network match the expected outcomes based on the design?*_ 6 | 7 | This repository contains NRFU for: 8 | 9 | * Execute tests using [PyTest](http://pytest.org) 10 | * Get Arista EOS data via [eAPI](https://github.com/arista-eosplus/pyeapi) 11 | * Test functions: 12 | * check optic transceiver inventory 13 | * check interface status 14 | * check LLDP cabling for expected neighbor 15 | * check LAG interfaces 16 | * check MLAG control-plane status 17 | * check MLAG interface status 18 | * check BGP neighbor status 19 | 20 | # Installation 21 | 22 | Before you use this repository you will need to install a number of python packages. I strongly suggest 23 | you setup a virtual environment first. Then you can run the following commands: 24 | 25 | ```bash 26 | $ python setup.py develop 27 | $ pip install -r requirements-develop.txt 28 | ``` 29 | 30 | # Quick Start - Offline Demo 31 | 32 | If you'd like to see a demonstration the test-cases working immediately, 33 | checkout the [offline-demo](offline-demo) directory. 34 | 35 | ```bash 36 | cd offline-demo 37 | ./run-demo.sh dev1 38 | ``` 39 | 40 | This will run the tests for this repository, but you will see the output of the test-cases running 41 | agaist fake data. For more details, refer to the [offline-demo README.md](offline-demo/README.md). 42 | 43 | # Online Demo 44 | 45 | To execute the tests against a live Device Under Test (DUT) you can checkout the example pytest 46 | testing files/functions defined in the [online-demo](online-demo) directory. In order to perform 47 | tests, there is also a script provided that will extract the existing information from your EOS device 48 | to automatically create test-cases. For more details, refer to the [online-demo README.md](online-demo/README.md). 49 | 50 | -------------------------------------------------------------------------------- /online-demo/nrfu-snapshot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | from pathlib import Path 19 | import json 20 | 21 | from nrfupytesteos import ( 22 | Device, 23 | nrfu_cabling, 24 | nrfu_lag_status, 25 | nrfu_interface_status, 26 | nrfu_optic_inventory, 27 | nrfu_mlag_status, 28 | nrfu_mlag_interface_status 29 | ) 30 | 31 | 32 | try: 33 | dev = Device(hostname=sys.argv[1]) 34 | 35 | except IndexError: 36 | sys.exit("Missing hostname") 37 | 38 | 39 | assert dev.probe(), f"Unable to reach {dev.hostname}, exit." 40 | 41 | snapshot_list = [ 42 | nrfu_cabling, 43 | nrfu_lag_status, 44 | nrfu_interface_status, 45 | nrfu_optic_inventory, 46 | nrfu_mlag_status, 47 | nrfu_mlag_interface_status 48 | ] 49 | 50 | 51 | def snapshot(): 52 | snapshot_dir = Path.cwd() / dev.hostname 53 | 54 | print(f"Ensure directory: {snapshot_dir.name}") 55 | snapshot_dir.mkdir(exist_ok=True) 56 | 57 | for nrfu in snapshot_list: 58 | testcases = nrfu.snapshot_testcases(dev) 59 | if not testcases: 60 | print(f"\t[-] {nrfu.TEST_CASE_NAME} skipping, no test-cases") 61 | continue 62 | 63 | tc_file = snapshot_dir / f'{nrfu.TEST_CASE_NAME}.json' 64 | print(f"\t[+] {nrfu.TEST_CASE_NAME} creating {len(testcases)} test-cases") 65 | json.dump(testcases, tc_file.open('w+'), indent=3) 66 | 67 | 68 | if __name__ == "__main__": 69 | snapshot() 70 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_exc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | class NrfuError(RuntimeError): 16 | def __init__(self, *vargs, extra=None): 17 | super(NrfuError, self).__init__(*vargs) 18 | self.extra = extra or '' 19 | 20 | 21 | class MissingError(NrfuError): 22 | def __init__(self, *vargs, missing=None): 23 | super(MissingError, self).__init__(*vargs) 24 | self.missing = missing 25 | 26 | def __str__(self): 27 | emsg = super(MissingError, self).__str__() 28 | msg = f"MISSING data: {self.missing}" 29 | return '\n'.join((emsg, msg, self.extra)) 30 | 31 | 32 | class UnexpectedError(NrfuError): 33 | def __init__(self, *vargs, unexpected=None): 34 | super(UnexpectedError, self).__init__(*vargs) 35 | self.unexpected = unexpected 36 | 37 | def __str__(self): 38 | emsg = super(UnexpectedError, self).__str__() 39 | msg = f"UNEXPECTED data: {self.unexpected}" 40 | return '\n'.join((emsg, msg, self.extra)) 41 | 42 | 43 | class MismatchError(NrfuError): 44 | def __init__(self, *vargs, expected=None, actual=None): 45 | super(MismatchError, self).__init__(*vargs) 46 | self.expected = expected 47 | self.actual = actual 48 | 49 | def __str__(self): 50 | emsg = super(MismatchError, self).__str__() 51 | exp_msg = f"MISMATCH:EXPECTED data: {self.expected or 'None'}" 52 | act_msg = f"MISMATCH:ACTUAL data: {self.actual or 'None'}" 53 | return '\n'.join((emsg, exp_msg, act_msg, self.extra)) 54 | -------------------------------------------------------------------------------- /online-demo/README.md: -------------------------------------------------------------------------------- 1 | # Example Live DUT Testing 2 | 3 | In order to connect to your remote devices, you will need to export the 4 | following environment variables: 5 | 6 | ```bash 7 | export EOS_USER=your-login-name 8 | export EOS_PASSWORD=your-login-password 9 | ``` 10 | 11 | If you are using an ssh-config file for hostname resolution (i.e. no DNS), you 12 | will also need to: 13 | 14 | ```bash 15 | export EOS_SSH_CONFIG= 16 | ``` 17 | 18 | # Snapshot Live DUT 19 | 20 | In order to automatically create test-cases based on the existing operational states, you can 21 | use the `nrfu-snapshot.py` program. For exmaple: 22 | 23 | ```bash 24 | ./nrfu-snapshot.py switch-101.bld1 25 | ``` 26 | 27 | You can provide either the hostname (as shown) or the IP address of the device. Note that which every 28 | you use, you must ensure that the EOS device is configured to support eAPI. 29 | 30 | Once you run this utility, you will see the following output indicating information about 31 | each of test functions. For example, running the command: 32 | 33 | ````bash 34 | $ ./nrfu-snapshot.py switch-101.bld1 35 | ```` 36 | 37 | Will result in the following output 38 | ```bash 39 | Ensure directory: switch-101.bld1 40 | [+] test-cabling creating 6 test-cases 41 | [+] test-lag-status creating 5 test-cases 42 | [+] test-interface-status creating 73 test-cases 43 | [+] test-optic-inventory creating 60 test-cases 44 | [+] test-mlag-status creating 1 test-cases 45 | [+] test-mlag-interface-status creating 4 test-cases 46 | ```` 47 | 48 | You should see the new directory `switch-101.bld1`, and inside there a number of JSON files storing 49 | the test-cases for each of the test functions, for example `switch-101.bld1/test_optic_inventory.json` 50 | 51 | # Run NRFU Tests 52 | 53 | Once you have your test-cases created, you can then run the NRFU test utility: 54 | 55 | For example using the same "switch-101.bld1" host: 56 | 57 | ````bash 58 | ./nrfu-dev.sh switch-101.bld1 59 | ```` 60 | 61 | The shell-script is a wrapper around calling pytest, and you should see as the first line in the output 62 | something like this: 63 | 64 | ```bash 65 | + pytest -v --tb=no --html=switch-101.bld1/report.html \ 66 | --nrfu-device switch-101.bld1 \ 67 | --nrfu-testcasedir switch-101.bld1 68 | ``` 69 | 70 | And then following that, the output of the pytest execution. The pytest run will generate an HTML report 71 | file that you can use to see details of each of the failed tests. You can open that report file in your 72 | browswer window directly -------------------------------------------------------------------------------- /online-demo/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from pathlib import Path 16 | 17 | import pytest 18 | 19 | from nrfupytesteos.conftest import * 20 | from pyeapi.eapilib import ConnectionError 21 | 22 | 23 | class NRFUconfig(object): 24 | """ for NRFU specific config / runtime """ 25 | pass 26 | 27 | 28 | def pytest_sessionstart(session): 29 | config = session.config 30 | 31 | # Store the instance of the Path to the test-case dir since we'll be using 32 | # it many times in each of the test function modules. 33 | 34 | config._nrfu = NRFUconfig() 35 | config._nrfu.testcases_dir = Path(config.option.nrfu_testcasedir).absolute() 36 | 37 | device_name = config.getoption("--nrfu-device") 38 | ssh_config_file = config.getoption('--ssh-config') 39 | dev = Device(device_name, ssh_config_file=ssh_config_file) 40 | 41 | # first probe IP/hostname of the device to ensure it is reachable. 42 | 43 | if not dev.probe(): 44 | pytest.exit(f"Device unreachable, check --nrfu-device value: {device_name}.") 45 | 46 | # next try to run a show command to ensure the credentials are valid. 47 | 48 | try: 49 | dev.execute('show version') 50 | 51 | except Exception as exc: 52 | if isinstance(exc, ConnectionError): 53 | pytest.exit("Unable to access device.\nCheck you $EOS_USER and $EOS_PASSWORD env") 54 | 55 | pytest.exit(f"Unable to access device {device_name}: {str(exc)}") 56 | 57 | # I am now going to attach this device object to the session config data 58 | # since we have it, and we can then reference it for use by the device 59 | # fixture so we don't need to re-connect to the device a second time. ;-) 60 | 61 | config._nrfu.device = dev 62 | 63 | 64 | @pytest.fixture(scope='session') 65 | def device(request): 66 | """ 67 | This fixture will be called once to return the existing Device instance 68 | that was setup in the session start hook function. 69 | """ 70 | 71 | return request.config._nrfu.device 72 | -------------------------------------------------------------------------------- /offline-demo/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | For the purpose of demonstration and testing, we are NOT going to talk directly 17 | to a device, but rather use captured device output so that we are not dependent 18 | upon having live device connectivity. For our demonstration, we will have a 19 | device called "dev1" and the show outputs will be stored in a directory called 20 | "dev1-show-outputs". The test-cases for this device will be stored in a 21 | directory called "dev1-testcases". 22 | """ 23 | from pathlib import Path 24 | 25 | import pytest 26 | 27 | from nrfupytesteos import Device 28 | 29 | # import the pytest_addoption from the package so we pickup the pytest options 30 | # for NRFU. The `pytest_addoption` function needs to be scoped in this file in 31 | # order for the pytest commandline to use the --nrfu options. 32 | 33 | from nrfupytesteos.conftest import pytest_addoption # noqa 34 | 35 | 36 | def pytest_sessionstart(session): 37 | """ 38 | pytest hook function called at the start of the session processing & after 39 | the command line arguments have been processed. 40 | 41 | Normally when a session starts we would attempt to connect to the device 42 | and verify that we can reach it. See the `online-demo/conftest.py` for 43 | performing that action with EOS. For the purposes of demonstration, we are 44 | going to store Path variables to the directories we will need in order to 45 | obtain the demo "show" output and the test-cases 46 | 47 | Parameters 48 | ---------- 49 | session : Session - pytest Session instance 50 | """ 51 | cfg_opts = session.config.option 52 | 53 | session.config._nrfu = dict( 54 | show_outputs_dir=Path.cwd().joinpath(cfg_opts.nrfu_device + "-show-outputs"), 55 | testcase_dir=Path(cfg_opts.nrfu_testcasedir).absolute() 56 | ) 57 | 58 | 59 | @pytest.fixture(scope='session') 60 | def device(pytestconfig): 61 | """ 62 | Normally when we want the device instance we would create a pyEapi session 63 | and return it; see the `online-demo/conftest.py` for real-world use. Since we 64 | are using captured show output data, we are going to instead add a variable 65 | to the Device instance so that the test functions can use it to load the 66 | fake data. 67 | 68 | Parameters 69 | ---------- 70 | pytestconfig : Config - the pytest Config instance 71 | 72 | Returns 73 | ------- 74 | Device 75 | The device instance that will be used by the test functions 76 | """ 77 | device_name = pytestconfig.option.nrfu_device 78 | dev = Device(device_name) 79 | dev.show_outputs_dir = pytestconfig._nrfu['show_outputs_dir'] 80 | return dev 81 | -------------------------------------------------------------------------------- /online-demo/test_02_cabling.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU cabling test-cases based on the --nrfu-testcasedir command line 17 | option. This file retrieves the device specific "show" output directly from 18 | the device. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU cabling module so the functions in this file can 25 | # generate the test-case names and invoke the actual NRFU validation function. 26 | 27 | import nrfupytesteos.nrfu_cabling as nrfu 28 | 29 | 30 | @pytest.fixture(scope='module') 31 | def device_lldp_neighbors(device): 32 | """ 33 | This fixture is used to return the EOS result of the 'show lldp neighbors' 34 | command as structured data. 35 | 36 | Parameters 37 | ---------- 38 | device : Device instance 39 | 40 | Returns 41 | ------- 42 | dict 43 | The dictionary output of the "show lldp neighbors" command 44 | """ 45 | return nrfu.snapshot_testdata(device) 46 | 47 | 48 | def pytest_generate_tests(metafunc): 49 | """ 50 | pytest will invoke this hook allowing us to dynamically load the device 51 | specific cabling test cases based on the directory the User provided as the 52 | --nrfu-testcasedir command line argument. 53 | 54 | Parameters 55 | ---------- 56 | metafunc : Metafunc instance used to parametrize the test function 57 | """ 58 | 59 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 60 | f'{nrfu.TEST_CASE_NAME}.json') 61 | 62 | metafunc.parametrize('testcase', 63 | json.load(testcases_file.open()), 64 | ids=nrfu.name_test) 65 | 66 | 67 | def test_cabling(device, device_lldp_neighbors, testcase): 68 | """ 69 | pytest will call this function for each test-case item loaded via the 70 | pytest_generate_tests hook function. This function will in turn call the 71 | actual NRFU EOS specific test function `nrfu.test_cabling` that will 72 | validate the specific `testcase` against the actual data 73 | `device_lldp_neighbors`. If `nrfu.test_cabling` detects a failure it will 74 | raise a specific NRFU exception. The pytest framework will catch that 75 | exception and report the test as failed. 76 | 77 | Parameters 78 | ---------- 79 | device : Device instance 80 | 81 | device_lldp_neighbors : dict 82 | The EOS structured output of the "show lldp neighbors" command 83 | 84 | testcase : dict 85 | A specific test-case from the list of all test-cases loaded. 86 | 87 | Raises 88 | ------ 89 | See `nrfu.test_cabling` docs for details 90 | """ 91 | nrfu.test_cabling( 92 | device=device, 93 | actual=device_lldp_neighbors, 94 | testcase=testcase) 95 | -------------------------------------------------------------------------------- /online-demo/test_00_optic_inventory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU optic-inventory test-cases based on the --nrfu-testcasedir command 17 | line option. This file retrieves the device specific "show" directly from the 18 | device. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU optic inventory module so the functions in this 25 | # file can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | import nrfupytesteos.nrfu_optic_inventory as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_inventory(device): 33 | """ 34 | This fixture is used to return the EOS result of the "show inventory" 35 | command as structured data. 36 | 37 | Parameters 38 | ---------- 39 | device : Device instance 40 | 41 | Returns 42 | ------- 43 | dict 44 | The dictionary output of the "show inventory" command 45 | """ 46 | return nrfu.snapshot_testdata(device) 47 | 48 | 49 | def pytest_generate_tests(metafunc): 50 | """ 51 | pytest will invoke this hook allowing us to dynamically load the device 52 | specific optic status test cases based on the directory the User provided 53 | as the --nrfu-testcasedir command line argument. 54 | 55 | Parameters 56 | ---------- 57 | metafunc : Metafunc instance used to parametrize the test function 58 | """ 59 | 60 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 61 | f'{nrfu.TEST_CASE_NAME}.json') 62 | 63 | metafunc.parametrize('testcase', 64 | json.load(testcases_file.open()), 65 | ids=nrfu.name_test) 66 | 67 | 68 | def test_optic_inventory(device, device_inventory, testcase): 69 | """ 70 | pytest will call this function for each test-case item loaded via the 71 | pytest_generate_tests hook function. This function will in turn call the 72 | actual NRFU EOS specific test function `nrfu.test_optic_inventory` that 73 | will validate the specific `testcase` against the actual data 74 | `device_inventory`. If `nrfu.test_optic_inventory` detects a failure it 75 | will raise a specific NRFU exception. The pytest framework will catch that 76 | exception and report the test as failed. 77 | 78 | Parameters 79 | ---------- 80 | device : Device instance 81 | 82 | device_inventory : dict 83 | The EOS structured output of the "show inventory" command 84 | 85 | testcase : dict 86 | A specific test-case from the list of all test-cases loaded. 87 | 88 | Raises 89 | ------ 90 | See `nrfu.test_optic_inventory` docs for details 91 | """ 92 | 93 | nrfu.test_optic_inventory( 94 | device=device, 95 | actual=device_inventory, 96 | testcase=testcase) 97 | -------------------------------------------------------------------------------- /online-demo/test_01_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU interface status test-cases based on the --nrfu-testcasedir command 17 | line option. This file retrieves the device specific "show" output from the 18 | device directly. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU interface status module so the functions in this 25 | # file can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | import nrfupytesteos.nrfu_interface_status as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_interfaces_status(device): 33 | """ 34 | This fixture is used to return the EOS result of the "show interfaces" command 35 | as structured data. 36 | 37 | Parameters 38 | ---------- 39 | device : Device instance 40 | 41 | Returns 42 | ------- 43 | dict 44 | The dictionary output of the "show interfaces" command 45 | """ 46 | return nrfu.snapshot_testdata(device) 47 | 48 | 49 | def pytest_generate_tests(metafunc): 50 | """ 51 | pytest will invoke this hook allowing us to dynamically load the device 52 | specific interface status test cases based on the directory the User 53 | provided as the --nrfu-testcasedir command line argument. 54 | 55 | Parameters 56 | ---------- 57 | metafunc : Metafunc instance used to parametrize the test function 58 | """ 59 | 60 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 61 | f'{nrfu.TEST_CASE_NAME}.json') 62 | 63 | metafunc.parametrize('testcase', 64 | json.load(testcases_file.open()), 65 | ids=nrfu.name_test) 66 | 67 | 68 | def test_interface_status(device, device_interfaces_status, testcase): 69 | """ 70 | pytest will call this function for each test-case item loaded via the 71 | pytest_generate_tests hook function. This function will in turn call the 72 | actual NRFU EOS specific test function `nrfu.test_interface_status` that 73 | will validate the specific `testcase` against the actual data 74 | `device_interfaces_status`. If `nrfu.test_interface_status` detects a 75 | failure it will raise a specific NRFU exception. The pytest framework will 76 | catch that exception and report the test as failed. 77 | 78 | Parameters 79 | ---------- 80 | device : Device instance 81 | 82 | device_interfaces_status : dict 83 | The EOS structured output of the "show interfaces" command 84 | 85 | testcase : dict 86 | A specific test-case from the list of all test-cases loaded. 87 | 88 | Raises 89 | ------ 90 | See `nrfu.test_interface_status` docs for details 91 | """ 92 | 93 | nrfu.test_interface_status( 94 | device=device, 95 | actual=device_interfaces_status, 96 | testcase=testcase) 97 | -------------------------------------------------------------------------------- /online-demo/test_03_lag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file provides the pytest framework and test functions used to dynamically 17 | load NRFU LAG test-cases based on the --nrfu-testcasedir command line option. 18 | This file retrieves the device specific "show" output from a file rather than 19 | an actual device to support a dev-demo environment. 20 | """ 21 | 22 | import json 23 | import pytest 24 | import nrfupytesteos.nrfu_lag_status as nrfu 25 | 26 | 27 | @pytest.fixture(scope='module') 28 | def device_lacp_neighbors(device): 29 | """ 30 | This fixture is used to return the EOS result of the 'show lacp neighbor' 31 | command as structured data. 32 | 33 | Rather than running the specific EOS show command, we are going to load the 34 | previously captured output. For a real-world example, see the file 35 | `online-demo/test_03_lag_status.py`. 36 | 37 | Parameters 38 | ---------- 39 | device : Device instance 40 | 41 | Returns 42 | ------- 43 | dict 44 | The dictionary output of the 'show lacp neighbor' command that was 45 | retrieve from a file rather than the device. 46 | """ 47 | return nrfu.snapshot_testdata(device) 48 | 49 | 50 | def pytest_generate_tests(metafunc): 51 | """ 52 | pytest will invoke this hook allowing us to dynamically load the device 53 | specific LAG test cases based on the directory the User provided as the 54 | --nrfu-testcasedir command line argument. 55 | 56 | Parameters 57 | ---------- 58 | metafunc : Metafunc instance used to parametrize the test function 59 | """ 60 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 61 | f'{nrfu.TEST_CASE_NAME}.json') 62 | 63 | if not testcases_file.exists(): 64 | metafunc.parametrize('testcase', [], ids=['no-tests']) 65 | return 66 | 67 | metafunc.parametrize('testcase', 68 | json.load(testcases_file.open()), 69 | ids=nrfu.name_test) 70 | 71 | 72 | def test_lag_status(device, device_lacp_neighbors, testcase): 73 | """ 74 | pytest will call this function for each test-case item loaded via the 75 | pytest_generate_tests hook function. This function will in turn call the 76 | actual NRFU EOS specific test function `nrfu.test_lag_status` that will 77 | validate the specific `testcase` against the actual data 78 | `device_lacp_neighbors`. If `nrfu.test_lag_status` detects a failure it 79 | will raise a specific NRFU exception. The pytest framework will catch that 80 | exception and report the test as failed. 81 | 82 | Parameters 83 | ---------- 84 | device : Device instance 85 | 86 | device_lacp_neighbors : dict 87 | The EOS structured output of the 'show lacp neighbor' command 88 | 89 | testcase : dict 90 | A specific test-case from the list of all test-cases loaded. 91 | 92 | Raises 93 | ------ 94 | See `nrfu.test_lag_status` docs for details 95 | """ 96 | nrfu.test_lag_status( 97 | device=device, 98 | actual=device_lacp_neighbors, 99 | testcase=testcase) 100 | 101 | -------------------------------------------------------------------------------- /offline-demo/test_00_optic_inventory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU optic-inventory test-cases based on the --nrfu-testcasedir command 17 | line option. This file retrieves the device specific "show" output from a file 18 | rather than an actual device to support a dev-demo environment. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU optic inventory module so the functions in this 25 | # file can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | from nrfupytesteos import nrfu_optic_inventory as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_inventory(device): 33 | """ 34 | This fixture is used to return the EOS result of the "show inventory" 35 | command as structured data. 36 | 37 | Rather than running the specific EOS show command, we are going to load the 38 | previously captured output. For a real-world example, see the file 39 | `online-demo/test_00_optic_inventory.py`. 40 | 41 | Parameters 42 | ---------- 43 | device : Device instance 44 | 45 | Returns 46 | ------- 47 | dict 48 | The dictionary output of the "show inventory" command that was retrieve 49 | from a file rather than the device. 50 | """ 51 | device_data = device.show_outputs_dir / 'show-inventory.json' 52 | return json.load(device_data.open()) 53 | 54 | 55 | def pytest_generate_tests(metafunc): 56 | """ 57 | pytest will invoke this hook allowing us to dynamically load the device 58 | specific optic status test cases based on the directory the User provided 59 | as the --nrfu-testcasedir command line argument. 60 | 61 | Parameters 62 | ---------- 63 | metafunc : Metafunc instance used to parametrize the test function 64 | """ 65 | tc_file = metafunc.config._nrfu['testcase_dir'].joinpath( 66 | 'testcases-optic-inventory.json') 67 | 68 | metafunc.parametrize('testcase', 69 | json.load(tc_file.open()), 70 | ids=nrfu.name_test) 71 | 72 | 73 | def test_optic_inventory(device, device_inventory, testcase): 74 | """ 75 | pytest will call this function for each test-case item loaded via the 76 | pytest_generate_tests hook function. This function will in turn call the 77 | actual NRFU EOS specific test function `nrfu.test_optic_inventory` that 78 | will validate the specific `testcase` against the actual data 79 | `device_inventory`. If `nrfu.test_optic_inventory` detects a failure it 80 | will raise a specific NRFU exception. The pytest framework will catch that 81 | exception and report the test as failed. 82 | 83 | Parameters 84 | ---------- 85 | device : Device instance 86 | 87 | device_inventory : dict 88 | The EOS structured output of the "show inventory" command 89 | 90 | testcase : dict 91 | A specific test-case from the list of all test-cases loaded. 92 | 93 | Raises 94 | ------ 95 | See `nrfu.test_optic_inventory` docs for details 96 | """ 97 | nrfu.test_optic_inventory(device=device, actual=device_inventory, 98 | testcase=testcase) 99 | -------------------------------------------------------------------------------- /offline-demo/test_01_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU interface status test-cases based on the --nrfu-testcasedir command 17 | line option. This file retrieves the device specific "show" output from a file 18 | rather than an actual device to support a dev-demo environment. 19 | """ 20 | import json 21 | import pytest 22 | 23 | # import the EOS specific NRFU interface status module so the functions in this 24 | # file can generate the test-case names and invoke the actual NRFU validation 25 | # function. 26 | 27 | from nrfupytesteos import nrfu_interface_status as nrfu 28 | 29 | 30 | @pytest.fixture(scope='module') 31 | def device_interfaces_status(device): 32 | """ 33 | This fixture is used to return the EOS result of the "show interfaces" command 34 | as structured data. 35 | 36 | Rather than running the specific EOS show command, we are going to load the 37 | previously captured output. For a real-world example, see the file 38 | `online-demo/test_01_interface_status.py`. 39 | 40 | Parameters 41 | ---------- 42 | device : Device instance 43 | 44 | Returns 45 | ------- 46 | dict 47 | The dictionary output of the "show interfaces" command that was retrieve 48 | from a file rather than the device. 49 | """ 50 | show_file = device.show_outputs_dir.joinpath('show-interfaces.json') 51 | return json.load(show_file.open()) 52 | 53 | 54 | def pytest_generate_tests(metafunc): 55 | """ 56 | pytest will invoke this hook allowing us to dynamically load the device 57 | specific interface status test cases based on the directory the User 58 | provided as the --nrfu-testcasedir command line argument. 59 | 60 | Parameters 61 | ---------- 62 | metafunc : Metafunc instance used to parametrize the test function 63 | """ 64 | 65 | tc_file = metafunc.config._nrfu['testcase_dir'].joinpath( 66 | 'testcases-interface-status.json') 67 | 68 | metafunc.parametrize('testcase', 69 | json.load(tc_file.open()), 70 | ids=nrfu.name_test) 71 | 72 | 73 | def test_interface_status(device, device_interfaces_status, testcase): 74 | """ 75 | pytest will call this function for each test-case item loaded via the 76 | pytest_generate_tests hook function. This function will in turn call the 77 | actual NRFU EOS specific test function `nrfu.test_interface_status` that 78 | will validate the specific `testcase` against the actual data 79 | `device_interfaces_status`. If `nrfu.test_interface_status` detects a 80 | failure it will raise a specific NRFU exception. The pytest framework will 81 | catch that exception and report the test as failed. 82 | 83 | Parameters 84 | ---------- 85 | device : Device instance 86 | 87 | device_interfaces_status : dict 88 | The EOS structured output of the "show interfaces" command 89 | 90 | testcase : dict 91 | A specific test-case from the list of all test-cases loaded. 92 | 93 | Raises 94 | ------ 95 | See `nrfu.test_interface_status` docs for details 96 | """ 97 | nrfu.test_interface_status(device=device, actual=device_interfaces_status, 98 | testcase=testcase) 99 | -------------------------------------------------------------------------------- /offline-demo/test_02_cabling.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU cabling test-cases based on the --nrfu-testcasedir command line 17 | option. This file retrieves the device specific "show" output from a file 18 | rather than an actual device to support a dev-demo environment. 19 | """ 20 | import json 21 | 22 | import pytest 23 | 24 | # import the EOS specific NRFU cabling module so the functions in this file can 25 | # generate the test-case names and invoke the actual NRFU validation function. 26 | 27 | from nrfupytesteos import nrfu_cabling as nrfu 28 | 29 | 30 | @pytest.fixture(scope='module') 31 | def device_lldp_neighbors(device): 32 | """ 33 | This fixture is used to return the EOS result of the 'show lldp neighbors' 34 | command as structured data. 35 | 36 | Rather than running the specific EOS show command, we are going to load the 37 | previously captured output. For a real-world example, see the file 38 | `online-demo/test_02_cabling.py`. 39 | 40 | Parameters 41 | ---------- 42 | device : Device instance 43 | 44 | Returns 45 | ------- 46 | dict 47 | The dictionary output of the "show lldp neighbors" command that was 48 | retrieve from a file rather than the device. 49 | """ 50 | show_file = device.show_outputs_dir.joinpath( 51 | 'show-lldp-neighbors.json') 52 | 53 | return json.load(show_file.open()) 54 | 55 | 56 | def pytest_generate_tests(metafunc): 57 | """ 58 | pytest will invoke this hook allowing us to dynamically load the device 59 | specific cabling test cases based on the directory the User provided as the 60 | --nrfu-testcasedir command line argument. 61 | 62 | Parameters 63 | ---------- 64 | metafunc : Metafunc instance used to parametrize the test function 65 | """ 66 | testcases_file = metafunc.config._nrfu['testcase_dir'].joinpath( 67 | 'testcases-cabling.json') 68 | 69 | metafunc.parametrize('testcase', 70 | json.load(testcases_file.open()), 71 | ids=nrfu.name_test) 72 | 73 | 74 | def test_cabling(device, device_lldp_neighbors, testcase): 75 | """ 76 | pytest will call this function for each test-case item loaded via the 77 | pytest_generate_tests hook function. This function will in turn call the 78 | actual NRFU EOS specific test function `nrfu.test_cabling` that will 79 | validate the specific `testcase` against the actual data 80 | `device_lldp_neighbors`. If `nrfu.test_cabling` detects a failure it will 81 | raise a specific NRFU exception. The pytest framework will catch that 82 | exception and report the test as failed. 83 | 84 | Parameters 85 | ---------- 86 | device : Device instance 87 | 88 | device_lldp_neighbors : dict 89 | The EOS structured output of the "show lldp neighbors" command 90 | 91 | testcase : dict 92 | A specific test-case from the list of all test-cases loaded. 93 | 94 | Raises 95 | ------ 96 | See `nrfu.test_cabling` docs for details 97 | """ 98 | nrfu.test_cabling(device, actual=device_lldp_neighbors, 99 | testcase=testcase) 100 | -------------------------------------------------------------------------------- /offline-demo/test_04_mlag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU MLAG test-cases based on the --nrfu-testcasedir command line option. 17 | This file retrieves the device specific "show" output from a file rather than 18 | an actual device to support a dev-demo environment. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU MLAG status module so the functions in this file 25 | # can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | from nrfupytesteos import nrfu_mlag_status as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_mlag_status(device): 33 | """ 34 | This fixture is used to return the EOS result of the 'show mlag' command as 35 | structured data. 36 | 37 | Rather than running the specific EOS show command, we are going to load the 38 | previously captured output. For a real-world example, see the file 39 | `online-demo/test_04_mlag_status.py`. 40 | 41 | Parameters 42 | ---------- 43 | device : Device instance 44 | 45 | Returns 46 | ------- 47 | dict 48 | The dictionary output of the 'show mlag' command that was retrieve from 49 | a file rather than the device. 50 | """ 51 | show_file = device.show_outputs_dir.joinpath( 52 | 'show-mlag.json') 53 | 54 | return json.load(show_file.open()) 55 | 56 | 57 | def pytest_generate_tests(metafunc): 58 | """ 59 | pytest will invoke this hook allowing us to dynamically load the device 60 | specific MLAG test cases based on the directory the User provided as the 61 | --nrfu-testcasedir command line argument. 62 | 63 | Parameters 64 | ---------- 65 | metafunc : Metafunc instance used to parametrize the test function 66 | """ 67 | 68 | testcases_file = metafunc.config._nrfu['testcase_dir'].joinpath( 69 | 'testcases-mlag-status.json') 70 | 71 | metafunc.parametrize('testcase', 72 | json.load(testcases_file.open()), 73 | ids=nrfu.name_test) 74 | 75 | 76 | def test_mlag_status(device, device_mlag_status, testcase): 77 | """ 78 | pytest will call this function for each test-case item loaded via the 79 | pytest_generate_tests hook function. This function will in turn call the 80 | actual NRFU EOS specific test function `nrfu.test_mlag_status` that will 81 | validate the specific `testcase` against the actual data 82 | `device_mlag_status`. If `nrfu.test_mlag_status` detects a failure it will 83 | raise a specific NRFU exception. The pytest framework will catch that 84 | exception and report the test as failed. 85 | 86 | Parameters 87 | ---------- 88 | device : Device instance 89 | 90 | device_mlag_status : dict 91 | The EOS structured output of the 'show mlag' command 92 | 93 | testcase : dict 94 | A specific test-case from the list of all test-cases loaded. 95 | 96 | Raises 97 | ------ 98 | See `nrfu.test_mlag_status` docs for details 99 | """ 100 | nrfu.test_mlag_status(device=device, actual=device_mlag_status, 101 | testcase=testcase) 102 | -------------------------------------------------------------------------------- /offline-demo/test_03_lag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU LAG test-cases based on the --nrfu-testcasedir command line option. 17 | This file retrieves the device specific "show" output from a file rather than 18 | an actual device to support a dev-demo environment. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU LAG status module so the functions in this file 25 | # can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | from nrfupytesteos import nrfu_lag_status as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_lacp_neighbors(device): 33 | """ 34 | This fixture is used to return the EOS result of the 'show lacp neighbor' 35 | command as structured data. 36 | 37 | Rather than running the specific EOS show command, we are going to load the 38 | previously captured output. For a real-world example, see the file 39 | `online-demo/test_03_lag_status.py`. 40 | 41 | Parameters 42 | ---------- 43 | device : Device instance 44 | 45 | Returns 46 | ------- 47 | dict 48 | The dictionary output of the 'show lacp neighbor' command that was 49 | retrieve from a file rather than the device. 50 | """ 51 | show_file = device.show_outputs_dir.joinpath( 52 | 'show-lacp-neighbor.json') 53 | 54 | return json.load(show_file.open()) 55 | 56 | 57 | def pytest_generate_tests(metafunc): 58 | """ 59 | pytest will invoke this hook allowing us to dynamically load the device 60 | specific LAG test cases based on the directory the User provided as the 61 | --nrfu-testcasedir command line argument. 62 | 63 | Parameters 64 | ---------- 65 | metafunc : Metafunc instance used to parametrize the test function 66 | """ 67 | 68 | testcases_file = metafunc.config._nrfu['testcase_dir'].joinpath( 69 | 'testcases-lag-status.json') 70 | 71 | metafunc.parametrize('testcase', 72 | json.load(testcases_file.open()), 73 | ids=nrfu.name_test) 74 | 75 | 76 | def test_pass_lag_status(device, device_lacp_neighbors, testcase): 77 | """ 78 | pytest will call this function for each test-case item loaded via the 79 | pytest_generate_tests hook function. This function will in turn call the 80 | actual NRFU EOS specific test function `nrfu.test_lag_status` that will 81 | validate the specific `testcase` against the actual data 82 | `device_lacp_neighbors`. If `nrfu.test_lag_status` detects a failure it 83 | will raise a specific NRFU exception. The pytest framework will catch that 84 | exception and report the test as failed. 85 | 86 | Parameters 87 | ---------- 88 | device : Device instance 89 | 90 | device_lacp_neighbors : dict 91 | The EOS structured output of the 'show lacp neighbor' command 92 | 93 | testcase : dict 94 | A specific test-case from the list of all test-cases loaded. 95 | 96 | Raises 97 | ------ 98 | See `nrfu.test_lag_status` docs for details 99 | """ 100 | nrfu.test_lag_status(device=device, actual=device_lacp_neighbors, 101 | testcase=testcase) 102 | -------------------------------------------------------------------------------- /online-demo/test_05_mlag_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU MLAG interface status test-cases based on the --nrfu-testcasedir 17 | command line option. This file retrieves the device specific "show" output 18 | directly from the device. 19 | """ 20 | 21 | # import the EOS specific NRFU MLAG interface status module so the functions in 22 | # this file can generate the test-case names and invoke the actual NRFU 23 | # validation function. 24 | 25 | import json 26 | import pytest 27 | 28 | # import the EOS specific NRFU MLAG interface status module so the functions in 29 | # this file can generate the test-case names and invoke the actual NRFU 30 | # validation function. 31 | 32 | import nrfupytesteos.nrfu_mlag_interface_status as nrfu 33 | 34 | 35 | @pytest.fixture(scope='module') 36 | def device_mlag_interfaces_status(device): 37 | """ 38 | This fixture is used to return the EOS result of the 'show mlag interfaces' 39 | command as structured data. 40 | 41 | Parameters 42 | ---------- 43 | device : Device instance 44 | 45 | Returns 46 | ------- 47 | dict 48 | The dictionary output of the 'show mlag interfaces' command 49 | """ 50 | return nrfu.snapshot_testdata(device) 51 | 52 | 53 | def pytest_generate_tests(metafunc): 54 | """ 55 | pytest will invoke this hook allowing us to dynamically load the device 56 | specific MLAG interface status test cases based on the directory the User 57 | provided as the --nrfu-testcasedir command line argument. 58 | 59 | Parameters 60 | ---------- 61 | metafunc : Metafunc instance used to parametrize the test function 62 | """ 63 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 64 | f'{nrfu.TEST_CASE_NAME}.json') 65 | 66 | if not testcases_file.exists(): 67 | metafunc.parametrize('testcase', [], ids=['no-tests']) 68 | return 69 | 70 | metafunc.parametrize('testcase', 71 | json.load(testcases_file.open()), 72 | ids=nrfu.name_test) 73 | 74 | def test_mlag_interface_status(device, device_mlag_interfaces_status, testcase): 75 | """ 76 | pytest will call this function for each test-case item loaded via the 77 | pytest_generate_tests hook function. This function will in turn call the 78 | actual NRFU EOS specific test function `nrfu.test_mlag_interface_status` 79 | that will validate the specific `testcase` against the actual data 80 | `device_mlag_interfaces_status`. If `nrfu.test_mlag_interface_status` 81 | detects a failure it will raise a specific NRFU exception. The pytest 82 | framework will catch that exception and report the test as failed. 83 | 84 | Parameters 85 | ---------- 86 | device : Device instance 87 | 88 | device_mlag_interfaces_status : dict 89 | The EOS structured output of the 'show mlag interfaces' command 90 | 91 | testcase : dict 92 | A specific test-case from the list of all test-cases loaded. 93 | 94 | Raises 95 | ------ 96 | See `nrfu.test_mlag_interface_status` docs for details 97 | """ 98 | nrfu.test_mlag_interface_status( 99 | device=device, 100 | actual=device_mlag_interfaces_status, 101 | testcase=testcase) 102 | -------------------------------------------------------------------------------- /online-demo/test_04_mlag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file provides the pytest framework and test functions used to dynamically 17 | load NRFU MLAG test-cases based on the --nrfu-testcasedir command line option. 18 | This file retrieves the device specific "show" output from a file rather than 19 | an actual device to support a dev-demo environment. 20 | """ 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU MLAG status module so the functions in this file 25 | # can generate the test-case names and invoke the actual NRFU validation 26 | # function. 27 | 28 | import nrfupytesteos.nrfu_mlag_status as nrfu 29 | 30 | 31 | TESTCASES_FILE = f'{nrfu.TEST_CASE_NAME}.json' 32 | 33 | 34 | @pytest.fixture(scope='module') 35 | def device_mlag_status(device): 36 | """ 37 | This fixture is used to return the EOS result of the 'show mlag' command as 38 | structured data. 39 | 40 | Rather than running the specific EOS show command, we are going to load the 41 | previously captured output. For a real-world example, see the file 42 | `online-demo/test_04_mlag_status.py`. 43 | 44 | Parameters 45 | ---------- 46 | device : Device instance 47 | 48 | Returns 49 | ------- 50 | dict 51 | The dictionary output of the 'show mlag' command that was retrieve from 52 | a file rather than the device. 53 | """ 54 | return nrfu.snapshot_testdata(device) 55 | 56 | 57 | def pytest_generate_tests(metafunc): 58 | """ 59 | pytest will invoke this hook allowing us to dynamically load the device 60 | specific MLAG test cases based on the directory the User provided as the 61 | --nrfu-testcasedir command line argument. 62 | 63 | Parameters 64 | ---------- 65 | metafunc : Metafunc instance used to parametrize the test function 66 | """ 67 | 68 | testcases_file = metafunc.config._nrfu.testcases_dir.joinpath( 69 | f'{nrfu.TEST_CASE_NAME}.json') 70 | 71 | if not testcases_file.exists(): 72 | metafunc.parametrize('testcase', [], ids=['no-tests']) 73 | return 74 | 75 | metafunc.parametrize('testcase', 76 | json.load(testcases_file.open()), 77 | ids=nrfu.name_test) 78 | 79 | 80 | def test_mlag_status(device, device_mlag_status, testcase): 81 | """ 82 | pytest will call this function for each test-case item loaded via the 83 | pytest_generate_tests hook function. This function will in turn call the 84 | actual NRFU EOS specific test function `nrfu.test_mlag_status` that will 85 | validate the specific `testcase` against the actual data 86 | `device_mlag_status`. If `nrfu.test_mlag_status` detects a failure it will 87 | raise a specific NRFU exception. The pytest framework will catch that 88 | exception and report the test as failed. 89 | 90 | Parameters 91 | ---------- 92 | device : Device instance 93 | 94 | device_mlag_status : dict 95 | The EOS structured output of the 'show mlag' command 96 | 97 | testcase : dict 98 | A specific test-case from the list of all test-cases loaded. 99 | 100 | Raises 101 | ------ 102 | See `nrfu.test_mlag_status` docs for details 103 | """ 104 | assert nrfu.test_mlag_status( 105 | device=device, 106 | actual=device_mlag_status, 107 | testcase=testcase) 108 | -------------------------------------------------------------------------------- /offline-demo/test_05_mlag_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This file provides the pytest framework and test functions used to dynamically 16 | load NRFU MLAG interface status test-cases based on the --nrfu-testcasedir 17 | command line option. This file retrieves the device specific "show" output from 18 | a file rather than an actual device to support a dev-demo environment. 19 | """ 20 | 21 | import json 22 | import pytest 23 | 24 | # import the EOS specific NRFU MLAG interface status module so the functions in 25 | # this file can generate the test-case names and invoke the actual NRFU 26 | # validation function. 27 | 28 | from nrfupytesteos import nrfu_mlag_interface_status as nrfu 29 | 30 | 31 | @pytest.fixture(scope='module') 32 | def device_mlag_interfaces_status(device): 33 | """ 34 | This fixture is used to return the EOS result of the 'show mlag interfaces' 35 | command as structured data. 36 | 37 | Rather than running the specific EOS show command, we are going to load the 38 | previously captured output. For a real-world example, see the file 39 | `online-demo/test_05_lag_status.py`. 40 | 41 | Parameters 42 | ---------- 43 | device : Device instance 44 | 45 | Returns 46 | ------- 47 | dict 48 | The dictionary output of the 'show mlag interfaces' command that was 49 | retrieve from a file rather than the device. 50 | """ 51 | show_file = device.show_outputs_dir.joinpath( 52 | 'show-mlag-interfaces.json') 53 | 54 | return json.load(show_file.open()) 55 | 56 | 57 | def pytest_generate_tests(metafunc): 58 | """ 59 | pytest will invoke this hook allowing us to dynamically load the device 60 | specific MLAG interface status test cases based on the directory the User 61 | provided as the --nrfu-testcasedir command line argument. 62 | 63 | Parameters 64 | ---------- 65 | metafunc : Metafunc instance used to parametrize the test function 66 | """ 67 | testcases_file = metafunc.config._nrfu['testcase_dir'].joinpath( 68 | 'testcases-mlag-interface-status.json') 69 | 70 | metafunc.parametrize('testcase', 71 | json.load(testcases_file.open()), 72 | ids=nrfu.name_test) 73 | 74 | 75 | def test_mlag_interface_status(device, device_mlag_interfaces_status, testcase): 76 | """ 77 | pytest will call this function for each test-case item loaded via the 78 | pytest_generate_tests hook function. This function will in turn call the 79 | actual NRFU EOS specific test function `nrfu.test_mlag_interface_status` 80 | that will validate the specific `testcase` against the actual data 81 | `device_mlag_interfaces_status`. If `nrfu.test_mlag_interface_status` 82 | detects a failure it will raise a specific NRFU exception. The pytest 83 | framework will catch that exception and report the test as failed. 84 | 85 | Parameters 86 | ---------- 87 | device : Device instance 88 | 89 | device_mlag_interfaces_status : dict 90 | The EOS structured output of the 'show mlag interfaces' command 91 | 92 | testcase : dict 93 | A specific test-case from the list of all test-cases loaded. 94 | 95 | Raises 96 | ------ 97 | See `nrfu.test_mlag_interface_status` docs for details 98 | """ 99 | 100 | nrfu.test_mlag_interface_status(device=device, 101 | actual=device_mlag_interfaces_status, 102 | testcase=testcase) 103 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_bgp_neighbor_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the NRFU test function for BGP neighbor status. 17 | 18 | Examples - testcase 19 | -------- 20 | { 21 | "test-case": "test-bgp-neighbors", 22 | "dut": "ex000102.nyc1", 23 | "params": { 24 | "peer_ip": "192.168.255.1", 25 | "peer_device": "ex050301", 26 | "interface": "Vlan4094", 27 | "peer_asn": "30219", 28 | "peer_role": "mlag-peer", 29 | "bgp_type": "iBGP" 30 | }, 31 | "expected": { 32 | "state": "up" 33 | } 34 | } 35 | 36 | 37 | Examples - "show ip bpg summary" 38 | -------- 39 | { 40 | "vrfs": { 41 | "default": { 42 | "routerId": "10.127.255.5", 43 | "peers": { 44 | "10.115.124.105": { 45 | "description": "\"eBGP with rs32.sccs1-eth2-1\"", 46 | "msgSent": 120528, 47 | "inMsgQueue": 0, 48 | "prefixReceived": 1864, 49 | "upDownTime": 1569593053.967133, 50 | "version": 4, 51 | "msgReceived": 70456, 52 | "prefixAccepted": 1864, 53 | "peerState": "Established", 54 | "outMsgQueue": 0, 55 | "underMaintenance": false, 56 | "asn": "64818" 57 | }, 58 | """ 59 | 60 | from nrfupytesteos import nrfu_exc as exc 61 | 62 | TEST_CASE = "test-bgp-neighbors" 63 | 64 | # def make_testcase(dut, lag_name, interfaces): 65 | # return { 66 | # "test-case": TEST_CASE, 67 | # "dut": dut, 68 | # "params": { 69 | # "name": lag_name 70 | # }, 71 | # "expected": { 72 | # "interfaces": interfaces 73 | # } 74 | # } 75 | # 76 | # 77 | # def snapshot_testdata(device): 78 | # return device.execute('show lacp neighbor') 79 | 80 | 81 | # def snapshot_testcases(device): 82 | # data = snapshot_testdata(device) 83 | # lags = data.get('portChannels') 84 | # 85 | # return [ 86 | # make_testcase(dut=device.hostname, 87 | # lag_name=lag_name, 88 | # interfaces=list(lag_data['interfaces'])) 89 | # for lag_name, lag_data in lags.items() 90 | # ] 91 | 92 | 93 | def name_test(item): 94 | """ used for pytest verbose output """ 95 | p = item['params'] 96 | return f"{p['peer_device']} role={p['role']} via={p['peer_ip']}" 97 | 98 | 99 | def test_bgp_nei_status(device, actual, testcase): 100 | """ 101 | Verifies the operational status of a BGP neighbor. 102 | 103 | Parameters 104 | ---------- 105 | device: Device instance (unused) 106 | 107 | actual: dict 108 | The "show ip bgp summary" dataset 109 | 110 | testcase: dict 111 | The testcase dataset 112 | 113 | Returns 114 | ------- 115 | True when the test passes 116 | 117 | Raises 118 | ------ 119 | MissingError: 120 | When an expected BGP neighbor peer IP is missing 121 | 122 | MismatchError: 123 | When the BGP neighbor is not in the "up" status 124 | """ 125 | peer_ip = testcase['params']['peer_ip'] 126 | actual_peers = actual["vrfs"]['default']['peers'] 127 | 128 | bgp_nei = actual_peers.get(peer_ip) 129 | if not bgp_nei: 130 | raise exc.MissingError(missing=bgp_nei) 131 | 132 | actual_state = bgp_nei["peerState"] 133 | if actual_state != 'Established': 134 | raise exc.MismatchError(expected="Established", actual=actual_state) 135 | 136 | return True 137 | -------------------------------------------------------------------------------- /nrfupytesteos/eos_device.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import re 17 | 18 | from first import first 19 | from paramiko.config import SSHConfig 20 | import pyeapi 21 | 22 | __all__ = ['Device'] 23 | 24 | _if_shorten_find_patterns = [ 25 | r'Ethernet(?P\d+)/1', 26 | r'Ethernet(?P\d+)', 27 | r'Management(?P\d+)', 28 | r'Port-Channel(?P\d+)', 29 | r'Vlan(?P\d+)' 30 | ] 31 | 32 | _if_shorten_replace_patterns = { 33 | 'E1': 'E{}', 34 | 'E2': 'E{}', 35 | 'M1': 'M{}', 36 | 'PO': 'Po{}', 37 | 'V': 'V{}' 38 | } 39 | 40 | _if_shorten_regex = re.compile('|'.join('(%s)' % r for r in _if_shorten_find_patterns)) 41 | 42 | 43 | def _if_shorten_replace_func(mo): 44 | r_name, r_val = first(filter(lambda i: i[1], mo.groupdict().items())) 45 | return _if_shorten_replace_patterns[r_name].format(r_val) 46 | 47 | 48 | def sorted_interfaces(if_list): 49 | match_numbers = re.compile(r"\d+", re.M) 50 | return sorted(if_list, key=lambda i: tuple(map(int, match_numbers.findall(i)))) 51 | 52 | 53 | class Device(object): 54 | """ 55 | An Arista EOS Device class that provides access via the eAPI. 56 | """ 57 | DEFAULT_TRANSPORT = 'https' 58 | 59 | def __init__(self, hostname, username=None, password=None, 60 | transport=None, port=None, 61 | ssh_config_file=None): 62 | 63 | self.hostname = hostname 64 | c_args = dict() 65 | 66 | c_args['username'] = os.getenv('EOS_USER') or os.getenv('USER') or username 67 | c_args['password'] = os.getenv('EOS_PASSWORD') or os.getenv('PASSWORD') or password 68 | 69 | if port: 70 | c_args['port'] = port 71 | 72 | ssh_config_file = ssh_config_file or os.getenv('EOS_SSH_CONFIG') 73 | if ssh_config_file: 74 | ssh_config = SSHConfig() 75 | ssh_config.parse(open(ssh_config_file)) 76 | found = ssh_config.lookup(hostname) 77 | 78 | if 'user' in found: 79 | c_args['username'] = found['user'] 80 | 81 | if 'hostname' in found: 82 | c_args['host'] = found['hostname'] 83 | 84 | if 'localforward' in found: 85 | port = int(first(found['localforward']).split()[0]) 86 | c_args['port'] = port 87 | c_args['host'] = 'localhost' 88 | 89 | else: 90 | c_args['host'] = hostname 91 | c_args['transport'] = transport or self.DEFAULT_TRANSPORT 92 | 93 | self.api = pyeapi.connect(**c_args) 94 | 95 | def probe(self, timeout=5): 96 | _orig_to = self.api.transport.timeout 97 | self.api.transport.timeout = timeout 98 | 99 | try: 100 | self.api.transport.connect() 101 | ok = True 102 | 103 | except Exception: 104 | ok = False 105 | 106 | finally: 107 | self.api.transport.timeout = _orig_to 108 | 109 | return ok 110 | 111 | def execute(self, command, encoding='json'): 112 | """ 113 | Execute an operational command, "show version" for example. 114 | 115 | Parameters 116 | ---------- 117 | command : str - command to execute 118 | 119 | encoding : str 120 | The return format encoding, defaults to 'json'. 121 | 122 | Returns 123 | ------- 124 | dict - results of the command 125 | """ 126 | res = self.api.execute(['enable', command], encoding=encoding) 127 | return res['result'][1] 128 | 129 | @staticmethod 130 | def shorten_if_name(if_name): 131 | return _if_shorten_regex.sub(_if_shorten_replace_func, if_name) 132 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_mlag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file is used to validate the operational state of the MLAG control protocol. The actual 17 | status is obtained from the "show mlag" command (see example JSON output below). For this test-case 18 | to succeed, we will check both the state is "active" and the negotiated status is "connected". 19 | 20 | Examples - Test case 21 | -------- 22 | { 23 | "test-case": "test-mlag-status", 24 | "dut": "lf5e1b01.nyc1", 25 | "params": { 26 | "peer_link": "Port-Channel2000", 27 | "peer_ip": "10.127.253.117", 28 | "vlan_name": "MLAG_CTRLVLAN", 29 | "vlan_id": 4094, 30 | "vlan_ipif": "10.127.253.116/31" 31 | }, 32 | "expected": { 33 | "state": "up" 34 | } 35 | } 36 | 37 | Examples - "show mlag" 38 | -------- 39 | { 40 | "localInterface": "Vlan4094", 41 | "systemId": "76:83:ef:ed:66:5d", 42 | "domainId": "MLAG_CTRLVLAN", 43 | "peerLink": "Port-Channel2000", 44 | "dualPrimaryDetectionState": "disabled", 45 | "localIntfStatus": "up", 46 | "peerLinkStatus": "up", 47 | "peerAddress": "192.168.255.2", 48 | "configSanity": "consistent", 49 | "portsErrdisabled": false, 50 | "state": "active", 51 | "reloadDelay": 600, 52 | "reloadDelayNonMlag": 600, 53 | "negStatus": "connected", 54 | "mlagPorts": { 55 | "Disabled": 0, 56 | "Active-partial": 0, 57 | "Inactive": 18, 58 | "Configured": 0, 59 | "Active-full": 1 60 | } 61 | } 62 | 63 | """ 64 | 65 | from nrfupytesteos import nrfu_exc as exc 66 | 67 | TEST_CASE_NAME = "test-mlag-status" 68 | 69 | 70 | def make_testcase(dut, domain, interface, peer_link, peer_ip): 71 | return { 72 | "test-case": TEST_CASE_NAME, 73 | "dut": dut, 74 | "params": { 75 | "interface": interface, 76 | "peer_link": peer_link, 77 | "peer_ip": peer_ip, 78 | "domain": domain, 79 | }, 80 | "expected": { 81 | "state": "up" 82 | } 83 | } 84 | 85 | 86 | def snapshot_testdata(device): 87 | return device.execute("show mlag") 88 | 89 | 90 | def snapshot_testcases(device): 91 | data = snapshot_testdata(device) 92 | 93 | if data['state'] == 'disabled': 94 | return [] 95 | 96 | return [make_testcase( 97 | dut=device.hostname, 98 | domain=data['domainId'], 99 | interface=data['localInterface'], 100 | peer_link=data['peerLink'], 101 | peer_ip=data['peerAddress'] 102 | )] 103 | 104 | 105 | def name_test(item): 106 | """ used for pytest verbose output """ 107 | return f"{item['params']['peer_link']}" 108 | 109 | 110 | def test_mlag_status(device, actual, testcase): 111 | """ 112 | Verify MLAG control protocol operational status. This test only verifies that 113 | the MLAG control plane is active/connected. 114 | 115 | TODO: add support to check the status to be expected in a down condition. 116 | 117 | Parameters 118 | ---------- 119 | device : Device instance (unused) 120 | actual : dict - EOS device mlag status 121 | testcase : dict - testcase 122 | 123 | Raises 124 | ------- 125 | MismatchError: 126 | When the MLAG control plane status is not active/connected. 127 | 128 | Returns 129 | ------- 130 | True: when testcase passes 131 | """ 132 | has_state = actual['state'] 133 | exp_state = testcase['expected']['state'] 134 | 135 | is_up = (exp_state == 'up' and has_state == 'active') 136 | has_neg_st = actual['negStatus'] 137 | is_neg = has_neg_st == "connected" 138 | 139 | if is_up and is_neg: 140 | return True 141 | 142 | raise exc.MismatchError( 143 | expected=('active', 'connected'), 144 | actual=(has_state, has_neg_st) 145 | ) 146 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_mlag_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the network ready for use test for an MLAG interface status. 17 | 18 | Examples - test case 19 | -------- 20 | { 21 | "test-case": "test-mlag-interface-status", 22 | "dut": "rs2101.dnvr1", 23 | "params": { 24 | "mlag": "302", 25 | "interface": "Port-Channel302", 26 | "peer_interface": "Port-Channel302" 27 | }, 28 | "expected": { 29 | "state": "up" 30 | } 31 | } 32 | 33 | 34 | Examples - "show mlag interfaces" 35 | -------- 36 | { 37 | "interfaces": { 38 | "300": { 39 | "localInterface": "Port-Channel300", 40 | "peerInterface": "Port-Channel300", 41 | "peerInterfaceStatus": "down", 42 | "localInterfaceStatus": "down", 43 | "status": "inactive", 44 | "localInterfaceDescription": "MLB-DD201.MLB.ORG" 45 | }, 46 | """ 47 | 48 | from nrfupytesteos import nrfu_exc as exc 49 | 50 | TEST_CASE_NAME = "test-mlag-interface-status" 51 | 52 | 53 | def make_testcase(dut, mlag, interface, peer_interface=None, state='up'): 54 | return { 55 | "test-case": TEST_CASE_NAME, 56 | "dut": dut, 57 | "params": { 58 | "mlag": mlag, 59 | "interface": interface, 60 | "peer_interface": peer_interface or interface 61 | }, 62 | "expected": { 63 | "state": state 64 | } 65 | } 66 | 67 | 68 | def snapshot_testdata(device): 69 | return device.execute('show mlag interfaces') 70 | 71 | 72 | def snapshot_testcases(device): 73 | data = snapshot_testdata(device) 74 | mlag_interfaces = data.get('interfaces') 75 | 76 | return [ 77 | make_testcase(dut=device.hostname, 78 | mlag=mlag, 79 | interface=mlag_data['localInterface'], 80 | peer_interface=mlag_data['peerInterface'], 81 | state=('up' if mlag_data['status'] == "active-full" 82 | else 'down')) 83 | 84 | for mlag, mlag_data in mlag_interfaces.items() 85 | ] 86 | 87 | 88 | def name_test(item): 89 | """ used for pytest verbose output """ 90 | return f"{item['params']['mlag']}:{item['expected']['state']}" 91 | 92 | 93 | def test_mlag_interface_status(device, actual, testcase): 94 | """ 95 | Verifies the operational status of the MLAG interface. 96 | 97 | Parameters 98 | ---------- 99 | device: Device instance 100 | (unused) 101 | 102 | actual: dict 103 | The result of the "show mlag interfaces" command 104 | 105 | testcase: dict 106 | The test case dataset 107 | 108 | Returns 109 | ------- 110 | bool: 111 | `True` when the test passes, otherwise an exception is raised. 112 | 113 | Raises 114 | ------ 115 | MissingError: 116 | When an expected MLAG is missing 117 | 118 | MismatchError: 119 | When an MLAG is not in the expect "up" or "down" condition. 120 | """ 121 | 122 | mlag_ifs = actual['interfaces'] 123 | 124 | mlag = testcase['params']['mlag'] 125 | mlag_ifstatus = mlag_ifs.get(mlag) 126 | 127 | if not mlag_ifstatus: 128 | raise exc.MissingError( 129 | f"MLAG {mlag} not found", 130 | missing=mlag) 131 | 132 | actual_state = mlag_ifstatus['status'] 133 | expected_state = testcase['expected']['state'] 134 | 135 | # check expected "up" condition 136 | 137 | if expected_state == 'up': 138 | if actual_state != 'active-full': 139 | raise exc.MismatchError( 140 | f'MLAG {mlag} not up as expected', 141 | expected=expected_state, 142 | actual=actual_state 143 | ) 144 | 145 | # if here, then interface is down as expected 146 | return True 147 | 148 | # check expected up state condition 149 | 150 | if actual_state != 'inactive': 151 | raise exc.MismatchError( 152 | f'MLAG {mlag} not down as expected', 153 | expected=expected_state, 154 | actual=actual_state 155 | ) 156 | 157 | return True 158 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_optic_inventory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the NRFU test to verify the optic inventory information for 17 | a given interface. 18 | 19 | The test-case dictionary has the form (example): 20 | 21 | { 22 | "test-case": "test-optic-inventory", 23 | "dut": "ex000102.nyc1", 24 | "params": { 25 | "interface": "Ethernet1", 26 | "role": "extern-inet" 27 | }, 28 | "expected": { 29 | "optic": "SFP-10G-LR-P" 30 | } 31 | } 32 | 33 | If no optic is expected, then set the `optic` value to the empty-string (''). 34 | 35 | The EOS transceiver inventory information is available from the "show 36 | inventory" output in the 'xcvrSlots' dict. This dict has the form: 37 | 38 | key: str - 39 | val: dict as shown in examples below 40 | 41 | 42 | Examples 43 | -------- 44 | "56": { 45 | "modelName": "", # <-- no optic present in interface 46 | "serialNum": "", 47 | "mfgName": "Not Present", 48 | "hardwareRev": "" 49 | } 50 | 51 | "60": { 52 | "modelName": "QSFP28-LR4-100G", 53 | "serialNum": "G1807348871", 54 | "mfgName": "Arista Networks", 55 | "hardwareRev": "01" 56 | } 57 | """ 58 | 59 | 60 | 61 | from nrfupytesteos import nrfu_exc as exc 62 | 63 | TEST_CASE_NAME = "test-optic-inventory" 64 | 65 | 66 | def make_testcase(dut, interface, optic): 67 | return { 68 | "test-case": TEST_CASE_NAME, 69 | "dut": dut, 70 | "params": { 71 | "interface": interface 72 | }, 73 | "expected": { 74 | "optic": optic or '' 75 | } 76 | } 77 | 78 | 79 | def snapshot_testdata(device): 80 | return device.execute('show inventory') 81 | 82 | 83 | def snapshot_testcases(device): 84 | data = snapshot_testdata(device) 85 | 86 | xcvrs = data['xcvrSlots'] 87 | return [ 88 | make_testcase(dut=device.hostname, 89 | interface=port_num, 90 | optic=port_data['modelName']) 91 | for port_num, port_data in xcvrs.items() 92 | ] 93 | 94 | 95 | def name_test(item): 96 | """ used for pytest verbose output """ 97 | return f"{item['params']['interface']}:{item['expected']['optic'] or 'none'}" 98 | 99 | 100 | def test_optic_inventory(device, actual, testcase): 101 | """ 102 | This test will verify that the given interface has the optic type as expected. 103 | If no optic is expected in the interface, then the test-case data should have 104 | the expected optic value set to empty-string. 105 | 106 | Parameters 107 | ---------- 108 | device : Device instance 109 | 110 | actual : dict 111 | output of "show inventory" command 112 | 113 | testcase : dict 114 | test case data 115 | 116 | Returns 117 | ------- 118 | True when test case passes 119 | 120 | Raises 121 | ------- 122 | MissingError: 123 | When requested interface does not show up in the inventory dataset. 124 | 125 | MismatchError: 126 | - When optic found does not match expected value. 127 | """ 128 | xcvrs = actual['xcvrSlots'] 129 | if_name = testcase['params']['interface'] 130 | port_no = device.shorten_if_name(if_name).split('E')[-1] 131 | 132 | xcvr_data = xcvrs.get(port_no) 133 | if not xcvr_data: 134 | raise exc.MissingError( 135 | "Interface not found", 136 | missing=if_name) 137 | 138 | expect_model = testcase['expected']['optic'] 139 | actual_model = xcvr_data['modelName'] 140 | 141 | if actual_model == expect_model: 142 | return True 143 | 144 | # if here then there is a mismatch. if expecting an optic then the error 145 | # message should indicate that the wrong optic is present and if an optic 146 | # is not expected, then the error message should indicate that no optic was 147 | # expected. We don't use an Unexpected exception here because that is 148 | # meant to server as 'unexpected additional information' 149 | 150 | wrong = "Wrong" if actual_model else "No" 151 | err_msg = (f"{wrong} optic found on interface {if_name}" if expect_model 152 | else f"No optic expected, but found on interface {if_name}") 153 | 154 | raise exc.MismatchError( 155 | err_msg, 156 | actual=actual_model, 157 | expected=expect_model) 158 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_cabling.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the NRFU test to verify the interface LLDP neighbor is as 17 | epxected. 18 | 19 | 20 | The test-case dictionary has the form (example): 21 | { 22 | "test-case": "test-cabling", 23 | "dut": "rs2105.dnvr1", 24 | "params": { 25 | "interface": "Ethernet49/1" 26 | }, 27 | "expected": { 28 | "remote-hostname": "rs21.dnvr1", 29 | "remote-interface": "Ethernet3/1" 30 | } 31 | } 32 | 33 | The EOS LLDP neighbor information is available from the "show lldp neighbors" 34 | output in the 'interfacesStatuses' dict. This dict has the form as shown 35 | below. 36 | 37 | Examples 38 | -------- 39 | { 40 | "lldpNeighbors": [ 41 | { 42 | "ttl": 120, 43 | "neighborDevice": "switch-21.bld1", 44 | "neighborPort": "Ethernet3/1", 45 | "port": "Ethernet49/1" 46 | }, 47 | { 48 | "ttl": 120, 49 | "neighborDevice": "switch-22.bld1", 50 | "neighborPort": "Ethernet3/1", 51 | "port": "Ethernet50/1" 52 | }, 53 | { 54 | "ttl": 120, 55 | "neighborDevice": "switch-2106.bld1", 56 | "neighborPort": "Ethernet59/1", 57 | "port": "Ethernet59/1" 58 | }, 59 | { 60 | "ttl": 120, 61 | "neighborDevice": "switch-2106.bld1", 62 | "neighborPort": "Ethernet60/1", 63 | "port": "Ethernet60/1" 64 | } 65 | ] 66 | } 67 | """ 68 | 69 | from first import first 70 | from nrfupytesteos import nrfu_exc as exc 71 | 72 | TEST_CASE_NAME = 'test-cabling' 73 | 74 | 75 | def make_testcase(dut, interface, remote_host, remote_interface, role='role=na', **extra_params): 76 | tc = { 77 | "test-case": TEST_CASE_NAME, 78 | "dut": dut, 79 | "params": { 80 | "interface": interface, 81 | "role": role, 82 | }, 83 | "expected": { 84 | "remote-hostname": remote_host, 85 | "remote-interface": remote_interface 86 | } 87 | } 88 | tc.update(**extra_params) 89 | return tc 90 | 91 | 92 | def snapshot_testdata(device): 93 | return device.execute("show lldp neighbors") 94 | 95 | 96 | def snapshot_testcases(device): 97 | data = snapshot_testdata(device) 98 | lldp_nbrs = data['lldpNeighbors'] 99 | 100 | return [make_testcase(dut=device.hostname, 101 | interface=entry['port'], 102 | remote_host=entry['neighborDevice'], 103 | remote_interface=entry['neighborPort']) 104 | for entry in lldp_nbrs] 105 | 106 | 107 | def name_test(item): 108 | """ used for pytest verbose output """ 109 | rmt_host = item['expected']['remote-hostname'] 110 | rmt_ifn = item['expected']['remote-interface'] 111 | return f"{item['params']['interface']}<[{item['params']['role']}]->{rmt_host}:{rmt_ifn}" 112 | 113 | 114 | def test_cabling(device, actual, testcase): 115 | """ 116 | This function will return a tuple (bool, str) to indicate 117 | if the testcase passes or fails. 118 | 119 | Parameters 120 | ---------- 121 | device : Device instance (unused) 122 | actual : dict - EOS device lldp neighbors data (all interfaces) 123 | testcase : dict - testcase 124 | 125 | Returns 126 | ------- 127 | True: when test case passes 128 | 129 | Raises 130 | ------- 131 | MissingError: 132 | When the requested interface does not exist in the dataset 133 | 134 | MismatchError: 135 | When either no neighbor is found, or 136 | the wrong neighbor is found. 137 | """ 138 | lldp_nbrs = actual['lldpNeighbors'] 139 | if_name = testcase['params']['interface'] 140 | 141 | lldp_if_nbr = first(nei for nei in lldp_nbrs if nei['port'] == if_name) 142 | if not lldp_if_nbr: 143 | raise exc.MissingError( 144 | f"Interface not found", 145 | missing=if_name) 146 | 147 | actual_rmt_dev = lldp_if_nbr['neighborDevice'] 148 | actual_rmt_ifn = lldp_if_nbr['neighborPort'] 149 | 150 | expect_rmt_dev = testcase['expected']['remote-hostname'] 151 | expect_rmt_ifn = testcase['expected']['remote-interface'] 152 | 153 | emsg = [] 154 | 155 | if actual_rmt_dev.lower() != expect_rmt_dev.lower(): 156 | emsg.append(f"Wrong remote-device: {actual_rmt_dev}") 157 | 158 | if actual_rmt_ifn.lower() != expect_rmt_ifn.lower(): 159 | emsg.append(f"Wrong remote-interface: {actual_rmt_ifn}") 160 | 161 | if emsg: 162 | raise exc.MismatchError( 163 | ', '.join(emsg), 164 | expected=f"{expect_rmt_dev}:{expect_rmt_ifn}", 165 | actual=f"{actual_rmt_dev}:{actual_rmt_ifn}" 166 | ) 167 | 168 | return True 169 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_lag_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the NRFU test function for LAG interfaces. It ensures that only the interfaces 17 | that are defined in the test case are actually present on the device. 18 | 19 | Examples - testcase 20 | -------- 21 | { 22 | "test-case": "test-lag-status", 23 | "dut": "ob050301.nyc1", 24 | "params": { 25 | "name": "Port-Channel2000" 26 | }, 27 | "expected": { 28 | "interfaces": [ 29 | "Ethernet51", 30 | "Ethernet52" 31 | ] 32 | } 33 | } 34 | 35 | 36 | Examples - "show lacp neighbor" 37 | -------- 38 | { 39 | "portChannels": { 40 | "Port-Channel2000": { 41 | "interfaces": { 42 | "Ethernet51": { 43 | "partnerPortPriority": 32768, 44 | "partnerPortState": { 45 | "collecting": true, 46 | "distributing": true, 47 | "synchronization": true, 48 | "defaulted": false, 49 | "timeout": false, 50 | "activity": true, 51 | "expired": false, 52 | "aggregation": true 53 | }, 54 | "partnerSystemId": "8000,76-83-ef-ed-66-5d", 55 | "partnerOperKey": "0x000a", 56 | "actorPortStatus": "bundled", 57 | "partnerPortId": 15 58 | }, 59 | """ 60 | 61 | 62 | from nrfupytesteos import nrfu_exc as exc 63 | 64 | TEST_CASE_NAME = "test-lag-status" 65 | 66 | 67 | def make_testcase(dut, lag_name, interfaces): 68 | return { 69 | "test-case": TEST_CASE_NAME, 70 | "dut": dut, 71 | "params": { 72 | "name": lag_name 73 | }, 74 | "expected": { 75 | "interfaces": interfaces 76 | } 77 | } 78 | 79 | 80 | def snapshot_testdata(device): 81 | return device.execute('show lacp neighbor') 82 | 83 | 84 | def snapshot_testcases(device): 85 | data = snapshot_testdata(device) 86 | lags = data.get('portChannels') 87 | 88 | return [ 89 | make_testcase(dut=device.hostname, 90 | lag_name=lag_name, 91 | interfaces=list(lag_data['interfaces'])) 92 | for lag_name, lag_data in lags.items() 93 | ] 94 | 95 | 96 | def name_test(item): 97 | """ used for pytest verbose output """ 98 | return f"{item['params']['name']}" 99 | 100 | 101 | def test_lag_status(device, actual, testcase): 102 | """ 103 | Verifies the operational status of the LAG. 104 | 105 | Parameters 106 | ---------- 107 | device: Device instance (unused) 108 | 109 | actual: dict 110 | The "show lacp neighbor" dataset 111 | 112 | testcase: dict 113 | The testcase dataset 114 | 115 | Returns 116 | ------- 117 | True when the test passes 118 | 119 | Raises 120 | ------ 121 | MissingError: 122 | When an expected interface is missing 123 | 124 | UnexpectedError: 125 | When an interface is present that does not belong 126 | 127 | MismatchError: 128 | When an interface is not in the "good" status 129 | """ 130 | lag_name = testcase['params']['name'] 131 | actual_lag = actual['portChannels'].get(lag_name) 132 | if not actual_lag: 133 | raise exc.MissingError(missing=lag_name) 134 | 135 | actual_if_names = set(actual_lag['interfaces']) 136 | exp_if_names = set(testcase['expected']['interfaces']) 137 | 138 | # first see if there are any missing interfaces, 139 | # if so raise a mismatch error. 140 | 141 | missing_if_names = exp_if_names - actual_if_names 142 | if missing_if_names: 143 | raise exc.MismatchError( 144 | expected=exp_if_names, 145 | actual=actual_if_names) 146 | 147 | # next check to see if there are any interfaces that should not be here 148 | 149 | unexp_if_names = actual_if_names - exp_if_names 150 | if unexp_if_names: 151 | raise exc.UnexpectedError(unexpected=unexp_if_names) 152 | 153 | # now for each interface, ensure that it is in the "good" state, which is 154 | # "bundled" 155 | 156 | if not actual_lag['interfaces']: 157 | raise exc.MismatchError( 158 | 'No interfaces found in LAG', 159 | expected=exp_if_names, 160 | actual="" 161 | ) 162 | 163 | for if_name, if_data in actual_lag['interfaces'].items(): 164 | port_status = if_data["actorPortStatus"] 165 | if port_status != "bundled": 166 | raise exc.MismatchError( 167 | expected='bundled', 168 | actual=port_status 169 | ) 170 | 171 | return True 172 | -------------------------------------------------------------------------------- /nrfupytesteos/nrfu_interface_status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jeremy Schulman, nwkautomaniac@gmail.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This file contains the NRFU test to verify the interface in the expected 17 | administrative state and operational state. 18 | 19 | 20 | The test-case dictionary has the form (example): 21 | { 22 | "test-case": "test-interface-status", 23 | "dut": "ex000102.nyc1", 24 | "params": { 25 | "interface": "Ethernet1", 26 | "role": "extern-inet" 27 | }, 28 | "expected": { 29 | "state": "down" 30 | } 31 | } 32 | 33 | The EOS interface information is available from the "show interfaces" 34 | output. This dict has the form as shown below. 35 | 36 | If the expected state is 'down', then the test should look for the 'linkStatus' 37 | equal to 'disabled' 38 | 39 | If the expected state is 'up', then the test should look for the 'linkStatus' equal 40 | to "connected" 41 | 42 | Examples 43 | -------- 44 | { 45 | "interfaces": { 46 | "Ethernet10": { 47 | "interfaceStatistics": { 48 | "inBitsRate": 0.0, 49 | "inPktsRate": 0.0, 50 | "outBitsRate": 0.0, 51 | "updateInterval": 300.0, 52 | "outPktsRate": 0.0 53 | }, 54 | "lanes": 0, 55 | "name": "Ethernet10", 56 | "interfaceStatus": "notconnect", 57 | "autoNegotiate": "off", 58 | "burnedInAddress": "74:83:ef:ed:66:67", 59 | "loopbackMode": "loopbackNone", 60 | "mtu": 10178, 61 | "hardware": "ethernet", 62 | "duplex": "duplexFull", 63 | "bandwidth": 10000000000, 64 | "forwardingModel": "dataLink", 65 | "lineProtocolStatus": "notPresent", 66 | "interfaceCounters": { 67 | "outBroadcastPkts": 0, 68 | "outUcastPkts": 0, 69 | "lastClear": 1562933487.5561786, 70 | "inMulticastPkts": 0, 71 | "counterRefreshTime": 1562940525.192617, 72 | "inBroadcastPkts": 0, 73 | "outputErrorsDetail": { 74 | "deferredTransmissions": 0, 75 | "txPause": 0, 76 | "collisions": 0, 77 | "lateCollisions": 0 78 | }, 79 | "inOctets": 0, 80 | "outDiscards": 0, 81 | "outOctets": 0, 82 | "inUcastPkts": 0, 83 | "inTotalPkts": 0, 84 | "inputErrorsDetail": { 85 | "runtFrames": 0, 86 | "rxPause": 0, 87 | "fcsErrors": 0, 88 | "alignmentErrors": 0, 89 | "giantFrames": 0, 90 | "symbolErrors": 0 91 | }, 92 | "linkStatusChanges": 0, 93 | "outMulticastPkts": 0, 94 | "totalInErrors": 0, 95 | "inDiscards": 0, 96 | "totalOutErrors": 0 97 | }, 98 | "interfaceMembership": "Member of Port-Channel16", 99 | "interfaceAddress": [], 100 | "physicalAddress": "74:83:ef:ed:66:67", 101 | "description": "\"ob000101.nyc1-E51\"" 102 | } 103 | } 104 | } 105 | """ 106 | 107 | 108 | 109 | from nrfupytesteos import nrfu_exc as exc 110 | 111 | TEST_CASE_NAME = "test-interface-status" 112 | 113 | 114 | def make_testcase(dut, interface, state): 115 | return { 116 | "test-case": TEST_CASE_NAME, 117 | "dut": dut, 118 | "params": { 119 | "interface": interface 120 | }, 121 | "expected": { 122 | "state": state 123 | } 124 | } 125 | 126 | 127 | def snapshot_testdata(device): 128 | return device.execute('show interfaces') 129 | 130 | 131 | def snapshot_testcases(device): 132 | data = snapshot_testdata(device) 133 | 134 | return [ 135 | make_testcase(dut=device.hostname, 136 | interface=if_name, 137 | state=( 138 | 'up' if if_data['interfaceStatus'] == 'connected' 139 | else 'down')) 140 | 141 | for if_name, if_data in data['interfaces'].items() 142 | ] 143 | 144 | 145 | def name_test(item): 146 | """ used for pytest verbose output """ 147 | return f"{item['params']['interface']}:{item['expected']['state']}" 148 | 149 | 150 | def test_interface_status(device, actual, testcase): 151 | """ 152 | This function will return a tuple (bool, str) to indicate 153 | if the testcase passes or fails. 154 | 155 | Parameters 156 | ---------- 157 | device : Device instance 158 | 159 | actual : dict 160 | EOS device interfaces data as shown in the file comments 161 | 162 | testcase : dict - testcase 163 | 164 | 165 | Returns 166 | ------- 167 | True: when testcase passes 168 | 169 | Raises 170 | ------ 171 | MissingError: 172 | When the requested interface does not exist in the actual dataset 173 | 174 | MismatchError: 175 | When the interface is not the expected state (up/down) 176 | """ 177 | status = actual['interfaces'] 178 | if_name = testcase['params']['interface'] 179 | if_status = status.get(if_name) 180 | 181 | if not if_status: 182 | raise exc.MissingError( 183 | 'No status for interface', 184 | missing=if_name) 185 | 186 | actual_state = if_status['interfaceStatus'] 187 | expected_state = testcase['expected']['state'] 188 | 189 | # check expected down state condition 190 | 191 | if expected_state == 'down': 192 | if actual_state != 'disabled': 193 | raise exc.MismatchError( 194 | f'Interface {if_name} not down as expected', 195 | expected=expected_state, 196 | actual=actual_state 197 | ) 198 | 199 | # if here, then interface is down as expected 200 | return True 201 | 202 | # check expected up state condition 203 | 204 | if actual_state != 'connected': 205 | raise exc.MismatchError( 206 | f'Interface {if_name} not up as expected', 207 | expected=expected_state, 208 | actual=actual_state 209 | ) 210 | 211 | return True 212 | -------------------------------------------------------------------------------- /offline-demo/dev1-testcases/testcases-interface-status.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test-case": "test-interface-status", 4 | "dut": "dev1", 5 | "params": { 6 | "interface": "Ethernet1", 7 | "ctype": "smf", 8 | "optic": "SFP-10G-LR", 9 | "ptype": "sfp", 10 | "role": "pair-peer", 11 | "speed": 10, 12 | "lag": "Port-Channel2000" 13 | }, 14 | "expected": { 15 | "state": "up" 16 | } 17 | }, 18 | { 19 | "test-case": "test-interface-status", 20 | "dut": "dev1", 21 | "params": { 22 | "interface": "Ethernet15", 23 | "ctype": "smf", 24 | "optic": "SFP-10G-LR", 25 | "ptype": "sfp", 26 | "role": "leaf-access", 27 | "speed": 10, 28 | "lag": "Port-Channel10" 29 | }, 30 | "expected": { 31 | "state": "up" 32 | } 33 | }, 34 | { 35 | "test-case": "test-interface-status", 36 | "dut": "dev1", 37 | "params": { 38 | "interface": "Ethernet2", 39 | "ctype": "smf", 40 | "optic": "SFP-10G-LR", 41 | "ptype": "sfp", 42 | "role": "pair-peer", 43 | "speed": 10, 44 | "lag": "Port-Channel2000" 45 | }, 46 | "expected": { 47 | "state": "up" 48 | } 49 | }, 50 | 51 | { 52 | "test-case": "test-interface-status", 53 | "dut": "dev1", 54 | "params": { 55 | "interface": "Ethernet29", 56 | "ctype": "smf", 57 | "optic": "SFP-10G-LR", 58 | "ptype": "sfp", 59 | "role": "leaf-access", 60 | "speed": 10, 61 | "unused": true 62 | }, 63 | "expected": { 64 | "state": "down" 65 | } 66 | }, 67 | { 68 | "test-case": "test-interface-status", 69 | "dut": "dev1", 70 | "params": { 71 | "interface": "Ethernet30", 72 | "ctype": "smf", 73 | "optic": "SFP-10G-LR", 74 | "ptype": "sfp", 75 | "role": "leaf-access", 76 | "speed": 10, 77 | "unused": true 78 | }, 79 | "expected": { 80 | "state": "down" 81 | } 82 | }, 83 | { 84 | "test-case": "test-interface-status", 85 | "dut": "dev1", 86 | "params": { 87 | "interface": "Ethernet31", 88 | "ctype": "smf", 89 | "optic": "SFP-10G-LR", 90 | "ptype": "sfp", 91 | "role": "leaf-access", 92 | "speed": 10, 93 | "unused": true 94 | }, 95 | "expected": { 96 | "state": "down" 97 | } 98 | }, 99 | { 100 | "test-case": "test-interface-status", 101 | "dut": "dev1", 102 | "params": { 103 | "interface": "Ethernet32", 104 | "ctype": "smf", 105 | "optic": "SFP-10G-LR", 106 | "ptype": "sfp", 107 | "role": "leaf-access", 108 | "speed": 10, 109 | "unused": true 110 | }, 111 | "expected": { 112 | "state": "down" 113 | } 114 | }, 115 | { 116 | "test-case": "test-interface-status", 117 | "dut": "dev1", 118 | "params": { 119 | "interface": "Ethernet33/1", 120 | "ptype": "qsfp", 121 | "unused": true 122 | }, 123 | "expected": { 124 | "state": "down" 125 | } 126 | }, 127 | { 128 | "test-case": "test-interface-status", 129 | "dut": "dev1", 130 | "params": { 131 | "interface": "Ethernet34/1", 132 | "ptype": "qsfp", 133 | "unused": true 134 | }, 135 | "expected": { 136 | "state": "down" 137 | } 138 | }, 139 | { 140 | "test-case": "test-interface-status", 141 | "dut": "dev1", 142 | "params": { 143 | "interface": "Ethernet4", 144 | "ctype": "cat6", 145 | "optic": "SFP-1G-T", 146 | "ptype": "sfp", 147 | "role": "oob-meraki", 148 | "speed": 1, 149 | "switchport_mode": "trunk", 150 | "switchport_native_vlan": "OOBMGMT", 151 | "trunk-all": true, 152 | "vlans": [ 153 | "OOBMGMT" 154 | ] 155 | }, 156 | "expected": { 157 | "state": "up" 158 | } 159 | }, 160 | { 161 | "test-case": "test-interface-status", 162 | "dut": "dev1", 163 | "params": { 164 | "interface": "Ethernet6", 165 | "ptype": "sfp", 166 | "unused": true 167 | }, 168 | "expected": { 169 | "state": "down" 170 | } 171 | }, 172 | { 173 | "test-case": "test-interface-status", 174 | "dut": "dev1", 175 | "params": { 176 | "interface": "Ethernet7", 177 | "ptype": "sfp", 178 | "unused": true 179 | }, 180 | "expected": { 181 | "state": "down" 182 | } 183 | }, 184 | { 185 | "test-case": "test-interface-status", 186 | "dut": "dev1", 187 | "params": { 188 | "interface": "Ethernet8", 189 | "ptype": "sfp", 190 | "unused": true 191 | }, 192 | "expected": { 193 | "state": "down" 194 | } 195 | }, 196 | { 197 | "test-case": "test-interface-status", 198 | "dut": "dev1", 199 | "params": { 200 | "interface": "Ethernet9", 201 | "ptype": "sfp", 202 | "unused": true 203 | }, 204 | "expected": { 205 | "state": "down" 206 | } 207 | }, 208 | { 209 | "test-case": "test-interface-status", 210 | "dut": "dev1", 211 | "params": { 212 | "interface": "Loopback0", 213 | "ptype": "loopback", 214 | "role": "loopback", 215 | "ip_addrs": [ 216 | { 217 | "name": "10.127.255.87/32", 218 | "role": "loopback", 219 | "rt": "global", 220 | "version": 4 221 | } 222 | ] 223 | }, 224 | "expected": { 225 | "state": "up" 226 | } 227 | }, 228 | { 229 | "test-case": "test-interface-status", 230 | "dut": "dev1", 231 | "params": { 232 | "interface": "Management1", 233 | "ctype": "cat6", 234 | "optic": "none", 235 | "ptype": "rj45", 236 | "role": "management", 237 | "ip_addrs": [ 238 | { 239 | "name": "172.30.51.23/23", 240 | "role": "management", 241 | "rt": "global", 242 | "version": 4 243 | } 244 | ] 245 | }, 246 | "expected": { 247 | "state": "up" 248 | } 249 | }, 250 | { 251 | "test-case": "test-interface-status", 252 | "dut": "dev1", 253 | "params": { 254 | "interface": "Port-Channel10", 255 | "mlag": true, 256 | "pool": "leaf-access", 257 | "ptype": "lag", 258 | "switchport_mode": "trunk" 259 | }, 260 | "expected": { 261 | "state": "up" 262 | } 263 | }, 264 | { 265 | "test-case": "test-interface-status", 266 | "dut": "dev1", 267 | "params": { 268 | "interface": "Vlan4094", 269 | "ptype": "vlan", 270 | "role": "mlag-peer", 271 | "vlan_id": 4094, 272 | "ip_addrs": [ 273 | { 274 | "link": true, 275 | "name": "10.127.253.147/31", 276 | "role": "mlag-peer", 277 | "rt": "global", 278 | "version": 4 279 | } 280 | ] 281 | }, 282 | "expected": { 283 | "state": "up" 284 | } 285 | }, 286 | { 287 | "test-case": "test-interface-status", 288 | "dut": "dev1", 289 | "params": { 290 | "interface": "Port-Channel2000", 291 | "ptype": "lag", 292 | "role": "pair-peer", 293 | "vlans": [ 294 | "MLAG_CTRLVLAN" 295 | ], 296 | "lacp-mode": "active" 297 | }, 298 | "expected": { 299 | "state": "up" 300 | } 301 | } 302 | ] -------------------------------------------------------------------------------- /offline-demo/dev1-show-outputs/show-inventory.json: -------------------------------------------------------------------------------- 1 | { 2 | "fpgas": {}, 3 | "storageDevices": {}, 4 | "xcvrSlots": { 5 | "56": { 6 | "modelName": "", 7 | "serialNum": "", 8 | "mfgName": "Not Present", 9 | "hardwareRev": "" 10 | }, 11 | "28": { 12 | "modelName": "", 13 | "serialNum": "", 14 | "mfgName": "Not Present", 15 | "hardwareRev": "" 16 | }, 17 | "29": { 18 | "modelName": "", 19 | "serialNum": "", 20 | "mfgName": "Not Present", 21 | "hardwareRev": "" 22 | }, 23 | "60": { 24 | "modelName": "QSFP28-LR4-100G", 25 | "serialNum": "G1807348871", 26 | "mfgName": "Arista Networks", 27 | "hardwareRev": "01" 28 | }, 29 | "35": { 30 | "modelName": "", 31 | "serialNum": "", 32 | "mfgName": "Not Present", 33 | "hardwareRev": "" 34 | }, 35 | "32": { 36 | "modelName": "", 37 | "serialNum": "", 38 | "mfgName": "Not Present", 39 | "hardwareRev": "" 40 | }, 41 | "24": { 42 | "modelName": "", 43 | "serialNum": "", 44 | "mfgName": "Not Present", 45 | "hardwareRev": "" 46 | }, 47 | "25": { 48 | "modelName": "", 49 | "serialNum": "", 50 | "mfgName": "Not Present", 51 | "hardwareRev": "" 52 | }, 53 | "26": { 54 | "modelName": "", 55 | "serialNum": "", 56 | "mfgName": "Not Present", 57 | "hardwareRev": "" 58 | }, 59 | "27": { 60 | "modelName": "", 61 | "serialNum": "", 62 | "mfgName": "Not Present", 63 | "hardwareRev": "" 64 | }, 65 | "20": { 66 | "modelName": "", 67 | "serialNum": "", 68 | "mfgName": "Not Present", 69 | "hardwareRev": "" 70 | }, 71 | "21": { 72 | "modelName": "", 73 | "serialNum": "", 74 | "mfgName": "Not Present", 75 | "hardwareRev": "" 76 | }, 77 | "22": { 78 | "modelName": "", 79 | "serialNum": "", 80 | "mfgName": "Not Present", 81 | "hardwareRev": "" 82 | }, 83 | "23": { 84 | "modelName": "", 85 | "serialNum": "", 86 | "mfgName": "Not Present", 87 | "hardwareRev": "" 88 | }, 89 | "46": { 90 | "modelName": "", 91 | "serialNum": "", 92 | "mfgName": "Not Present", 93 | "hardwareRev": "" 94 | }, 95 | "47": { 96 | "modelName": "", 97 | "serialNum": "", 98 | "mfgName": "Not Present", 99 | "hardwareRev": "" 100 | }, 101 | "44": { 102 | "modelName": "", 103 | "serialNum": "", 104 | "mfgName": "Not Present", 105 | "hardwareRev": "" 106 | }, 107 | "45": { 108 | "modelName": "", 109 | "serialNum": "", 110 | "mfgName": "Not Present", 111 | "hardwareRev": "" 112 | }, 113 | "42": { 114 | "modelName": "", 115 | "serialNum": "", 116 | "mfgName": "Not Present", 117 | "hardwareRev": "" 118 | }, 119 | "43": { 120 | "modelName": "", 121 | "serialNum": "", 122 | "mfgName": "Not Present", 123 | "hardwareRev": "" 124 | }, 125 | "40": { 126 | "modelName": "", 127 | "serialNum": "", 128 | "mfgName": "Not Present", 129 | "hardwareRev": "" 130 | }, 131 | "41": { 132 | "modelName": "", 133 | "serialNum": "", 134 | "mfgName": "Not Present", 135 | "hardwareRev": "" 136 | }, 137 | "1": { 138 | "modelName": "", 139 | "serialNum": "", 140 | "mfgName": "Not Present", 141 | "hardwareRev": "" 142 | }, 143 | "3": { 144 | "modelName": "", 145 | "serialNum": "", 146 | "mfgName": "Not Present", 147 | "hardwareRev": "" 148 | }, 149 | "2": { 150 | "modelName": "", 151 | "serialNum": "", 152 | "mfgName": "Not Present", 153 | "hardwareRev": "" 154 | }, 155 | "5": { 156 | "modelName": "", 157 | "serialNum": "", 158 | "mfgName": "Not Present", 159 | "hardwareRev": "" 160 | }, 161 | "4": { 162 | "modelName": "", 163 | "serialNum": "", 164 | "mfgName": "Not Present", 165 | "hardwareRev": "" 166 | }, 167 | "7": { 168 | "modelName": "", 169 | "serialNum": "", 170 | "mfgName": "Not Present", 171 | "hardwareRev": "" 172 | }, 173 | "6": { 174 | "modelName": "", 175 | "serialNum": "", 176 | "mfgName": "Not Present", 177 | "hardwareRev": "" 178 | }, 179 | "9": { 180 | "modelName": "", 181 | "serialNum": "", 182 | "mfgName": "Not Present", 183 | "hardwareRev": "" 184 | }, 185 | "8": { 186 | "modelName": "", 187 | "serialNum": "", 188 | "mfgName": "Not Present", 189 | "hardwareRev": "" 190 | }, 191 | "18": { 192 | "modelName": "", 193 | "serialNum": "", 194 | "mfgName": "Not Present", 195 | "hardwareRev": "" 196 | }, 197 | "13": { 198 | "modelName": "", 199 | "serialNum": "", 200 | "mfgName": "Not Present", 201 | "hardwareRev": "" 202 | }, 203 | "34": { 204 | "modelName": "", 205 | "serialNum": "", 206 | "mfgName": "Not Present", 207 | "hardwareRev": "" 208 | }, 209 | "38": { 210 | "modelName": "", 211 | "serialNum": "", 212 | "mfgName": "Not Present", 213 | "hardwareRev": "" 214 | }, 215 | "30": { 216 | "modelName": "", 217 | "serialNum": "", 218 | "mfgName": "Not Present", 219 | "hardwareRev": "" 220 | }, 221 | "15": { 222 | "modelName": "", 223 | "serialNum": "", 224 | "mfgName": "Not Present", 225 | "hardwareRev": "" 226 | }, 227 | "14": { 228 | "modelName": "", 229 | "serialNum": "", 230 | "mfgName": "Not Present", 231 | "hardwareRev": "" 232 | }, 233 | "11": { 234 | "modelName": "", 235 | "serialNum": "", 236 | "mfgName": "Not Present", 237 | "hardwareRev": "" 238 | }, 239 | "10": { 240 | "modelName": "", 241 | "serialNum": "", 242 | "mfgName": "Not Present", 243 | "hardwareRev": "" 244 | }, 245 | "39": { 246 | "modelName": "", 247 | "serialNum": "", 248 | "mfgName": "Not Present", 249 | "hardwareRev": "" 250 | }, 251 | "12": { 252 | "modelName": "", 253 | "serialNum": "", 254 | "mfgName": "Not Present", 255 | "hardwareRev": "" 256 | }, 257 | "59": { 258 | "modelName": "", 259 | "serialNum": "G1807348750", 260 | "mfgName": "Arista Networks", 261 | "hardwareRev": "01" 262 | }, 263 | "58": { 264 | "modelName": "", 265 | "serialNum": "", 266 | "mfgName": "Not Present", 267 | "hardwareRev": "" 268 | }, 269 | "17": { 270 | "modelName": "", 271 | "serialNum": "", 272 | "mfgName": "Not Present", 273 | "hardwareRev": "" 274 | }, 275 | "16": { 276 | "modelName": "", 277 | "serialNum": "", 278 | "mfgName": "Not Present", 279 | "hardwareRev": "" 280 | }, 281 | "19": { 282 | "modelName": "", 283 | "serialNum": "", 284 | "mfgName": "Not Present", 285 | "hardwareRev": "" 286 | }, 287 | "54": { 288 | "modelName": "", 289 | "serialNum": "", 290 | "mfgName": "Not Present", 291 | "hardwareRev": "" 292 | }, 293 | "31": { 294 | "modelName": "", 295 | "serialNum": "", 296 | "mfgName": "Not Present", 297 | "hardwareRev": "" 298 | }, 299 | "49": { 300 | "modelName": "QSFP28-LR4-100G", 301 | "serialNum": "G1807348872", 302 | "mfgName": "Arista Networks", 303 | "hardwareRev": "01" 304 | }, 305 | "51": { 306 | "modelName": "", 307 | "serialNum": "", 308 | "mfgName": "Not Present", 309 | "hardwareRev": "" 310 | }, 311 | "36": { 312 | "modelName": "", 313 | "serialNum": "", 314 | "mfgName": "Not Present", 315 | "hardwareRev": "" 316 | }, 317 | "53": { 318 | "modelName": "", 319 | "serialNum": "", 320 | "mfgName": "Not Present", 321 | "hardwareRev": "" 322 | }, 323 | "52": { 324 | "modelName": "", 325 | "serialNum": "", 326 | "mfgName": "Not Present", 327 | "hardwareRev": "" 328 | }, 329 | "33": { 330 | "modelName": "", 331 | "serialNum": "", 332 | "mfgName": "Not Present", 333 | "hardwareRev": "" 334 | }, 335 | "55": { 336 | "modelName": "", 337 | "serialNum": "", 338 | "mfgName": "Not Present", 339 | "hardwareRev": "" 340 | }, 341 | "37": { 342 | "modelName": "", 343 | "serialNum": "", 344 | "mfgName": "Not Present", 345 | "hardwareRev": "" 346 | }, 347 | "48": { 348 | "modelName": "", 349 | "serialNum": "", 350 | "mfgName": "Not Present", 351 | "hardwareRev": "" 352 | }, 353 | "57": { 354 | "modelName": "", 355 | "serialNum": "", 356 | "mfgName": "Not Present", 357 | "hardwareRev": "" 358 | }, 359 | "50": { 360 | "modelName": "QSFP28-LR4-100G", 361 | "serialNum": "G1807348758", 362 | "mfgName": "Arista Networks", 363 | "hardwareRev": "01" 364 | } 365 | }, 366 | "portCount": 97, 367 | "switchedBootstrapPortCount": 4, 368 | "managementPortCount": 1, 369 | "dataLinkPortCount": 0, 370 | "emmcFlashDevices": { 371 | "Toshiba 008G70": { 372 | "serialNum": "d163b950", 373 | "storageSize": 7, 374 | "firmwareRev": "0.0" 375 | } 376 | }, 377 | "cardSlots": {}, 378 | "internalPortCount": 0, 379 | "powerSupplySlots": { 380 | "1": { 381 | "name": "PWR-500AC-F", 382 | "serialNum": "DDQT91A1UTU" 383 | }, 384 | "2": { 385 | "name": "PWR-500AC-F", 386 | "serialNum": "DDQT91A1UJK" 387 | } 388 | }, 389 | "fanTraySlots": { 390 | "1": { 391 | "serialNum": "N/A", 392 | "numFans": 1, 393 | "name": "FAN-7000H-F" 394 | }, 395 | "3": { 396 | "serialNum": "N/A", 397 | "numFans": 1, 398 | "name": "FAN-7000H-F" 399 | }, 400 | "2": { 401 | "serialNum": "N/A", 402 | "numFans": 1, 403 | "name": "FAN-7000H-F" 404 | }, 405 | "4": { 406 | "serialNum": "N/A", 407 | "numFans": 1, 408 | "name": "FAN-7000H-F" 409 | } 410 | }, 411 | "systemInformation": { 412 | "name": "DCS-7050SX3-48YC12", 413 | "description": "48x25GbE SFP + 12x100GbE QSFP Switch", 414 | "mfgDate": "2019-03-23", 415 | "hardwareRev": "11.04", 416 | "hwEpoch": "01.00", 417 | "serialNum": "JPE18524048" 418 | }, 419 | "unconnectedPortCount": 0, 420 | "switchedPortCount": 92, 421 | "switchedFortyGOnlyPortCount": 0 422 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /offline-demo/reports/dev1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test Report 6 | 170 | 171 | 406 |

dev1.html

407 |

Report generated on 13-Oct-2019 at 15:06:27 by pytest-html v2.0.0

408 |

Environment

409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 |
Packages{'pytest': '5.2.1', 'py': '1.8.0', 'pluggy': '0.13.0'}
PlatformDarwin-18.7.0-x86_64-i386-64bit
Plugins{'html': '2.0.0', 'metadata': '1.8.0', 'cov': '2.8.1'}
Python3.6.8
422 |

Summary

423 |

31 tests ran in 0.23 seconds.

424 | 31 passed, 0 skipped, 0 failed, 0 errors, 0 expected failures, 0 unexpected passes 425 |

Results

426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 |
ResultTestDurationLinks
Passedoffline-demo/test_00_optic_inventory.py::test_optic_inventory[Ethernet50/1:QSFP28-LR4-100G]0.00
443 |
No log output captured.
Passedoffline-demo/test_00_optic_inventory.py::test_optic_inventory[Ethernet49/1:QSFP28-LR4-100G]0.00
452 |
No log output captured.
Passedoffline-demo/test_00_optic_inventory.py::test_optic_inventory[Ethernet12:none]0.00
461 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet1:up]0.00
470 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet15:up]0.00
479 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet2:up]0.00
488 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet29:down]0.00
497 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet30:down]0.00
506 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet31:down]0.00
515 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet32:down]0.00
524 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet33/1:down]0.00
533 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet34/1:down]0.00
542 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet4:up]0.00
551 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet6:down]0.00
560 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet7:down]0.00
569 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet8:down]0.00
578 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Ethernet9:down]0.00
587 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Loopback0:up]0.00
596 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Management1:up]0.00
605 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Port-Channel10:up]0.00
614 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Vlan4094:up]0.00
623 |
No log output captured.
Passedoffline-demo/test_01_interface_status.py::test_interface_status[Port-Channel2000:up]0.00
632 |
No log output captured.
Passedoffline-demo/test_02_cabling.py::test_cabling[Ethernet49/1<[spine-leaf]->switch-21.bld1:Ethernet3/1]0.00
641 |
No log output captured.
Passedoffline-demo/test_02_cabling.py::test_cabling[Ethernet50/1<[spine-leaf]->switch-22.bld1:Ethernet3/1]0.00
650 |
No log output captured.
Passedoffline-demo/test_02_cabling.py::test_cabling[Ethernet59/1<[peer-pair]->switch-2106.bld1:Ethernet59/1]0.00
659 |
No log output captured.
Passedoffline-demo/test_02_cabling.py::test_cabling[Ethernet60/1<[peer-pair]->switch-2106.bld1:Ethernet60/1]0.00
668 |
No log output captured.
Passedoffline-demo/test_03_lag_status.py::test_pass_lag_status[Port-Channel2000]0.00
677 |
No log output captured.
Passedoffline-demo/test_04_mlag_status.py::test_mlag_status[Port-Channel2000]0.00
686 |
No log output captured.
Passedoffline-demo/test_05_mlag_interface_status.py::test_mlag_interface_status[300:down]0.00
695 |
No log output captured.
Passedoffline-demo/test_05_mlag_interface_status.py::test_mlag_interface_status[301:up]0.00
704 |
No log output captured.
Passedoffline-demo/test_05_mlag_interface_status.py::test_mlag_interface_status[302:up]0.00
713 |
No log output captured.
--------------------------------------------------------------------------------