Manage Network Configuration State with pyATS
Introduction
Python Automated Test Systems (pyATS) is an automated testing framework where Python3 tests can be run automatically. Initially used exclusively by Cisco Engineering, pyATS has evolved over time, attracting thousands of test developers and accumulating millions of lines of test scripts and libraries.
The pyATS base platform framework is designed for rapid, agile development cycles, emphasizing data-driven and reusable testing. It offers a strong, extensible Python framework that allows developers to transition from simple, linear test cases to more complex, asynchronous test suites.
Scenario
As the person in charge of managing and testing your company's network configuration, you utilize pyATS for automation. Its structured data output and seamless integration with existing CI/CD and reporting tools simplify the process. Upon discovering pyATS, you realize the necessity of familiarizing yourself with its classes and device definition processes.
In this scenario, you'll employ pyATS to test network configuration states using commands like learn, parse, and diff. Additionally, you'll utilize pyATS to push configuration settings to network devices.
Activity Objective
The objective of this activity is to:
- Manage configuration state through network and command comparisons.
- Perform network state testing using pyATS.
- Push configuration settings to devices with pyATS.
Managing Configuration State with Network and Command Comparisons
A crucial aspect of your role involves managing the state of your network and its configurations. You need the capability to establish a baseline snapshot of the network configuration state and compare subsequent snapshots to identify any changes automatically.
pyATS is well-suited for capturing and managing changes in network and configuration states. Utilizing the learn command, you can create a baseline snapshot of a working configuration and differentiate it from later snapshots.
In this task, you'll learn how to use the learn command to create device configuration snapshots. You'll also understand the significance of producing structured data from CLI output using the parse command and utilizing diff to promptly identify changes in configuration state.
Activity Steps
- Retrieve the interface details for R1, R2, and R3 using the learn command.
pyats learn interface --testbed-file my_testbed.yaml --output my_snapshot0
The Genie Learn Summary will display for R1, R2, and R3.
Review the contents of the Connected to section and the Learnt feature status and file locations.
$ pyats learn interface --testbed-file my_testbed.yaml --output my_snapshot0
Learning '['interface']' on devices '['R1', 'R2', 'R3']'
100%|…| 1/1 [00:01<00:00, 1.52s/it]
+==============================================================================+
| Genie Learn Summary for device R1 |
+==============================================================================+
| Connected to R1 |
| - Log: my_snapshot0/connection_R1.txt |
|------------------------------------------------------------------------------|
| Learnt feature 'interface' |
| - Ops structure: my_snapshot0/interface_iosxe_R1_ops.txt |
| - Device Console: my_snapshot0/interface_iosxe_R1_console.txt |
|==============================================================================|
+==============================================================================+
| Genie Learn Summary for device R2 |
+==============================================================================+
| Connected to R2 |
| - Log: my_snapshot0/connection_R2.txt |
|------------------------------------------------------------------------------|
| Learnt feature 'interface' |
| - Ops structure: my_snapshot0/interface_iosxe_R2_ops.txt |
| - Device Console: my_snapshot0/interface_iosxe_R2_console.txt |
|==============================================================================|
+==============================================================================+
| Genie Learn Summary for device R3 |
+==============================================================================+
| Connected to R3 |
| - Log: my_snapshot0/connection_R3.txt |
|------------------------------------------------------------------------------|
| Learnt feature 'interface' |
| - Ops structure: my_snapshot0/interface_iosxe_R3_ops.txt |
| - Device Console: my_snapshot0/interface_iosxe_R3_console.txt |
|=============================================================================|
By following these steps, you'll effectively manage the configuration state of your network using pyATS.
Step Through Connection Log and Review Contents
Introduction
In this step, you will review the connection log contents to understand the interactions with the devices.
Activity Steps
- Use the
cat
command to view the contents of the connection log for R1. Open your Terminal and execute the following command:
cat my_snapshot1/connection_R1.txt | more
Connection Log Output
2024-08-03 17:22:57,294: %UNICON-INFO: +++ R1 logfile my_snapshot1/connection_R1.txt +++
2024-08-03 17:22:57,294: %UNICON-INFO: +++ Unicon plugin iosxe (unicon.plugins.iosxe) +++
2024-08-03 17:22:57,748: %UNICON-INFO: +++ connection to spawn: ssh -l cisco 172.21.1.21 -p 22, id: 140119532091232 +++
2024-08-03 17:22:57,749: %UNICON-INFO: connection to R1
R1#
2024-08-03 17:22:57,838: %UNICON-INFO: +++ initializing handle +++
2024-08-03 17:22:57,904: %UNICON-INFO: +++ R1 with via 'cli': executing command 'term length 0' +++
term length 0
R1#
2024-08-03 17:22:58,100: %UNICON-INFO: +++ R1 with via 'cli': executing command 'term width 0' +++
term width 0
R1#
2024-08-03 17:22:58,294: %UNICON-INFO: +++ R1 with via 'cli': executing command 'show version' +++
show version
Cisco IOS XE Software, Version 17.03.04a
. . .
Review Ops Structure Output for 'interface' Feature
Introduction
Now, let's review the Ops structure output for the Learnt feature 'interface' section.
Activity Steps
- Use the
cat
command to view themy_snapshot1/interface_iosxe_R1_ops.txt
output. Execute the following command in your Terminal:
cat my_snapshot1/interface_iosxe_R1_ops.txt | more
Ops Structure Output
},
"GigabitEthernet3": {
. . .
},
"auto_negotiate": true,
"bandwidth": 1000000,
"counters": {
"in_broadcast_pkts": 0,
"in_crc_errors": 0,
"in_errors": 0,
"in_mac_pause_frames": 0,
"in_multicast_pkts": 0,
"in_octets": 12563408,
"in_pkts": 80466,
"last_clear": "never",
"out_broadcast_pkts": 0,
"out_errors": 0,
"out_mac_pause_frames": 0,
"out_multicast_pkts": 0,
"out_octets": 11526148,
"out_pkts": 75672,
"rate": {
"in_rate": 0,
. . .
Review Console Output for Learnt Feature
Introduction
Finally, let's understand how the console output from the learnt feature differs from the structured data in the Ops view.
Activity Steps
- Use the
cat
command to display the contents of the file:
cat my_snapshot1/interface_iosxe_R1_console.txt | more
Console Output
+++ R1 with via 'cli': executing command 'show vrf' +++
show vrf
Name Default RD Protocols Interfaces
vlan12 <not set> ipv4 Gi2
vlan13 <not set> ipv4 Gi3
vlan23 <not set> ipv4
R1#
+++ R1 with via 'cli': executing command 'show interfaces' +++
show interfaces
GigabitEthernet1 is up, line protocol is up
Hardware is CSR vNIC, address is 000c.2992.caa8 (bia 000c.2992.caa8)
Description: MGMT - 172.21.1.20/24
Internet address is 172.21.1.21/24
MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
reliability 255/255, txload 1/255, rxload 1/255
Encapsulation ARPA, loopback not set
Keepalive set (10 sec)
Full Duplex, 1000Mbps, link type is auto, media type is Virtual
output flow-control is unsupported, input flow-control is unsupported
ARP type: ARPA, ARP Timeout 04:00:00
Last input 00:00:00, output 00:00:00, output hang never
Last clearing of "show interface" counters never
Input queue: 1/375/0/0 (size/max/drops/flushes); Total output drops: 0
Queueing strategy: fifo
. . .
By reviewing these outputs, you gain insight into the interactions with the devices and understand the structured data provided by pyATS, essential for integrations with other systems and workflows.
Using pyATS Parse Command for CLI Output
Introduction
In this task, you'll utilize the pyATS parse command to convert CLI command output into structured data, either in JSON format or a dictionary.
Activity Steps
- Use the parse command to parse the output of the
show interfaces
CLI command for devices R1 and R2 specified in yourmy_testbed.yaml
file. Save the output to themy_parsed_output/
directory.
pyats parse "show interfaces" --testbed-file my_testbed.yaml --devices R1 R2 --output my_parsed_output/
Parsed Output Summary
+==============================================================================+
| Genie Parse Summary for R1 |
+==============================================================================+
| Connected to R1 |
| - Log: my_parsed_output//connection_R1.txt |
|------------------------------------------------------------------------------|
| Parsed command 'show interfaces' |
| - Parsed structure: my_parsed_output//R1_show-interfaces_parsed.txt |
| - Device Console: my_parsed_output//R1_show-interfaces_console.txt |
|------------------------------------------------------------------------------|
+==============================================================================+
| Genie Parse Summary for R2 |
+==============================================================================+
| Connected to R2 |
| - Log: my_parsed_output//connection_R2.txt |
|------------------------------------------------------------------------------|
| Parsed command 'show interfaces' |
| - Parsed structure: my_parsed_output//R2_show-interfaces_parsed.txt |
| - Device Console: my_parsed_output//R2_show-interfaces_console.txt |
|------------------------------------------------------------------------------|
- Verify the parsed output to ensure it contains the required details and structure.
Use the cat
command to view the contents of the my_parsed_output//R1_show-interfaces_parsed.txt
output.
cat my_parsed_output//R1_show-interfaces_parsed.txt | more
Parsed Output Example
{
"GigabitEthernet1": {
"arp_timeout": "04:00:00",
"arp_type": "arpa",
"auto_negotiate": true,
"bandwidth": 1000000,
"counters": {
"in_broadcast_pkts": 0,
"in_crc_errors": 0,
"in_errors": 0,
"in_frame": 0,
"in_giants": 0,
"in_ignored": 0,
"in_mac_pause_frames": 0,
"in_multicast_pkts": 0,
"in_no_buffer": 0,
"in_octets": 241812350,
"in_overrun": 0,
"in_pkts": 415010,
"in_runts": 0,
"in_throttles": 0,
"in_watchdog": 0,
"last_clear": "never",
"out_babble": 0,
"out_broadcast_pkts": 0,
"out_buffer_failure": 0,
"out_buffers_swapped": 0,
"out_collision": 0,
"out_deferred": 0,
. . .
- Compare the parsed output with unstructured data by using the parse command with the
--raw
argument to output unstructured data, saving this data to themy_unparsed_output/
directory.
pyats parse "show interfaces" --testbed-file my_testbed.yaml --devices R1 R2 --raw --output my_unparsed_output/
Unparsed Output Summary
+==============================================================================+
| Genie Execute Summary for R1 |
+==============================================================================+
| Connected to R1 |
| - Log: my_unparsed_output//connection_R1.txt |
|------------------------------------------------------------------------------|
| Executed command 'show interfaces' |
| - Device Console: my_unparsed_output//R1_show-interfaces_console.txt |
|------------------------------------------------------------------------------|
+==============================================================================+
| Genie Execute Summary for R2 |
+==============================================================================+
| Connected to R2 |
| - Log: my_unparsed_output//connection_R2.txt |
|------------------------------------------------------------------------------|
| Executed command 'show interfaces' |
| - Device Console: my_unparsed_output//R2_show-interfaces_console.txt |
|------------------------------------------------------------------------------|
- Review the format of the unstructured data.
Use the cat
command to view the contents of the my_unparsed_output//R1_show-interfaces_console.txt
output.
cat my_unparsed_output//R1_show-interfaces_console.txt | more
Unparsed Output Example
+++ R1 with via 'cli': executing command 'show interfaces' +++
show interfaces
GigabitEthernet1 is up, line protocol is up
Hardware is CSR vNIC, address is 000c.2992.caa8 (bia 000c.2992.caa8)
Description: MGMT - 172.21.1.20/24
Internet address is 172.21.1.21/24
MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
reliability 255/255, txload 1/255, rxload 1/255
Encapsulation ARPA, loopback not set
Keepalive set (10 sec)
Full Duplex, 1000Mbps, link type is auto, media type is Virtual
output flow-control is unsupported, input flow-control is unsupported
ARP type: ARPA, ARP Timeout 04:00:00
Last input 00:00:00, output 00:00:00, output hang never
Last clearing of "show interface" counters never
Input queue: 1/375/0/0 (size/max/drops/flushes); Total output drops: 0
Queueing strategy: fifo
. . .
This example showcases the versatility of the parse command in pyATS, converting CLI output into structured data, which is crucial for effective automation and analysis of network configurations.
Verifying Baseline Snapshots and Comparing Configuration States
Introduction
Before making changes to network configurations, it's crucial to verify existing baselines and compare them with subsequent snapshots. In this task, you'll utilize pyATS to verify and compare baseline snapshots of network configurations.
Activity Steps
- Verify the existing baseline, or “Day 1,” snapshot of the working network configuration for your devices.
Use the ls
command to view the contents of the my_snapshot_day1
directory.
ls my_snapshot_day1
Output:
connection_R1.txt interface_iosxe_R1_console.txt interface_iosxe_R2_ops.txt
connection_R2.txt interface_iosxe_R1_ops.txt interface_iosxe_R3_console.txt
connection_R3.txt interface_iosxe_R2_console.txt interface_iosxe_R3_ops.txt
- Take a second, or “Day 2,” baseline snapshot of questionable network configuration for your devices.
Use the learn command to take a second baseline snapshot of your network configuration.
pyats learn interface --testbed-file my_testbed.yaml --output my_snapshot_day2
Day 2 Snapshot Summary:
+==============================================================================+
| Genie Learn Summary for device R1 |
+==============================================================================+
| Connected to R1 |
| - Log: my_snapshot_day2/connection_R1.txt |
|------------------------------------------------------------------------------|
| Learnt feature 'interface' |
| - Ops structure: my_snapshot_day2/interface_iosxe_R1_ops.txt |
| - Device Console: my_snapshot_day2/interface_iosxe_R1_console.txt |
|==============================================================================|
. . .
- Compare the Day 1 and Day 2 snapshots.
Use the diff command to compare the two snapshots.
pyats diff my_snapshot_day1 my_snapshot_day2 --output diff_snapshot
Diff Summary:
+==============================================================================+
| Genie Diff Summary between directories my_snapshot_day1/ and |
| my_snapshot_day2/ |
+==============================================================================+
| File: interface_iosxe_R3_ops.txt |
| - Identical |
|------------------------------------------------------------------------------|
| File: interface_iosxe_R2_ops.txt |
| - Diff can be found at diff_snapshot/diff_interface_iosxe_R2_ops.txt |
|------------------------------------------------------------------------------|
| File: interface_iosxe_R1_ops.txt |
| - Identical |
|------------------------------------------------------------------------------|
- Review the differences identified in the output.
Use the cat command to view the contents of the diff_snapshot/diff_interface_iosxe_R2_ops.txt
output.
cat diff_snapshot/diff_interface_iosxe_R2_ops.txt | more
Difference Output:
--- my_snapshot_day1/interface_iosxe_R2_ops.txt
+++ my_snapshot_day2/interface_iosxe_R2_ops.txt
info:
GigabitEthernet1:
accounting:
- ipv6:
- chars_in: 280
- chars_out: 0
- pkts_in: 4
- pkts_out: 0
GigabitEthernet2:
- auto_negotiate: True
+ auto_negotiate: False
- bandwidth: 1000000
+ bandwidth: 10
- port_speed: 1000mbps
+ port_speed: 10000mbps
+ vrf: vlan12
GigabitEthernet3:
+ vrf: vlan23
Conclusion
By utilizing pyATS's learn and diff commands, you efficiently verified and compared baseline snapshots of network configurations. Identifying configuration changes between Day 1 and Day 2 snapshots ensures effective network configuration state management.
Test Network State with pyATS and Easypy
Introduction
In this task, you'll be running a job in Easypy using an example job file (half_duplex_job.py
) that references a test script (half_duplex.py
). The purpose of this script is to verify that all interfaces are operating at full duplex. You'll review the output of the job using the HTML log viewer.
Activity Steps
- Begin by locating the
half_duplex_job.py
andhalf_duplex.py
files in thepyats
directory.
ls h*py
Result
half_duplex_job.py half_duplex.py
- Open the
half_duplex.py
file in VSCode and review its sections and contents.
Common Setup Section:
###################################################################
# COMMON SETUP SECTION #
###################################################################
class CommonSetup(aetest.CommonSetup):
@aetest.subsection
def load_testbed(self, testbed):
# Convert pyATS testbed to Genie Testbed
logger.info(
"Converting pyATS testbed to Genie Testbed to support pyATS Library features"
)
testbed = load(testbed)
self.parent.parameters.update(testbed=testbed)
@aetest.subsection
def connect(self, testbed):
"""
establishes connection to all your testbed devices.
"""
# make sure testbed is provided
assert testbed, "Testbed is not provided!"
# connect to all testbed devices
# By default ANY error in the CommonSetup will fail the entire test run
# Here we catch common exceptions if a device is unavailable to allow test to continue
try:
testbed.connect()
except (TimeoutError, StateMachineError, ConnectionError):
logger.error("Unable to connect to all devices")
Testcases Section:
################################################################### # TESTCASES SECTION # ################################################################### class interface_duplex(aetest.Testcase): @aetest.setup def setup(self, testbed): """Learn and save the interface details from the testbed devices.""" self.learnt_interfaces = {} for device_name, device in testbed.devices.items(): # Only attempt to learn details on supported network operation systems if device.os in ("ios", "iosxe", "iosxr", "nxos"): logger.info(f"{device_name} connected status: {device.connected}") logger.info(f"Learning Interfaces for {device_name}") self.learnt_interfaces[device_name] = device.learn("interface").info @aetest.test def test(self, steps): # Loop over every device with learnt interfaces for device_name, interfaces in self.learnt_interfaces.items(): with steps.start( f"Looking for half-duplex Interfaces on {device_name}", continue_=True ) as device_step:
continued-
# Loop over every interface that was learnt
for interface_name, interface in interfaces.items():
with device_step.start(
f"Checking Interface {interface_name}", continue_=True
) as interface_step:
# Verify that this interface has "duplex_mode" (Virtual interfaces Lack duplex)
if "duplex_mode" in interface.keys():
if interface['duplex_mode'] == 'half':
interface_step.failed(
f'Device {device_name} Interface {interface_name} is in half-duplex mode')
else:
# If the interface has no duplex, mark as skipped
interface_step.skipped(
f"Device {device_name} Interface {interface_name} has no duplex"
)
- Open the
half_duplex_job.py
file and review its contents.
Result
from pyats.easypy import run
from half_duplex import interface_duplex
# main entry point
def main(runtime):
# run the testscript
run(testscript=interface_duplex)
Explanation:
- The
run
function frompyats.easypy
is imported to execute the test script. - The
main
function serves as the main entry point for the job file. - The test script (
half_duplex.py
) is imported, and itsinterface_duplex
testcase is executed.
- In this setup, multiple test scripts can be executed in the same environment as part of a pyATS job. Job files are fundamental for Easypy, allowing the execution of multiple test scripts as tasks in the same environment, sharing testbeds, and collectively archiving logs and results. It's an excellent method for grouping or combining similar test scripts to produce useful result summaries.
You want to use the best method for regression and sanity testing, so you run the job file in the Easypy runtime environment, where it will produce logs and archives.
$ pyats run job half_duplex_job.py --testbed-file my_testbed.yaml
This command runs the half_duplex_job.py
in the Easypy runtime environment using the testbed defined in my_testbed.yaml
. Upon completion, you should see output indicating that the job has finished:
. . .
2022-08-03T22:04:07: %EASYPY-INFO: Done!
To view the logs, you can use the following command:
$ pyats logs view
This command will launch the pyATS log viewer. From there, you can select the link on the left side of the screen for the job that you just ran (LOGxxxxxxxx
item that matches the half_duplex_job
).
Once you've selected the appropriate log, you can verify the pass-fail status of your testcase by selecting the Overview tab. This tab displays the overall status and the high-level details for the job and the testcase.
You can further review the status of the three testcase sections (common_setup
, testcase
, and common_cleanup
). By expanding the sections, you can see the output of each subsection, such as load_testbed
and connect
.
To understand how the testcase
section is set up and the tests it performs, expand and examine the interface_duplex
section. Here, you can review the output of all the steps in the testcase section, including the setup and test subsections.
Additionally, you can navigate to the Files tab in the log viewer and select the half_duplex_job.report
file. This file contains the archive location, Task Result Summary, and Task Result Details, providing insights into the artifacts created from running the job.
By completing these steps, you have successfully utilized the Easypy runtime environment, reviewed the components of the job and the testcase, and examined the log viewer archive to illustrate its utility in testing and verifying the network state.