diff --git a/Makefile b/Makefile index 9037f0b..06be661 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ test: mkdir -p .pyATS - docker run -it --rm -v $(PWD):/tests ciscotestautomation/pyats bash /tests/run_tests.sh + docker run -it -e PYATS_USERNAME -e PYATS_PASSWORD -e PYATS_AUTH_PASS --rm -v $(PWD):/tests ciscotestautomation/pyats bash /tests/run_tests.sh diff --git a/README.md b/README.md index dd737a9..37dc63c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ # Introduction -This repo provides a demonstration of Cisco pyATS with a NetDevOps style workflow. +This repo contains a full test script in pyATS with local libraries that connects to a +testbed of IOS devices, and runs various test cases that parses command outputs, +collects router information, and report them in log. # Requirements -* Python 2.7 +* Python 3.x * Docker # Workflow @@ -20,7 +22,7 @@ export VIRL_HOST=myvirlserver.change.me ### Clone this repo / create virtualenv / install requirements ``` -git clone https://github.com/kecorbin/pyats-ios-sample +git clone https://github.com/CiscoDevNet/pyats-ios-sample cd pyats-ios-sample virtualenv venv source venv/bin/activate @@ -76,7 +78,31 @@ virl generate pyats ``` -### Verify +#### Testbed Credentials + +The generated testbed will look to the execution environment to provide credentials, make +sure these are set in either your local environment, or docker container. + +``` +tacacs: + username: "%ENV{PYATS_USERNAME}" +passwords: + tacacs: "%ENV{PYATS_PASSWORD}" + enable: "%ENV{PYATS_AUTH_PASS}" + line: "%ENV{PYATS_PASSWORD}" + +``` + +You can set these like such + + +``` +export PYATS_USERNAME=cisco +export PYATS_PASSWORD=cisco +export PYATS_AUTH_PASS=cisco +``` + +### Test Cases Launch pyATS test suite @@ -128,11 +154,7 @@ make test ``` -# pyATS details -This repo contains a full test script in pyATS with local libraries that connects to a -testbed of IOS devices, and runs various test cases that parses command outputs, -collects router information, and report them in log. ## General Information @@ -164,6 +186,8 @@ demonstration purposes. | | Gig0/1 <-> Gig0/1 | | +-------------+ +-------------+ +``` + ## Testing This script performs the following tests for demonstration purposes. @@ -192,4 +216,4 @@ $ easypy pyats_ios_example_job.py -testbed_file pyats_ios_example.yaml References: For the complete and up-to-date user guide on pyATS, visit: - https://developer.cisco.com/site/pyats/docs/ \ No newline at end of file + https://developer.cisco.com/site/pyats/docs/ diff --git a/pyats-intro.py b/pyats-intro.py new file mode 100644 index 0000000..3f4c5b0 --- /dev/null +++ b/pyats-intro.py @@ -0,0 +1,79 @@ +import sys +from IPython import embed +import logging +import unicon + +from genie.conf import Genie +from ats.topology import loader +from genie.abstract import Lookup +from genie.libs import ops # noqa + + + + + +if __name__ == '__main__': + + # local imports + import argparse + + parser = argparse.ArgumentParser(description="standalone parser") + parser.add_argument('--testbed', default='./default_testbed.yaml', + dest='testbed', type=loader.load) + parser.add_argument('--device', dest='device_name') + + args, unknown = parser.parse_known_args() + + device_name = args.device_name + + # pyats testbed != genie testbed + genie_testbed = Genie.init(args.testbed) + + # this gives us device_name as Device Object e.g dist1 + vars()[device_name] = genie_testbed.devices[device_name] + # or we can also just use `device` + device = vars()[device_name] + + # logger = logging.getLogger("UNICON") + # unicon.logs.remove_stream_handler(logging) + + # connect to the device (quietly) + import time + print("pyATS and Genie are about to profile the device and create structured data for " + "you to use") + time.sleep(10) + device.connect() + + + # work with an abstracted device model + abstract = Lookup.from_device(device) + + # interface info is always a good place to start.. + interfaces = abstract.ops.interface.interface.Interface(device) + interfaces.learn() + + print(""" + + Welcome to the device object tutorial, as you may have noticed, Genie was + busy profiling all of the interfaces of your device. you can access them via + + interfaces.info + + you can interact with your device using either `device` or the device name + you specified with --device + + You can start by exploring some of the common operations available for the + device by typing + + dir(device) + + you can also explore the rest of the genie models at: + + https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/genie_libs/#/models + + Enjoy! + + """) + + + embed() diff --git a/pyats_ios_example.py b/pyats_ios_example.py index 4a008f1..8519ddd 100644 --- a/pyats_ios_example.py +++ b/pyats_ios_example.py @@ -13,11 +13,11 @@ Topology: - +-------------+ Eth0/0 <-> Eth0/0 +-------------+ + +-------------+ GigabitEthernet0/1 +-------------+ | | ------------------------------------ | | | ios1 | | ios2 | - | | ------------------------------------ | | - +-------------+ Eth0/1 <-> Eth0/1 +-------------+ + | | | | + +-------------+ +-------------+ Testing: This script performs the following tests for demonstration purposes. @@ -34,10 +34,7 @@ counts. - execute `show ip interface brief` command: basic command execution and - data parsing; extract all - ethernet and serial - interfaces; logs number of - interface counts. + data parsing; - verify ethernet and serial interface counts from above commands. @@ -114,12 +111,21 @@ def check_topology(self, # add them to testscript parameters self.parent.parameters.update(ios1 = ios1, ios2 = ios2) - # get corresponding links + # verify at least one interface between two devices links = ios1.find_links(ios2) assert len(links) >= 1, 'require one link between ios1 and ios2' - # save link as uut link parameter - self.parent.parameters['uut_link'] = links.pop() + # Select an Interface by Name, for use cases like new link turnup, + # this could also be provided as an argument to a script + link = ios1.interfaces['GigabitEthernet0/1'] + + # the parameters here are fairly arbitary, but are useful for + # passing some data between tests, so that additional things + # can be done.g IP lookup / ping / counters + self.parent.parameters['uut_link'] = link + + # let someone know + logger.info("Using UUT Link: {}".format(link)) @aetest.subsection @@ -166,12 +172,12 @@ class PingTestcase(aetest.Testcase): @aetest.setup def setup(self, uut_link): destination = [] - for intf in uut_link.interfaces: - destination.append(str(intf.ipv4.ip)) - # apply loop to next section - aetest.loop.mark(self.ping, destination = destination) + # get the IP address of the link being tested + destination.append(str(uut_link.ipv4.ip)) + # apply loop to next section `ping destination from these/all devices` + aetest.loop.mark(self.ping, destination=destination) @aetest.test def ping(self, device, destination): diff --git a/pyats_ios_example.yaml b/pyats_ios_example.yaml index 493d770..0fe1c08 100644 --- a/pyats_ios_example.yaml +++ b/pyats_ios_example.yaml @@ -2,56 +2,59 @@ testbed: name: pyATS_IOS_Example_Testbed devices: ios1: + os: iosxe connections: defaults: class: 'unicon.Unicon' - a: + a: protocol: telnet - ip: localhost - port: 11023 - passwords: - enable: lab - line: lab - tacacs: lab - tacacs: - username: lab + ip: 10.94.241.240 + tacacs: + username: cisco + passwords: + tacacs: cisco type: ios + custom: + abstraction: + order: [os, type] + ios2: + os: iosxe connections: defaults: class: 'unicon.Unicon' - a: + a: protocol: telnet - ip: localhost - port: 11024 - passwords: - enable: lab - line: lab - tacacs: lab - tacacs: - username: lab + ip: 10.94.241.239 + tacacs: + username: cisco + passwords: + tacacs: cisco type: ios + custom: + abstraction: + order: [os, type] topology: ios1: interfaces: - FastEthernet0/0: + GigabitEthernet0/1: ipv4: 10.10.10.1/24 ipv6: '10:10:10::1/64' link: n1 type: ethernet - Loopback0: + Loopback0: ipv4: 192.168.0.1/32 ipv6: '192::1/128' link: ios1_Loopback0 type: loopback ios2: interfaces: - FastEthernet0/0: + GigabitEthernet0/1: ipv4: 10.10.10.2/24 ipv6: '10:10:10::2/64' link: n1 type: ethernet - Loopback0: + Loopback0: ipv4: 192.168.0.2/32 ipv6: '192::2/128' link: ios2_Loopback0 diff --git a/requirements.txt b/requirements.txt index 3335ecf..daf160a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,89 @@ -virlutils +appnope==0.1.0 +asynctest==0.12.2 +backcall==0.1.0 +backports.shutil-get-terminal-size==1.0.0 +blinker==1.4 +certifi==2018.4.16 +chardet==3.0.4 +Click==7.0 +colorama==0.3.9 +cursor==1.2.0 +decorator==4.3.0 +dill==0.2.8.2 +docopt==0.6.2 +enum34==1.1.6 +Flask==1.0.2 +genie==3.0.0 +genie.abstract==3.0.1 +genie.conf==3.0.1 +genie.examples==3.0.1 +genie.harness==3.0.4 +genie.libs.conf==3.0.4 +genie.libs.filetransferutils==3.0.1 +genie.libs.ops==3.0.4 +genie.libs.parser==3.0.7 +genie.libs.robot==3.0.7 +genie.libs.sdk==3.0.3 +genie.libs.telemetry==3.0.3 +genie.metaparser==3.0.1 +genie.ops==3.0.1 +genie.parsergen==3.0.2 +genie.predcore==3.0.1 +genie.telemetry==3.0.2 +genie.utils==3.0.1 +graphviz==0.8.3 +gunicorn==19.9.0 +halo==0.0.16 +httplib2==0.11.3 +idna==2.7 +ipython==6.5.0 +ipython-genutils==0.2.0 +ItsDangerous==1.0.0 +jedi==0.12.1 +Jinja2==2.10 +jsonpickle==0.9.6 +junit-xml==1.8 +log-symbols==0.0.11 +lxml==4.2.4 +MarkupSafe==1.0 +netaddr==0.7.19 +parso==0.3.1 +pexpect==4.6.0 +pickleshare==0.7.4 +prompt-toolkit==1.0.15 +psutil==5.4.6 +ptyprocess==0.6.0 +pyats==4.1.0 +pyats.aereport==4.1.0 +pyats.aetest==4.1.3 +pyats.async==4.1.0 +pyats.connections==4.1.0 +pyats.datastructures==4.1.0 +pyats.easypy==4.1.7 +pyats.examples==4.1.0 +pyats.kleenex==4.1.0 +pyats.log==4.1.0 +pyats.results==4.1.0 +pyats.robot==4.1.1 +pyats.tcl==4.1.1 +pyats.templates==4.1.0 +pyats.topology==4.1.2 +pyats.utils==4.1.4 +Pygments==2.2.0 +python-dateutil==2.7.3 +PyYAML==3.12 +requests==2.19.1 +robotframework==3.0.4 +setproctitle==1.1.10 +simplegeneric==0.8.1 +six==1.11.0 +spinners==0.0.23 +tabulate==0.8.2 +termcolor==1.1.0 +traitlets==4.3.2 +unicon==3.1.2 +urllib3==1.23 +virlutils==0.8.4 +wcwidth==0.1.7 +Werkzeug==0.14.1 +xmltodict==0.11.0 diff --git a/run_tests.sh b/run_tests.sh index 97056f8..620a675 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1 +1 @@ -easypy /tests/pyats_ios_example_job.py -testbed_file /tests/default_testbed.yaml -archive_dir=/tests/.pyATS +easypy pyats_ios_example_job.py -testbed_file default_testbed.yaml -archive_dir=./.pyATS diff --git a/topology.virl b/topology.virl index 851392d..0fb3967 100644 --- a/topology.virl +++ b/topology.virl @@ -1,7 +1,8 @@ - true + flat + false @@ -40,6 +41,8 @@ no ip domain lookup ip domain name virl.info crypto key generate rsa modulus 768 ip ssh server algorithm authentication password +username cisco secret cisco +enable secret cisco line vty 0 4 transport input ssh telnet exec-timeout 720 0 @@ -96,6 +99,7 @@ router bgp 1 neighbor 192.168.0.2 activate exit-address-family ! +ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 {{ gateway }} ! ! end @@ -140,6 +144,8 @@ no ip domain lookup ip domain name virl.info crypto key generate rsa modulus 768 ip ssh server algorithm authentication password +username cisco secret cisco +enable secret cisco line vty 0 4 transport input ssh telnet exec-timeout 720 0 @@ -196,6 +202,7 @@ router bgp 1 neighbor 192.168.0.1 activate exit-address-family ! +ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 {{ gateway }} ! ! end