# Hands On - Useful Python Libraries for Network Engineers * [Setup and Preparation](#setup-and-preparation) * [Devnet Sandbox](#devnet-sandbox) * [Libraries to Work with Data](#libraries-to-work-with-data) * [XML with xmltodict](#xml-with-xmltodict) * [JSON with json](#json-with-json) * [YAML with PyYAML](#yaml-with-pyyaml) * [CSV with csv](#csv-with-csv) * [YANG with pyang](#yang-with-pyang) * [Libraries to Work with APIs](#libraries-to-work-with-apis) * [rest with requests](#rest-with-requests) * [NETCONF with ncclient](#netconf-with-ncclient) * [CLI with netmiko](#cli-with-netmiko) * [Other Cool Python Stuff](#other-cool-python-stuff) * [Introduction to pyATS](#introduction-to-pyats) # Setup and Preparation ## Devnet Sandbox This lab was written to be run using the [DevNet Devbox Sandbox](https://devnetsandbox.cisco.com/RM/Diagram/Index/f1a51f3b-3377-444d-97f0-5ad300d976be?diagramType=Topology). This sandbox is a basic CentOS 7 workstation with typical development tools and software installed. Specifically used in this lab are Python 3.6 and Vagrant (used to instantiate an IOS XE router for use in the labs.) If you are doing this lab on your own, you'll need to reserve an instance of this sandbox before beginning. If you are doing this as part of a guided workshop, the instructor will assign you a pod. ### Steps to complete preparation 1. Using either AnyConnect or OpenConnect, establish a VPN to your pod. 2. SSH to the Devbox at IP `10.10.20.20` using credentials `root / cisco123` ```bash ssh root@10.10.20.20 ``` 1. Install the Python 3.6 development libraries. ```bash yum install -y python36u-devel ``` 1. Add the IOS XE 16.9 Vagrant Box to your workstation. Instructions for creating the Box file are available on github at [github.com/hpreston/vagrant_net_prog](https://github.com/hpreston/vagrant_net_prog/tree/master/box_building#cisco-csr-1000v). If you are completing this as part of a guided lab, the instructor will provide details on how to complete this step. ```bash vagrant box add --name iosxe/16.09.01 serial-csr1000v-universalk9.16.09.01.box ``` 1. Clone the code samples to the devbox from GitHub and change into the directory. ```bash git clone https://github.com/hpreston/python_networking cd python_networking ``` 1. Create a Python 3.6 virtual environment and install Python libraries for exercises. ```bash python3.6 -m venv venv source venv/bin/activate pip install -r requirements.txt ``` 1. Start and baseline the IOS XE Vagrant environment. ```bash vagrant up # After it completes python vagrant_device_setup.py ``` # Libraries to Work with Data Exercises in this section are intended to be executed from an interactive Python interpreter. [iPython](https://ipython.org) has been installed as part of the requirements.txt installation and is one option. You can start an iPython window by simply typing `ipython`. For each step in the list below, type the specified command (or commands) and then press enter until the iPython prompt goes to the next step, and/or shows the expected output (ie. print or pprint commands). Other options could be just `python` or `idle`. ## XML with xmltodict 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd data_manipulation/xml ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` 1. Import the xmltodict library ```python import xmltodict ``` 1. Open the sample xml file and read it into variable ```python with open("xml_example.xml") as f: xml_example = f.read() ``` 1. Print the raw XML data ```python print(xml_example) ``` 1. Parse the XML into a Python (Ordered) dictionary ```python xml_dict = xmltodict.parse(xml_example) ``` 1. Pretty Print the Python Dictionary Object ```python from pprint import pprint pprint(xml_dict) ``` 1. Save the interface name into a variable using XML nodes as keys ```python int_name = xml_dict["interface"]["name"] ``` 1. Print the interface name ```python print(int_name) ``` 1. Change the IP address of the interface ```python xml_dict["interface"]["ipv4"]["address"]["ip"] = "192.168.0.2" ``` 1. Check that the IP address has been changed in the dictionary ```python pprint(xml_dict) ``` 1. Revert to the XML string version of the dictionary ```python print(xmltodict.unparse(xml_dict)) ``` 1. After you've completed exploring, exit the interpreter. ```python exit() ``` ## JSON with json 1. From the root of the `python_networking` repository, change into the exercise directory. ```python cd data_manipulation/json ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` 1. Import the jsontodict library ```python import json ``` 1. Open the sample json file and read it into variable ```python with open("json_example.json") as f: json_example = f.read() ``` 1. Print the raw json data ```python print(json_example) ``` 1. Parse the json into a Python dictionary ```python json_dict = json.loads(json_example) ``` 1. Pretty Print the Python Dictionary Object ```python from pprint import pprint pprint(json_dict) ``` 1. Save the interface name into a variable ```python int_name = json_dict["interface"]["name"] ``` 1. Print the interface name ```python print(int_name) ``` 1. Change the IP address of the interface ```python json_dict["interface"]["ipv4"]["address"][0]["ip"] = "192.168.0.2" ``` 1. Check that the IP address has been changed in the dictionary ```python pprint(json_dict) ``` 1. Revert to the json string version of the dictionary ```python print(json.dumps(json_dict)) ``` 1. After you've completed exploring, exit the interpreter. ```python exit() ``` ## YAML with PyYAML 1. From the root of the `python_networking` repository, change into the exercise directory. ```python cd data_manipulation/yaml ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` 1. Import the yamltodict library ```python import yaml ``` 1. Open the sample yaml file and read it into variable ```python with open("yaml_example.yaml") as f: yaml_example = f.read() ``` 1. Print the raw yaml data ```python print(yaml_example) ``` 1. Parse the yaml into a Python dictionary ```python yaml_dict = yaml.load(yaml_example) ``` 1. Pretty Print the Python Dictionary Object ```bash from pprint import pprint pprint(yaml_dict) ``` 1. Save the interface name into a variable ```python int_name = yaml_dict["interface"]["name"] ``` 1. Print the interface name ```python print(int_name) ``` 1. Change the IP address of the interface ```python yaml_dict["interface"]["ipv4"]["address"][0]["ip"] = "192.168.0.2" ``` 1. Check that the IP address has been changed in the dictionary ```python pprint(yaml_dict) ``` 1. Revert to the yaml string version of the dictionary ```python print(yaml.dump(yaml_dict, default_flow_style=False)) ``` 1. After you've completed exploring, exit the interpreter. ```python exit() ``` ## CSV with csv 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd data_manipulation/csv ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` 1. Import the csv library ```python import csv ``` 1. Open the sample csv file and print it to screen ```python with open("csv_example.csv") as f: print(f.read()) ``` 1. Open the sample csv file, and create a csv.reader object ```python with open("csv_example.csv") as f: csv_python = csv.reader(f) # Loop over each row in csv and leverage the data in code for row in csv_python: print("{device} is in {location} " \ "and has IP {ip}.".format( device = row[0], location = row[2], ip = row[1] ) ) ``` 1. Create a new tuple for additional router. ```python router4 = ("router4", "10.4.0.1", "Chicago") ``` 1. Add new router to CSV file. ```python with open("csv_example.csv", "a") as f: csv_writer = csv.writer(f) csv_writer.writerow(router4) ``` 1. Re-read and print out the CSV content. ```python with open("csv_example.csv") as f: print(f.read()) ``` 1. After you've completed exploring, exit the interpreter. ```python exit() ``` ## YANG with pyang 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd data_manipulation/yang ``` 1. Print the YANG module in a simple text tree ```bash pyang -f tree ietf-interfaces.yang ``` 1. Print only part of the tree ```bash pyang -f tree --tree-path=/interfaces/interface \ ietf-interfaces.yang ``` 1. Print an example XML skeleton (NETCONF) ```bash pyang -f sample-xml-skeleton ietf-interfaces.yang ``` 1. Create an HTTP/JS view of the YANG Model (no output expected in the CLI) ```bash pyang -f jstree -o ietf-interfaces.html \ ietf-interfaces.yang ``` 1. *Optional:* Open `ietf-interfaces.html` in a web browser. Will need to RDP into the Devbox to do this step. 1. Control the "nested depth" in trees ```bash pyang -f tree --tree-depth=2 ietf-ip.yang ``` 1. Display a full module. ```bash pyang -f tree \ ietf-ip.yang ``` 1. Include deviation models in the processing ```bash pyang -f tree \ --deviation-module=cisco-xe-ietf-ip-deviation.yang \ ietf-ip.yang ``` # Libraries to Work with APIs Exercises in this section are intended to be executed from an interactive Python interpreter. [iPython](https://ipython.org) has been installed as part of the requirements.txt installation and is one option. You can start an iPython window by simply typing `ipython`. Other options could be just `python` or `idle`. Each exercise also includes a Python script file that can be executed directly. ## rest with requests 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd device_apis/rest ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` ### Retrieve Network Configuration Details with RESTCONF with `restconf_example1.py` 1. Import libraries ```python import requests, urllib3 import sys ``` 1. Add parent directory to path to allow importing common vars ```python sys.path.append("..") from device_info import vagrant_iosxe as device ``` 1. Disable Self-Signed Cert warning for demo ```python urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) ``` 1. Setup base variable for request ```python restconf_headers = {"Accept": "application/yang-data+json"} restconf_base = "https://{ip}:{port}/restconf/data" interface_url = restconf_base + "/ietf-interfaces:interfaces/interface={int_name}" ``` 1. Create URL GigE2 Config ```python url = interface_url.format(ip = device["address"], port = device["restconf_port"], int_name = "GigabitEthernet2" ) ``` 1. Check the complete URL you just composed ```python print(url) ``` 1. Send RESTCONF request to core1 for GigE2 Config ```python r = requests.get(url, headers = restconf_headers, auth=(device["username"], device["password"]), verify=False) ``` 1. Print returned data ```python print(r.text) ``` 1. If REST call was successful, report interesting details. ```python if r.status_code == 200: # Process JSON data into Python Dictionary and use interface = r.json()["ietf-interfaces:interface"] print("The interface {name} has ip address {ip}/{mask}".format( name = interface["name"], ip = interface["ietf-ip:ipv4"]["address"][0]["ip"], mask = interface["ietf-ip:ipv4"]["address"][0]["netmask"], ) ) else: print("No interface {} found.".format("GigabitEthernet2")) ``` ### Modify Network Configuration Details with RESTCONF with `restconf_example2.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python import requests, urllib3, sys sys.path.append("..") from device_info import vagrant_iosxe as device urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) restconf_headers = {"Accept": "application/yang-data+json"} restconf_base = "https://{ip}:{port}/restconf/data" interface_url = restconf_base + "/ietf-interfaces:interfaces/interface={int_name}" ``` 1. Add additional `Content-Type` header. ```python restconf_headers["Content-Type"] = "application/yang-data+json" ``` 1. Create dictionary with details on a new loopback interface. ```python loopback = {"name": "Loopback101", "description": "Demo interface by RESTCONF", "ip": "192.168.101.1", "netmask": "255.255.255.0"} ``` 1. Setup data body to create new loopback interface ```python data = { "ietf-interfaces:interface": { "name": loopback["name"], "description": loopback["description"], "type": "iana-if-type:softwareLoopback", "enabled": True, "ietf-ip:ipv4": { "address": [ { "ip": loopback["ip"], "netmask": loopback["netmask"] } ] } } } ``` 1. Create URL ```python url = interface_url.format(ip = device["address"], port = device["restconf_port"], int_name = loopback["name"] ) ``` 1. Check the complete URL you just composed ```python print(url) ``` 1. Send RESTCONF request to device ```python r = requests.put(url, headers = restconf_headers, auth=(device["username"], device["password"]), json = data, verify=False) ``` 1. Check Status Code (expected `201`) ```python print("Request Status Code: {}".format(r.status_code)) ``` 1. Query for details on the new interface you just created ```python # Create URL and send RESTCONF request to core1 for GigE2 Config url = interface_url.format(ip = device["address"], port = device["restconf_port"], int_name = "Loopback101" ) r = requests.get(url, headers = restconf_headers, auth=(device["username"], device["password"]), verify=False) # Print returned data print(r.text) ``` ### Delete Network Configuration Details with RESTCONF with `restconf_example3.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python import requests, urllib3, sys sys.path.append("..") from device_info import vagrant_iosxe as device urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) restconf_headers = {"Accept": "application/yang-data+json"} restconf_base = "https://{ip}:{port}/restconf/data" interface_url = restconf_base + "/ietf-interfaces:interfaces/interface={int_name}" url = interface_url.format(ip = device["address"], port = device["restconf_port"], int_name = "Loopback101" ) ``` 1. Send DELETE request to remove the Loopback. ```python r = requests.delete(url, headers = restconf_headers, auth=(device["username"], device["password"]), verify=False) ``` 1. Check Status Code (expected `204`) ```python print("Request Status Code: {}".format(r.status_code)) ``` 1. Query for details on the new interface (no output expected, as you just deleted it) ```python r = requests.get(url, headers = restconf_headers, auth=(device["username"], device["password"]), verify=False) ``` 1. Check status code (expected `404`) ```python print(r.status_code) ``` ## NETCONF with ncclient 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd device_apis/netconf ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` ### Retrieve Network Configuration Details with NETCONF with `netconf_example1.py` 1. Import libraries ```python from ncclient import manager from xml.dom import minidom import xmltodict import sys ``` 1. Add parent directory to path to allow importing common vars ```python sys.path.append("..") from device_info import vagrant_iosxe as device ``` 1. Create filter template for an interface ```python interface_filter = """ {int_name} """ ``` 1. Open NETCONF connection to device * *Note: Normally you'd use a `with` block to open connection to device. This avoids needing to manually `m.close_session()` at the end of a script, but for interactive use, this format is chosen.* ```python m = manager.connect(host = device["address"], port = device["netconf_port"], username = device["username"], password = device["password"], hostkey_verify = False) ``` 1. Verify NETCONF connection is active (expected output `true`) ```python m.connected ``` 1. Create desired NETCONF filter for a particular interface ```python filter = interface_filter.format(int_name = "GigabitEthernet2") ``` 1. Execute a NETCONF using the filter ```python r = m.get_config("running", filter) ``` 1. Pretty print raw xml to screen ```python xml_doc = minidom.parseString(r.xml) print(xml_doc.toprettyxml(indent = " ")) ``` 1. Process the XML data into Python Dictionary and use ```python interface = xmltodict.parse(r.xml) ``` 1. Pretty Print the full Python (Ordered) Dictionary. ```python from pprint import pprint pprint(interface) ``` 1. If RPC returned data, print out the interesting pieces. ```python if not interface["rpc-reply"]["data"] is None: # Create Python variable for interface details interface = interface["rpc-reply"]["data"]["interfaces"]["interface"] print("The interface {name} has ip address {ip}/{mask}".format( name = interface["name"]["#text"], ip = interface["ipv4"]["address"]["ip"], mask = interface["ipv4"]["address"]["netmask"], ) ) else: print("No interface {} found".format("GigabitEthernet2")) ``` ### Modify Network Configuration Details with NETCONF with `netconf_example2.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python from ncclient import manager from xml.dom import minidom import xmltodict import sys sys.path.append("..") from device_info import vagrant_iosxe as device interface_filter = """ {int_name} """ m = manager.connect(host = device["address"], port = device["netconf_port"], username = device["username"], password = device["password"], hostkey_verify = False) ``` 1. Verify NETCONF connection is active ```python m.connected ``` 1. Create Python dictionary with new Loopback Details ```python loopback = {"int_name": "Loopback102", "description": "Demo interface by NETCONF", "ip": "192.168.102.1", "netmask": "255.255.255.0"} ``` 1. Create NETCONF template for an interface ```python config_data = """ {int_name} {description} ianaift:softwareLoopback true
{ip} {netmask}
""" ``` 1. Create desired NETCONF config payload ```python config = config_data.format(**loopback) ``` 1. Send operation ```python r = m.edit_config(target = "running", config = config) ``` 1. Print OK status (expected output `true`) ```python print("NETCONF RPC OK: {}".format(r.ok)) ``` 1. Create a new NETCONF to check on new loopback interface ```python filter = interface_filter.format(int_name = "Loopback102") ``` 1. Execute a NETCONF using this filter ```python r = m.get_config("running", filter) ``` 1. Pretty print the raw XML to screen ```python xml_doc = minidom.parseString(r.xml) print(xml_doc.toprettyxml(indent = " ")) ``` ### Delete Network Configuration Details with NETCONF with `netconf_example3.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python from ncclient import manager from xml.dom import minidom import xmltodict import sys sys.path.append("..") from device_info import vagrant_iosxe as device interface_filter = """ {int_name} """ loopback = {"int_name": "Loopback102", "description": "Demo interface by NETCONF", "ip": "192.168.102.1", "netmask": "255.255.255.0"} m = manager.connect(host = device["address"], port = device["netconf_port"], username = device["username"], password = device["password"], hostkey_verify = False) ``` 1. Verify NETCONF connection is active ```python m.connected ``` 1. Create new config template to delete an interface ```python config_data = """ {int_name} """ ``` 1. Create desired NETCONF config payload and execute to delete the interface ```python config = config_data.format(**loopback) r = m.edit_config(target = "running", config = config) ``` 1. Print OK status (expected output `true`) ```python print("NETCONF RPC OK: {}".format(r.ok)) ``` 1. Create a new NETCONF to check on new loopback interface ```python filter = interface_filter.format(int_name = "Loopback102") ``` 1. Execute a NETCONF using this filter ```python r = m.get_config("running", filter) ``` 1. Pretty print the raw XML to screen (expected output will not include the loopback interface, as you just deleted it) ```python xml_doc = minidom.parseString(r.xml) print(xml_doc.toprettyxml(indent = " ")) ``` ### End the NETCONF Connection 1. Send a RPC request to disconnect the connection. ```python m.close_session() m.connected ``` 1. End the Python interpreter. ```python exit() ``` ## CLI with netmiko 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd device_apis/cli ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` ### Retrieve Network Configuration Details with CLI with `netmiko_example1.py` 1. Import libraries ```python from netmiko import ConnectHandler import re import sys ``` 1. Add parent directory to path to allow importing common vars ```python sys.path.append("..") from device_info import vagrant_iosxe as device ``` 1. Set device_type for netmiko ```python device["device_type"] = "cisco_ios" ``` 1. Create a CLI command template ```python show_interface_config_temp = "show running-config interface {}" ``` 1. Open CLI connection to device. * *Note: Normally you'd use a with block to open connection to device. This avoids needing to manually `m.close_session()` at the end of a script, but for interactive use, this format is chosen.* ```python ch = ConnectHandler(ip = device["address"], port = device["ssh_port"], username = device["username"], password = device["password"], device_type = device["device_type"]) ``` 1. Create desired CLI command ```python command = show_interface_config_temp.format("GigabitEthernet2") ``` 1. Verify the command has been created correctly ```python print(command) ``` 1. Send command to device ```python interface = ch.send_command(command) ``` 1. Print the raw command output to the screen ```python print(interface) ``` 1. Create regular expression searches to parse the output for desired interface details ```python name = re.search(r'interface (.*)', interface).group(1) description = re.search(r'description (.*)', interface).group(1) ``` 1. Pull out the ip and mask for the interface ```python ip_info = re.search(r'ip address (.*) (.*)', interface) ip = ip_info.group(1) netmask = ip_info.group(2) ``` 1. Print the desired info to the screen ```python print("The interface {name} has ip address {ip}/{mask}".format( name = name, ip = ip, mask = netmask, ) ) ``` ### Modify Network Configuration Details with CLI with `netmiko_example2.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python from netmiko import ConnectHandler import re, sys sys.path.append("..") from device_info import vagrant_iosxe as device device["device_type"] = "cisco_ios" show_interface_config_temp = "show running-config interface {}" ch = ConnectHandler(ip = device["address"], port = device["ssh_port"], username = device["username"], password = device["password"], device_type = device["device_type"]) ``` 1. Create Python dictionary with new Loopback Details ```python loopback = {"int_name": "Loopback103", "description": "Demo interface by CLI and netmiko", "ip": "192.168.103.1", "netmask": "255.255.255.0"} ``` 1. Create a CLI configuration ```python interface_config = [ "interface {}".format(loopback["int_name"]), "description {}".format(loopback["description"]), "ip address {} {}".format(loopback["ip"], loopback["netmask"]), "no shut" ] ``` 1. Send configuration to device ```python output = ch.send_config_set(interface_config) ``` 1. Print the raw command output to the screen ```python print("The following configuration was sent: ") print(output) ``` 1. Create a CLI command to retrieve the new configuration. ```python command = show_interface_config_temp.format("Loopback103") interface = ch.send_command(command) print(interface) ``` ### Delete Network Configuration Details with CLI with `netmiko_example3.py` 1. Continuing from previous exercise. If starting from new interpreter, execute these steps. ```python from netmiko import ConnectHandler import re, sys sys.path.append("..") from device_info import vagrant_iosxe as device device["device_type"] = "cisco_ios" show_interface_config_temp = "show running-config interface {}" ch = ConnectHandler(ip = device["address"], port = device["ssh_port"], username = device["username"], password = device["password"], device_type = device["device_type"]) ``` 1. Create a new CLI configuration to delete the interface. ```python interface_config = [ "no interface {}".format(loopback["int_name"]) ] ``` 1. Send configuration to device ```python output = ch.send_config_set(interface_config) ``` 1. Print the raw command output to the screen ```python print("The following configuration was sent: ") print(output) ``` 1. Create a CLI command to verify configuration removed. ```python command = show_interface_config_temp.format("Loopback103") interface = ch.send_command(command) print(interface) ``` *Note: attempting to view the configuration of a non-existing interface will generate a CLI error. This output is expected, and one of the reasons APIs like NETCONF or RESTCONF are better suited to programmatic interactions.* ### End the CLI connection to the device 1. Disconnect from the device. ```python ch.disconnect() ``` 1. End the Python interpreter. ```python exit() ``` # Other Cool Python Stuff ## Introduction to pyATS [pyATS](https://developer.cisco.com/site/pyats) is a network testing tool developed by Cisco and made available for free, with significant elements of the underlying code open source. pyATS offers network developers the ability to profile the network state of hardware, interfaces, protocols, etc... before, during and after changes, to ensure the network is operating as designed, and identify problems before the dreaded phone call. To enable this level of robust testing, pyATS offers a standard way to communicate with network elements and standardize the data returned into native Python objects. This core functionality opens up a lot of flexibility on how pyATS can be used by network developers. In the following exercises, you will get a brief introduction to pyATS to connect and learn about device details. ### Connect and Interact with a Device 1. From the root of the `python_networking` repository, change into the exercise directory. ```bash cd network_testing/pyats ``` 1. Start an interactive Python interpreter. Example below: ```python # ipython Python 3.6.5 (default, Apr 10 2018, 17:08:37) Type 'copyright', 'credits' or 'license' for more information IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: ``` 1. Import in pyATS libraries and tools ```python from genie.conf import Genie from ats.topology import loader from genie.abstract import Lookup from genie.libs import ops # noqa ``` 1. Read and process the testbed (inventory) file ```python genie_testbed = Genie.init("./default_testbed.yaml") ``` 1. Create a pyATS device object from testbed ```python vagrant_iosxe1 = genie_testbed.devices["vagrant-iosxe1"] ``` 1. Connect to the device ```python vagrant_iosxe1.connect() ``` * pyATS establishes a connection to the device 1. Create an abstract device to standardize Python API and code for platform ```python vagrant_iosxe1_abstract = Lookup.from_device(vagrant_iosxe1) ``` 1. Using the abstract device, learn about the Interfaces on the end device ```python vagrant_iosxe1_interfaces = vagrant_iosxe1_abstract.ops.interface.interface.Interface(vagrant_iosxe1) vagrant_iosxe1_interfaces.learn() ``` 1. Print out the interface details that were learned ```python vagrant_iosxe1_interfaces.info ``` 1. Display a single interface from the device ```python vagrant_iosxe1_interfaces.info["GigabitEthernet1"] ``` 1. Print the mac address for the interface ```python vagrant_iosxe1_interfaces.info["GigabitEthernet1"]["mac_address"] ``` 1. Notice that there was no parsing of command line output needed to access this data 1. Execute a command on the device and print the output ```python print(vagrant_iosxe1.execute("show version")) ``` 1. Or store the output into a variable ```python version = vagrant_iosxe1.execute("show version") ``` 1. Send a configuration command to the device ```python vagrant_iosxe1.configure("ntp server 10.10.10.10") ``` 1. Create a configuration command list and send to the device ```python config_loopback = [ "interface Loopback201", "description Configured by pyATS", "ip address 172.16.201.1 255.255.255.0", "no shut" ] vagrant_iosxe1.configure(config_loopback) ``` 1. Re-learn the interfaces ```python vagrant_iosxe1_interfaces = vagrant_iosxe1_abstract.ops.interface.interface.Interface(vagrant_iosxe1) vagrant_iosxe1_interfaces.learn() ``` 1. Get details about new interface ```python vagrant_iosxe1_interfaces.info["Loopback201"] ``` 1. Disconnect from the devices ```python vagrant_iosxe1.disconnect() ```