Mission 4: Use pyATS to verify your automated deployment
What is pyATS and Genie?
pyATS (Python Automated Test System) is a testing framework developed by Cisco for automating network testing and validation. It helps ensure that your network devices and configurations are functioning as expected by running automated test cases. With pyATS, you can perform tasks such as verifying deployments, checking configurations, and troubleshooting issues.
Genie (Generate Easy Network Information Engine) is a library that works alongside pyATS to simplify network testing and automation. It provides:
- Parsers: Pre-built tools to extract and structure information from device outputs (e.g., CLI commands like show ip interface brief). These parsers turn raw text into structured Python objects, making it easier to analyze and manipulate the data.
- Triggers: Pre-built functions that automate specific tasks or operations on devices.
- Testbeds: A way to define and manage your network topology. Testbeds describe your network devices and their properties, such as:
- Device roles (e.g., router, switch).
- Connection details (e.g., SSH, Telnet, or Console).
- Credentials (e.g., username and password).
By defining your Testbed , you can easily manage device connections and run various tests across your network.
What do we need to use pyATS?
To use pyATS, there are a few key components you need:
-
Testbed
A testbed is a YAML file that describes your network environment. It contains information about the devices under test, such as their IP addresses, credentials, and connection methods.
Example Pod-1:
testbed: credentials: default: username: netadmin password: C1sco12345 devices: WLSN-sw1: os: iosxe connections: vty: protocol: ssh ip: 10.99.1.11
-
Test script
This is a Python script that defines what pyATS should do. It typically includes the following sections:
- CommonSetup: Prepares the environment before running the actual tests (e.g., connecting to devices).
- Testcases: Contains the actual tests or validations to be performed.
- CommonCleanup: Cleans up resources or restores the environment after the tests are done (e.g., disconnecting from devices).
Example Script: Connecting to the devices in the testbed, checking if the IOS-XE version is compliant and disconnect:
from pyats import aetest from genie import testbed class CommonSetup(aetest.CommonSetup): # Connecting to the devices in the testbed @aetest.subsection def connect(self, testbed): for device in testbed: device.connect() class Testcase(aetest.Testcase): # Testcase to verify the devices are compliant in terms of software version @aetest.test def software_version(self, testbed, steps): for device_name in testbed.devices: device = testbed.devices[device_name] os = device.parse("show version")["version"]["xe_version"] with steps.start(f"Testing IOS-XE Version on device {device_name}") as step: if not os == "17.13.01a": step.failed(f"OS is not compliant, IOS-XE version is {os}") else: step.passed("OS is compliant") class CommonCleanup(aetest.CommonCleanup): # Disconnecting from the devices @aetest.subsection def disconnect(self,testbed): for device in testbed: device.disconnect() if __name__ == "__main__": devices = testbed.load("testbed.yml") aetest.main(testbed=devices)
-
Job definition
You could execute the previous script directly, but the superior way is to create a dedicated job file. This will run the pyATS tests and automatically generate a report about all the test cases. A simple job file could look like this:
from pyats.easypy import run def main(runtime): run(testscript="testscript.py", runtime=runtime)
Netbox dynamic inventory
In the previous example, we relied on a static testbed, which was a manually defined YAML file used to describe the network environment for our pyATS tests. While this approach works, it requires frequent updates as the network changes, making it inefficient and error-prone in dynamic environments.
To overcome these challenges, we will now integrate NetBox as a dynamic source for our pyATS testbed. By dynamically querying NetBox, we can ensure that our testbed always reflects the latest network state without requiring manual intervention.
For our specific use case, we want to filter devices that are in a planned
state and belong to your pod
.
Lets explore the additions we need to make to have a dynamic Netbox inventory.
First of all we need two additional imports:
- NetBox: This import allows us to retrieve testbed data dynamically from NetBox. Instead of manually defining devices in a YAML file, we will fetch them directly from the NetBox API.
- Genie: Once we retrieve the device information from NetBox, we need to load it into pyATS as a valid testbed. Genie provides the necessary functionality to handle this conversion.
from pyats.contrib.creators.netbox import Netbox
from genie import testbed
Now that we have the required imports, the next step is to instantiate the NetBox testbed. This object will query NetBox, apply our filter criteria, and generate a testbed dynamically.
nb_testbed = Netbox(
netbox_url = os.getenv("NETBOX_API"),
user_token = os.getenv("NETBOX_TOKEN"),
def_user = "cli_user",
def_pass = "cli_password",
url_filter="status=planned&tenant=pod1", # Filter devices that are in 'planned' state and belong to 'pod1'
verify = False
)
Once we have applied our filters and queried NetBox, we need to generate the testbed from the retrieved data.
tb = nb_testbed._generate()
As we have generated the testbed, the next step is to load it into pyATS so that it can be used in our test execution.
devices = testbed.load(tb)
Now that we have successfully generated and loaded the dynamic testbed, the final step is to pass it to the test script so that it can be used during execution.
To do this, we specify the testbed
as an argument in the run()
function:
run(testscript="testscript.py", testbed=devices, runtime=runtime)
Step 1: Edit the job script to use Netbox as dynamic inventory
-
Open the file
job.py
in thepyATS
directory. -
Paste the following code into your job.py and make sure the
pod
variable is set to match your pod:from pyats.contrib.creators.netbox import Netbox from pyats.easypy import run from genie import testbed import os script_path = os.path.join(os.path.dirname(__file__), "test_network.py") def main(runtime): netbox_url = os.getenv("NETBOX_API") netbox_token = os.getenv("NETBOX_TOKEN") pod = "pod3" status = "planned" url_filter = f"status={status}&tenant={pod}" nb_testbed = Netbox( netbox_url = netbox_url, user_token = netbox_token, def_user = "netadmin", def_pass = "C1sco12345", url_filter=url_filter, verify = False ) tb = nb_testbed._generate() devices = testbed.load(tb) run(testscript=script_path, testbed=devices, runtime=runtime, netbox_url=netbox_url, netbox_token=netbox_token)
Step 2: Review the test-script
-
Open the file
test_network.py
in thepyATS
directory. -
Let's check what we are doing in there:
- CommonSetup: Our CommonSetup is handling the connection to the devices in the testbed
- Testcases: We have one simple Testcase in place. We check if the right NTP server is configured and also synchronized.
- CommonCleanup: Disconnecting from the device, and update the netbox status from
planned
toactive
if the NTP test was successful.
Step 3: Run the pyATS job and expect it to fail
Now we can run our pyATS job. At this stage we will expect it to fail because we did not onboard the switch by now. We will onboard the switch in the next Mission with a Gitlab CI/CD pipeline and then run the test to verify if the switch was deployed sucessfully.
-
Run the script:
pyats run job ./pyATS/job.py
-
This should result in a connection attempt to your switch and will eventually fail because the switch still is in factory default configuration.
Warning
The test is expected to fail as the switch is in factory configuration and has no credentials configured for authentication
-
Make sure to commit and push your changes to your Git repository:
git add . git commit -m "mission-4" git push