Cisco pyATS testbed is a YAML representation of your network’s topology. It defines the devices, their connection details (such as IP addresses and credentials), and optionally their interface connectivities.
It is called a testbed because it serves as a foundational configuration for automated testing and validation of network infrastructures. However, if device connectivities are ignored, it functions similarly to an inventory in other automation tools like Netmiko and Scrapli in Nornir.
Cisco pyATS Testbed Fundamental
Cisco pyATS Testbed definition
The Cisco pyATS testbed functions similarly to an inventory in other automation tools, particularly in the way it’s used during the course.
It provides a structured listing of network devices, but its capabilities go beyond that. The testbed can also include additional details, such as the network topology, which defines how devices are interconnected. This information becomes valuable when running automated test cases, as it allows for monitoring the status of device links, verifying connectivity, and assessing traffic flow across these links.
The testbed, therefore, serves not only as an inventory but also as a dynamic framework for testing and validating the health and performance of network infrastructures.
comprehensive Testbed example in cisco pyATS
This is an example testbed from the Cisco pyATS documentation, which includes network servers such as a file server and an NTP server, along with a list of devices and their connectivity topology.
While this example showcases a comprehensive testbed with various network elements, what we primarily use during the course is a simplified inventory.
This inventory typically contains the list of devices, how to connect to each device, and the necessary credentials for establishing those connections.
pyATS Testbed Structure
This is a simple sample testbed file that includes only the “devices” section, featuring two devices: R1 and R2. The “connections” method used is “cli”, although other popular options such as “rest” and “netconf” are also available. In this lesson, we will look at a sample for each of these methods. A device can support multiple connection types.
The “credentials” section defines the “username” and “password” used to authenticate when connecting to each device. Additionally, you can specify an “enable” password if required to access privileged modes on the device.
The “os” field specifies the device’s operating system. Some common options include iosxr, iosxe, ios, nxos, and linux, among others.
---
devices:
R1:
connections:
cli:
ip: 192.168.2.91
protocol: ssh
credentials:
default:
password: rayka-co.com
username: rayka
os: iosxe
type: iosxe
R2:
connections:
cli:
ip: 192.168.2.92
protocol: ssh
credentials:
default:
password: rayka-co.com
username: rayka
os: iosxe
type: iosxe
Check and Validate the Syntax and Format of Testbed YAML Files
It’s a good idea to check both the syntax of your YAML file and the validity of your testbed file before using them.
To check the syntax of the YAML file, we use yamllint
. You can install it with the following command:
pip install yamllint
After installation, run the following command to check the syntax of your testbed.yaml file:
yamllint testbed.yaml
If no output is displayed, it means the syntax is correct. Otherwise, you will receive an error indicating where the issue is.
In addition, you can validate the format and syntax of the testbed file using the pyats validate testbed
command.
(majid) root@majid-ubuntu:/home/majid/devnet/pyats# pyats validate testbed testbed-cli.yaml ... Loading testbed file: testbed-cli.yaml -------------------------------------------------------------------------------- Testbed Name: testbed-cli Testbed Devices: . |-- R1 [iosxe/iosxe] `-- R2 [iosxe/iosxe] YAML Lint Messages ------------------ Warning Messages ---------------- - Device 'R1' has no interface definitions - Device 'R2' has no interface definitions
pyATS Testbed Connection Methods
CLI is not the only method for connecting to network devices—REST API and NETCONF are two other widely used methods in network automation.
Connection Method | Description | Commonly Supported By | Data Format |
---|---|---|---|
SSH (CLI) | Sends traditional CLI commands for automation | Most Cisco devices (routers, switches, firewalls, ISE) | Text (CLI output) |
NETCONF | Uses YANG-based structured data exchange | Cisco routers, switches, ACI (partially) | XML |
REST API | Uses web-based API calls for automation | Cisco ACI, Cisco ISE, Cisco DNA Center, modern Cisco devices | JSON (mostly), XML (sometimes) |
Many devices, such as Cisco routers and switches, support all three methods (CLI over SSH, NETCONF, and REST API) for automation. However, some platforms, like Cisco ACI, primarily support REST API and NETCONF but do not provide CLI-based automation via SSH.
Beyond connection methods, the automation approach also differs In some other aspects:
SSH automation relies on sending CLI commands.
NETCONF and REST API use structured data models (YANG).
NETCONF typically exchanges data in XML format.
REST API usually uses JSON format.
Due to these differences, you may need to use multiple connection methods when automating test cases in pyATS or similar frameworks, depending on the device or platform.
Here, you can see testbeds using different connection methods (CLI, NETCONF, and REST API).
---
devices:
R1:
connections:
cli:
ip: 192.168.2.91
protocol: ssh
credentials:
default:
password: rayka-co.com
username: rayka
os: iosxe
type: iosxe
R2:
connections:
cli:
ip: 192.168.2.92
protocol: ssh
credentials:
default:
password: rayka-co.com
username: rayka
os: iosxe
type: iosxe
---
devices:
R1:
os: iosxe
type: router
connections:
rest:
class: rest.connector.Rest
ip: 192.168.2.91
port: 443
verify: False
credentials:
rest:
username: rayka
password: rayka-co.com
--- devices: R1: os: iosxe type: router connections: netconf: class: 'yang.connector.Netconf' protocol: ssh ip: 192.168.2.91 port: 830 credentials: default: username: rayka password: rayka-co.com
Check Testbed Connectivity
The first step after creating a testbed is to ensure that connectivity to the devices configured within the testbed can be successfully established.
To test connectivity, the simplest approach is to write a small script that loads the testbed and attempts to connect to one or more devices.
Below is a sample script that demonstrates how to connect to a single device configured in the testbed using the CLI method:
By using via='cli'
in the device.connect
command, we specify the connection method to be used. Other options include device.connect(via='rest')
and device.connect(via='netconf')
, which allow connecting via REST API or NETCONF, respectively.
# Import the loader module from pyATS to handle testbed files from pyats.topology import loader # Load the testbed configuration from the specified YAML file # This file defines the devices and their connection details testbed = loader.load('2.1.testbed-cli.yaml') # Access the device named 'R1' from the testbed # 'R1' should be defined in the testbed YAML file with its connection parameters device = testbed.devices['R1'] # Establish a CLI (Command-Line Interface) connection to 'R1' # This uses the connection details specified in the testbed file device.connect(via='cli') # Perform desired operations on the device here # For example, you can execute commands or retrieve information # Disconnect the CLI session to 'R1' # It's important to close the connection after operations are complete device.disconnect()
The result indicates that the Unicon IOS-XE plugin is being used to connect to the devices.
Unicon is a connection management library developed by Cisco for pyATS and genie. It handles the establishment and management of network device connections, as well as executing commands on those devices. In upcoming lessons, we will discuss more about Unicon.
(majid) root@majid-ubuntu:/home/majid/devnet/pyats# python test-cli.py 2025-03-08 03:24:53,160: %UNICON-INFO: +++ R1 logfile /tmp/R1-cli-20250308T032453160.log +++ 2025-03-08 03:24:53,161: %UNICON-INFO: +++ Unicon plugin iosxe (unicon.plugins.iosxe) +++ 2025-03-08 03:24:53,679: %UNICON-INFO: +++ connection to spawn: ssh -l majid 10.13.14.16, id: 129709476506240 +++ 2025-03-08 03:24:53,680: %UNICON-INFO: connection to R1 (majid@10.13.14.16) Password: R1# R1# 2025-03-08 03:24:53,886: %UNICON-INFO: +++ R1 with via 'cli': executing command 'show version | include operating mode' +++ show version | include operating mode R1#
Similarly, these are sample scripts demonstrating how to connect to devices using REST API and NETCONF.
import logging import urllib3 from pyats.topology import loader import warnings warnings.filterwarnings("ignore", message="default_tokens is a deprecated argument") urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) logging.basicConfig(level=logging.DEBUG) testbed = loader.load('testbed-rest.yaml') device = testbed.devices['R1'] device.connect(via='rest') device.rest.disconnect()
import logging from pyats.topology import loader logging.basicConfig(level=logging.DEBUG) testbed = loader.load('testbed-netconf.yaml') device = testbed.devices['R1'] device.connect(via='netconf') device.netconf.disconnect()
Avoid Clear Text Password in Testbed
In a real production environment, it is generally not recommended to store clear-text passwords in the testbed file. There are multiple methods to avoid this, but one of the best approaches is to use environment variables and dynamically set the credentials in a Python script.
With this approach, the testbed file itself does not contain the actual password. Instead, we leave the credentials
field to be configured at runtime. This ensures better security while maintaining flexibility.
Here is the proper way to handle credentials securely in a pyATS testbed by leveraging environment variables instead of storing passwords in plain text.
Step 1: Define the Testbed File Without Credentials
Since the credentials
field is mandatory, we include it in the testbed file but leave the values empty.
---
devices:
R1:
connections:
cli:
ip: 192.168.2.91
protocol: ssh
credentials: {}
os: iosxe
type: iosxe
Step 2: Set Credentials Using Environment Variables
In Linux, use the export
command to define environment variables for the username and password:
export USERNAME="rayka" export PASSWORD="rayka-co.com"
This prevents passwords from being stored in configuration files.
Step 3: Use Python to Inject Credentials
In the Python script, we use os.getenv to read environment variables and set the credentials dynamically.
import os username = os.getenv('USERNAME') password = os.getenv('PASSWORD') device.credentials["default"] = {"username": username, "password": password}