pyATS Series - Tips and Tricks
Introduction
Ever dreamed of a test framework that could be used across multiple platforms, OS and vendors, which could do regression, sanity and feature testing; already used by thousands of engineers and developers worldwide? Guess what, it exists, it’s free, and you can start using it right now!
pyATS (Python Automated Test Systems, to be pronounced “py A. T. S.”) was first created as an internal project, to ease the validation of two OS versions. It has been made public in 2017 through Cisco Devnet.
Many thanks to Romain Cyrille, Cisco CX Engineer, for his help writting this article!
Other pyATS episodes
You’ve missed the first episode? You would like to read more? Below the list of published episodes:
Abstract
This article is intended to share my Tips and Tricks I wish I knew when I started to use pyATS. It’s based on my own experience and does not intend to be exhaustive. I will try to update this article frequently.
Do you have a Tip or a Trick that could benefit everyone? Feel free to share it in the comments!
pyATS installation and maintenance
This section will give general Tips and Tricks about pyATS installation and maintenance.
Installing pyATS
After checking the Requirements, you can install pyATS and pyATS librairies by running the below command.
> pip install "pyats[full]"
Veryfying the installation
You can verify pyATS has been successfuly installed by running the below command. It should return the current pyATS version, with a similar output. This command also shows if there are any available package updates.
> pyats version check
You are currently running pyATS version: 23.3
Python: 3.10.4 [64bit]
Package Version
---------------------------- -------
genie 23.3
genie.libs.clean 23.3
## output chunked for brevity ##
yang.connector 23.3
Updating pyATS
You can check if there is a newer pyATS version and update it with the below command. It should return a similar output.
> pyats version update
Checking your current environment...
The following packages will be removed:
Package Version
---------------------------- -------
genie 23.3
genie.libs.clean 23.3
## output chunked for brevity ##
unicon.plugins 23.3
yang.connector 23.3
Fetching package list... (it may take some time)
... and updated with:
Package Version
---------------------------- -------------
genie latest (23.4)
genie.libs.clean latest (23.4)
## output chunked for brevity ##
unicon.plugins latest (23.4)
yang.connector latest (23.4)
Are you sure to continue [y/N]?
pyATS Testbed
This section will give general Tips and Tricks about pyATS testbeds.
Testbed is not in the directory of execution
When the testbed cannot be found, you have a similar error as the output below. Double check the path to your testbed file. By default, pyATS is looking for a testbed file in the directory where you execute the command.
pyats.utils.yaml.exceptions.LoadError: Content of 'testbed.yaml' failed to load into a dict.
Default credentials and CLI
When credentials are shared by many devices (ex: in a lab), it could be useful to set them as default. You should add them in your testbed.yaml
file. You can still use specific credentials (i.e. non-default) for other devices in your testbed.
In the below example, xr1
and xr2
will use all default credentials. xr3
will non-default credentials.
Only credentials
can have default values. Objects like os
or connections
have to be set for each device in the testbed. Testbed and Devices configurable information can be found in the documentation.
testbed: # Default information section
credentials:
default: # Default credentials
username: cisco
password: cisco
devices:
xr1:
connections:
cli:
ip: 192.168.1.11
protocol: ssh
port: 22
os: iosxr
platform: iosxrv
type: router
xr2:
connections:
cli:
ip: 192.168.1.12
protocol: ssh
port: 22
os: iosxr
platform: iosxrv
type: router
xr3:
connections:
cli:
ip: 192.168.1.12
protocol: ssh
port: 22
credentials:
default:
username: antoine
password: cisco123
os: iosxr
platform: iosxrv
type: router
By default, the name of your device in the testbed should EXACTLY match the hostname of your device. This information is case sensitive.
pyATS Supported Operating Systems and Platforms
pyATS relies on Unicon to support network devices. They are described with their operating system os
(ex: iosxr
) and optionally their platform
(ex: spitfire
, for Cisco 8000) and model
(rarely used).
You can find supported platforms by Unicon in the pyATS documentation.
Ensure that devices you are using are accurately represented as this will serve as the source of truth for Genie Abstract as well in a near future update.
Linux Host in a Testbed
Linux host are supported by Unicon. You can add them in your testbed. Below an example of what it could look like.
my_linux_host:
os: linux
type: linux
connections:
cli:
ip: 10.10.10.10
port: 22
protocol: ssh
You can find supported platforms by Unicon in the pyATS documentation.
Using a SSH Proxy
Your device might be accessible via a proxy (ex: Bastion). You can add a proxy to your testbed and indicates for which device you should first connect to the proxy.
In the below example, I will connect to xrd1
via a proxy and to xrd2
directly. jumphost
will store information regarding how to connect to the proxy. You can name it how you want, as long at it matches with the device proxy value. Note that jumphost
you could also make jumphost
use the default credentials, as seen before.
The associated bash command would be: ssh -J [email protected] [email protected]
. The first login/ip
are the ones of the proxy, the second couple are the ones from the device.
testbed:
credentials:
default:
username: cisco
password: cisco123
devices:
jumphost:
os: linux
platform: linux
type: linux
connections:
cli:
ip: 192.168.1.1
port: 22
protocol: ssh
credentials:
default:
username: iosxr4ever
password: cisco!
xrd1:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.1
proxy: jumphost
port: 22
protocol: ssh
xrd2:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.2
port: 22
protocol: ssh
More information about how to use proxy in the pyATS documentation.
Using a SSH Tunnel
Sometimes it’s more convenient to use a SSH tunnel rather than an proxy. For example, you cannot use pyATS NETCONF plugin with a proxy, but it works with a SSH tunnel.
In the below example, I will connect to xrd1
via a SSH tunnel and to xrd2
directly. js
will store information regarding how to connect to the SSH tunnel. You can name it how you want, as long at it matches with the device sshtunnel
value.
testbed:
credentials:
default:
username: cisco
password: cisco123
servers:
js:
address: 10.10.10.10
credentials:
ssh:
username: cisco
password: cisco123
devices:
xrd1:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.1
sshtunnel:
host: js
port: 22
protocol: ssh
xrd2:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.2
port: 22
protocol: ssh
More information about how to use SSH tunnel in the pyATS documentation.
Validating a Testbed file
You can verify that there is no typo or error in your testbed file by using the below command. If your testbed has error, it should look like the below output.
> pyats validate testbed <path_to_testbed_file>
Loading testbed file: testbed.yaml
--------------------------------------------------------------------------------
YAML Lint Messages
------------------
36:24 error no new line character at the end of file (new-line-at-end-of-file)
Testbed Credentials Management
Having clear password in your testbed may not be a best practice. pyATS offers severals options to help with password management.
These options are mostly used for credentials management but in reality they can be used almost anywhere in the testbed file.
Prompting
One solution is to prompt the user for their credentials at the start of the script, this can be done very easily and directly in the testbed. The prompt can be customized or left empty for the default one to be used.
testbed:
credentials:
default:
username: "%ASK{}"
password: "%ASK{Password won't be echoed}"
This is the result when launching the pyATS script:
$python pyats_script.py
Enter value for testbed.credentials.default.username: cisco
Enter default password for testbed
Password won't be echoed:
Environment Variables
It is possible to fill your testbed using environment variables:
testbed:
credentials:
default:
username: "%ENV{PYATS_USER}"
password: "%ENV{PYATS_PASSWORD}"
Depending on your environment their are many way to set environment variables. If you are on a Linux terminal you can use the export
command for example (hint: if you put a space before the export
command it should not be saved in your shell history):
$ export PYATS_USER=cisco
$ export PYATS_PASSWORD=cisco123
$python pyats_script.py
Encrypted Text
Encryption is another option and the pyats secret
tool can help you encode and decode text. More information in the pyATS documentation.
The encrypted password can then be set in the testbed and will automatically be decrypted when the script is launched.
testbed:
credentials:
default:
username: cisco
password: "%ENC{gAAAAABdsgvwElU9_3RTZsRnd4b1l3Es2gV6Y_DUnUE8C9y3SdZGBc2v0B2m9sKV}"
Avoid printting the default commands after connecting to a device
By default, after connecting to a device, pyATS will send a bunch of exec
and configuration
level commands. It will also send logging to standard output. You can disable them by editing their respective arguments: init_exec_commands
, init_config_commands
and log_stdout
in the testbed connection
settings
parameter; like in the below example.
settings
has to be under the appropriate parameter (here cli
), otherwise it will be seen as a new connection
method.
Note that init_exec_commands
and init_config_commands
should be list of commands to use when initializating the connection. log_stdout
should be a boolean option to enable/disable logging to standard output.
xrd1:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.1
proxy: jumphost
port: 22
protocol: ssh
settings:
init_exec_commands: []
init_config_commands: []
log_stdout: False
It’s not recommended to use init_exec_commands: []
as your device might not have terminal width
and terminal length 0
commands. You could have issues with long outputs.
More information about the device connect()
method in the pyATS documentation.
Disconnecting quickly
When you disconnect from a device, using the disconnect()
method, Unicon will wait about 10 seconds. The documentation says this is to prevent connection issues on rapid connect/disconnect sequences. It can be annoying when you script connect()
and disconnect()
from many devices.
To change the default timers, you can change the GRACEFUL_DISCONNECT_WAIT_SEC
and POST_DISCONNECT_WAIT_SEC
in the testbed connection
settings
parameter; like in the below example.
xrd1:
os: iosxr
platform: iosxrv
type: router
connections:
cli:
ip: 10.10.10.1
proxy: jumphost
port: 22
protocol: ssh
settings:
GRACEFUL_DISCONNECT_WAIT_SEC: 0
POST_DISCONNECT_WAIT_SEC: 0
More information about the device disconnect()
method in the pyATS documentation.
Using pyATS with Python
Mismatch between the hostname and the keys in the testbed
The name of the device in your testbed, and the hostname MUST match. It’s case sensitive. In case it doesn’t match, you will have a similar error.
unicon.core.errors.TimeoutError: Prompt timeout occured, please check the hostname
You can also specify in your Python script that you do not care if they don’t match by setting the learn_hostname
argument to True
in your device connect()
method.
device.connect(learn_hostname=True)
More information about the device connect()
method in the pyATS documentation.
Avoid printting the default commands after connecting to a device
By default, after connecting to a device, pyATS will send a bunch of exec
and configuration
level commands. It will also send logging to standard output. You can disable them by editing their respective arguments: init_exec_commands
, init_config_commands
and log_stdout
in the device connect()
method; like in the below example.
device.connect(init_exec_commands=[],
init_config_commands=[],
log_stdout=False)
It’s not recommended to use init_exec_commands: []
as your device might not have terminal width
and terminal length 0
commands. You could have issues with long outputs.
More information about the device connect()
method in the pyATS documentation.
Parsing an already saved output
If you already have an output (ex: in a text file) that you would like to parse with pyATS, you can use the below commands. You first need to load a device from a testbed, so the pyATS librairies know from which OS it should look for the appropriate parser.
from genie.testbed import load
device = load('./testbed.yaml')['device']
with open('./output.txt') as file:
output = file.read()
device.parse('<show command>', output=output)
Getting the password as plaintext
By default, when you try to retrieve a device’s password using the device.credentials.default.password
attribute, it will return a line of *
.
from genie import testbed
testbed = testbed.load('./testbed.yaml')
device = testbed.devices['xrd1']
print(device.credentials.default.password)
The above Python script would return: ************************
.
If you would like to retrieve the password as plaintext, you can use the plaintext
attribute of the device.credentials.default.password
object.
from genie import testbed
testbed = testbed.load('./testbed.yaml')
device = testbed.devices['xrd1']
print(device.credentials.default.password.plaintext)
The above Python script would return Cisco123
(or whatever you used as password).
Handling Dialogs
It could happen that you need to handle a specific dialog with pyATS (ex: you’re connecting to a linux host and would like to use a command that requires sudo
privileges). In this case, the device might ask you for a password.
You can use the Dialog
and Statement
Classes of the unicon.eal.dialogs
package to handle it.
In the below example, we are expecting the device to send us [sudo] password for cisco:
line, when using the command sudo traceroute 10.3.1.3
. Will will use the action 'fsendline("{source.credentials.default.password.plaintext}")'
which will send the device’s password as plaintext.
In the below example, pattern
will match a regex. Don’t forget to escape special characters like ([?
if needed.
from genie import testbed
from unicon.eal.dialogs import Statement, Dialog
testbed = testbed.load('./testbed.yaml')
device = testbed.devices['source']
dialog = Dialog([
Statement(pattern=r'.*[sudo] password for cisco:',
action=f'sendline("{source.credentials.default.password.plaintext}")',
loop_continue=True,
continue_timer=False)
])
device.connect()
traceroute = device.execute('sudo traceroute 10.3.1.3', reply=dialog)
print(traceroute)
device.disconnect()
You can read more about Dialog
and Statement
Classes in the Common Service documentation.
Conclusion
In this fifth episode of the pyATS series, we saw a bunch of useful Tips and Tricks based on my experience with pyATS.
The code used for each blog post can be found here. This link will include the code for all posts.
Resources
Below a few useful pyATS resources.
Leave a Comment