Getting Started with Python Network Libraries for Network Engineers – Part 3

Blog Detail

This blog post is the third in a series covering common Python libraries that can be used to interact with network devices. In this post we will cover the Scrapli Python library by Carl Montanari. Per its documentation, Scrapli is the words “scrape” and “cli” (i.e. screen scrape) squished together. Its goal is to provide a thoroughly tested, well typed, well documented, simple API that supports both synchronous and asynchronous interaction with network devices.

Differentiation from Other Libraries

Scrapli is different from other libraries in the following ways:

  1. Scrapli provides multiple forms of transport. It defaults to using a system’s local SSH binary. This is in contrast to Netmiko, which uses Paramiko for transport. The forms of transport it supports can be found here along with a justification for the decision to allow for multiple transports and default to a system’s local SSH binary.
  2. Scrapli supports the same platforms as NAPALM out of the box. This is a subset of the platforms supported by Netmiko. The scrapli community project provides the ability for the scrapli community to contribute to and use drivers beyond those included in the base project. The platforms supported out of the box are:
    • Cisco IOS-XE
    • Cisco NX-OS
    • Juniper JunOS
    • Cisco IOS-XR
    • Arista EOS
  3. If you’re looking to write unit/integration tests for your code, scrapli has an adjacent library called scrapli-replay that can be used to do so.

Installation

You can install Scrapli via pip install scrapli. Or, if you are using Poetry, you can use poetry add scrapli. Other methods for installing scrapli are documented here.

Getting Connected

Note: We will only cover connecting via SSH in this blog post.

To start, you’ll want to import and instantiate one of the scrapli “core” drivers from scrapli.driver.core.

Scrapli requires a hostname (IP or DNS name) and an authentication method (generally username and password) to get connected. In addition to this, you will also want to specify whether or not to use SSH strict host key checking (we pass in False below).

>>> from scrapli.driver.core import IOSXEDriver
>>>
>>> conn = IOSXEDriver(
...     host="192.0.2.3",
...     auth_username="cisco",
...     auth_password="cisco",
...     auth_strict_key=False,
... )
>>> type(conn)
scrapli.driver.core.cisco_iosxe.sync_driver.IOSXEDriver
>>> conn.open()

Note: If host strict key checking is enabled (the default), an SSH session will be permitted only to hosts that have a key defined inside of your system’s “known_hosts” file.

You can also use the Scrapli class to dynamically select and instantiate a driver (much like ConnectHandler in Netmiko) in the following way:

>>> from scrapli import Scrapli
>>>
>>> conn = Scrapli(
...     host="192.0.2.3",
...     auth_username="cisco",
...     auth_password="cisco",
...     auth_strict_key=False,
...     platform="cisco_iosxe",
...  )
>>> type(conn)
scrapli.driver.core.cisco_iosxe.sync_driver.IOSXEDriver
>>> conn.open()

A list of the basic driver arguments can be found here.

Sending Commands

Once you have instantiated your Driver object, you can send single show commands via the .send_command() method. You will use the same command syntax you would type in if you were directly connected to the device via SSH:

>>> response = conn.send_command("show ip interface brief")
>>> print(response.result)
Interface              IP-Address      OK? Method Status                Protocol
FastEthernet0          unassigned      YES NVRAM  down                  down
GigabitEthernet1/0/1   192.0.2.3       YES unset  up                    up
GigabitEthernet1/0/2   unassigned      YES unset  up                    up
GigabitEthernet1/0/3   unassigned      YES unset  up                    up
>>> print(response.failed)
False

Likewise, you can send multiple commands using the .send_commands() method.

>>> response = conn.send_commands(["show ip interface brief", "show running-config | include hostname"])
>>> print(response.result[0])
Interface              IP-Address      OK? Method Status                Protocol
FastEthernet0          unassigned      YES NVRAM  down                  down
GigabitEthernet1/0/1   192.0.2.3       YES unset  up                    up
GigabitEthernet1/0/2   unassigned      YES unset  up                    up
GigabitEthernet1/0/3   unassigned      YES unset  up                    up
>>> print(response.result[1])
hostname cisco

You can also send commands from a file using the .send_commands_from_file() method.

# commands.txt
show ip int br
show run | i hostname
response = conn.send_commands_from_file("commands.txt")
>>> print(response.result[0])
Interface              IP-Address      OK? Method Status                Protocol
FastEthernet0          unassigned      YES NVRAM  down                  down
GigabitEthernet1/0/1   192.0.2.3       YES unset  up                    up
GigabitEthernet1/0/2   unassigned      YES unset  up                    up
GigabitEthernet1/0/3   unassigned      YES unset  up                    up
>>> print(response.result[1])
hostname cisco

If you want to run a command to edit the configuration, you would use the .send_configs() method instead. This method takes care of entering configuration mode for you, and it requires the commands be in a list or set:

>>> response = conn.send_configs(["interface Gi1/0/3", "no description"])
>>> print(response.result)
configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
cisco(config)#interface Gi1/0/3
cisco(config-if)#no description

Note: The send_configs() method doesn’t exit config mode like Netmiko does. It, instead, relies on the send_command() method to acquire the user exec mode prompt.

As is the case with .send_commands(), you can also send configurations from a file using the send_configs_from_file() method.

# config_changes.txt
interface Gi1/0/3
 no description
>>> response = conn.send_configs_from_file("config_changes.txt")
>>> print(response.result)
configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
cisco(config)#interface Gi1/0/3
cisco(config-if)#no description

Finally, there is a send_config() method that will parse the configuration provided as input and split it into lines, each line being sent to the device. This is useful for copying full configs over to the device.

Command Output Parsing

We’ve covered parsing strategies here on this blog before. Just as with Netmiko, Scrapli supports TextFSMTTP, and Genie. Let’s take a quick look on how to use them with Scrapli.

TextFSM

Scrapli defines TextFSM as an “extra”. As such, the first step to using TextFSM templates with scrapli is to install the TextFSM library. You can use pip or Poetry to do so.

pip install 'scrapli[textfsm]'
poetry add 'scrapli[textfsm]'

Once TextFSM is installed, you can use the .textfsm_parse_output() method on a Scrapli response object to return the output as parsed by the NTC template. This method uses the platform inferred by the driver to set the textfsm-platform. It combines this with the command sent to the device to guess as to which template it should use to parse the returned data.

>>> response = conn.send_command("show interfaces")
>>> print(response.textfsm_parse_output())
[{'abort': '',
  'address': '381c.1ae6.cd81',
  'bandwidth': '100000 Kbit',
  'bia': '381c.1ae6.cd81',
  'crc': '0',
  'delay': '100 usec',
...

You can see the template used for this command here.

Scrapli also supports defining the template that should be used to parse the response manually. You can import and use the textfsm_parse function from scrapli.helper to do so.

>>> from scrapli.helper import textfsm_parse
>>>
>>> response = conn.send_command("show interfaces")
>>> structured_result = textfsm_parse("/path/to/template", response.result)

TTP

Scrapli also supports the TTP parsing library. As with TextFSM, you will need to install it via pip install ttp or poetry add ttp before it can be used. The TTP installation does not currently include any templates, so you will need to find or create your own and provide the path to those templates.

>>> response = conn.send_command("show interfaces")
>>> structured_result = response.ttp_parse_output(template="show_interfaces_template.ttp")
>>> pprint(structured_result)
[[[{'description': 'CAM1',
    'interface': 'GigabitEthernet1/0/1',
    'link_status': 'up',
    'protocol_status': 'up'},
   {'description': 'CAM2',
    'interface': 'GigabitEthernet1/0/2',
    'link_status': 'up',
    'protocol_status': 'up'},
...

Genie

The last parser that Scrapli currently supports is Genie. As with TextFSM and TTP, scrapli does not install Genie nor its required library, pyATS, by default, so you will need to install them separately via pip install 'pyats[library]' or poetry add 'pyats[library]'. Athough Genie parsers exist for non-Cisco platforms, In Scrapli, Genie parsers can be used only for Cisco devices.

>>> response = conn.send_command("show interfaces")
>>> structured_result = response.genie_parse_output()
>>> pprint(structured_result)
{'GigabitEthernet1/0/1': {'arp_timeout': '04:00:00',
                          'arp_type': 'arpa',
                          'bandwidth': 100000,
                          'connected': True,
                          'counters': {'in_broadcast_pkts': 41240,
...

Note: Genie does not support custom templates.

Next Steps

  • Perhaps you’d like to use asyncio for interactions with network devices, or learn more advanced concepts? The documentation is excellent, and a great place to start.
  • If you’d like more examples of common interactions using scrapli, Carl has created a directory with such examples here.
  • Scrapli has an ecosystem including a few adjacent tools that are worth checking out (scrapli_netconf, scrapli_nornir…etc.).

Conclusion

I hope you enjoy Scrapli. It’s an excellent tool and is my personal go-to tool for new projects in which I need to automate interactions with devices for which it has a driver.

-Phillip

New to Python libraries? NTC’s Training Academy is holding a 3-day course Automating Networks with Python I on September 26-28, 2022 with 50% labs to get you up to speed.
Visit our 2022 public course schedule to see our full list.



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Getting Started with Python Network Libraries for Network Engineers – Part 1

Blog Detail

This blog post will be the first in a series covering common Python libraries that can be used to interact with network devices. In this post we will cover the Netmiko Python library by Kirk Byers. Netmiko is based on the Paramiko Python library, but whereas Paramiko was designed to interact with standard OpenSSH devices (like Linux), Netmiko was designed to interact with network devices. It has a large number of supported platforms included for connecting via SSH, and it can also accommodate limited Telnet or serial connections as well as Secure Copy (SCP) for file transfers.

Installation

You can install Netmiko via pip install netmiko. Or, if you are using Poetry, you can use poetry add netmiko.

Getting Connected

Note: We will only be covering connecting via SSH in this blog post.

Like all SSH connections, Netmiko requires a hostname (IP or DNS name) and an authentication method (generally username and password) to get connected. In addition to this, you will also need to specify the device type you will be connecting to.

>>> from netmiko import ConnectHandler
>>> 
>>> conn = ConnectHandler(
...     host="192.0.2.3",
...     username="cisco",
...     password="cisco",
...     device_type="cisco_ios"
... )

There are two ways of determining the device type: looking it up in a list or having Netmiko try to detect the device type automatically. You can see the list of current device types by digging into the code on GitHub, specifically the CLASS_MAPPER_BASE dictionary in the ssh_dispatcher.py file. If, however, you aren’t exactly sure which device type you need to choose, you can use the SSHDetect class to have Netmiko help:

>>> from netmiko import ConnectHandler, SSHDetect
>>> 
>>> detect = SSHDetect(
...     host="192.0.2.3",
...     username="cisco",
...     password="cisco",
...     device_type="autodetect"  # Note specifically passing 'autodetect' here is required
... )
>>> detect.autodetect()  # This method returns the most likely device type
'cisco_ios'
>>> detect.potential_matches  # You can also see all the potential device types and their corresponding accuracy rating
{'cisco_ios': 99}
>>> conn = ConnectHandler(
...     host="192.0.2.3",
...     username="cisco",
...     password="cisco",
...     device_type=detect.autodetect()
... )

Common Methods

Once you have instantiated your ConnectHandler object, you can send single show commands via the .send_command() method. You will use the same command syntax you would type in if you were directly connected to the device via SSH:

>>> output = conn.send_command("show ip int br")
>>> print(output)
Interface              IP-Address      OK? Method Status                Protocol
FastEthernet0          unassigned      YES NVRAM  down                  down
GigabitEthernet1/0/1   unassigned      YES unset  up                    up
GigabitEthernet1/0/2   unassigned      YES unset  up                    up
GigabitEthernet1/0/3   unassigned      YES unset  up                    up
...

Note: You can send multiple show commands back-to-back with the .send_multiline(["command1", "command2"]).

If you want to run a command to edit the configuration, you would use the .send_config_set() method instead. This method takes care of entering and exiting configuration mode for you, and it requires the commands be in a list or set:

>>> output = conn.send_config_set(("interface Gi1/0/3", "no description"))
>>> print(output)
configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
cisco(config)#interface Gi1/0/3
cisco(config-if)#no description
cisco(config-if)#end
cisco#

And since we are good network engineers, we know we should always save our configuration after making changes with the .save_config() method:

>>> output = conn.save_config()
>>> print(output)
write mem
Building configuration...
[OK]
cisco#

Command Output Parsing

We’ve covered parsing strategies here on this blog before, including the three currently supported parsers Netmiko supports which are TextFSMTTP, and Genie. Let’s take a quick look on how to use them with Netmiko.

TextFSM

If you are just starting out, the easiest way to get structured output data would be to use the included TextFSM parser. By default, Netmiko includes the TextFSM library for the parsing as well as NTC Templates to use as the default templates. To get structured output, simply add use_textfsm=True to the parameters of the .send_command() method:

>>> output = conn.send_command("show interfaces", use_textfsm=True)
>>> pprint(output)
[{'abort': '',
  'address': '381c.1ae6.cd81',
  'bandwidth': '100000 Kbit',
  'bia': '381c.1ae6.cd81',
  'crc': '0',
  'delay': '100 usec',
...

You can see the template used for this command here.

TTP

Netmiko also supports the TTP parsing library, but you will need to install it via pip install ttp or poetry add ttp first. It also does not currently include any templates, so you will need to find or create your own and then provide the path to those templates when you send your command.

Creating TTP templates yourself is definitely more of a manual process, but it gives you the freedom to pare down to only the information that you need. For example, if you just need the interface name, status, and description you can have a template like so:

{{ interface }} is {{ link_status }}, line protocol is {{ protocol_status }} {{ ignore }}
  Description: {{ description }}

And then you would reference the template path using ttp_template:

>>> output = conn.send_command("show interfaces", use_ttp=True, ttp_template="templates/show_interfaces.ttp")
>>> pprint(output)
[[[{'description': 'CAM1',
    'interface': 'GigabitEthernet1/0/1',
    'link_status': 'up',
    'protocol_status': 'up'},
   {'description': 'CAM2',
    'interface': 'GigabitEthernet1/0/2',
    'link_status': 'up',
    'protocol_status': 'up'},
...

Genie

The last parser that Netmiko currently supports is Genie. Netmiko, however, does not install Genie nor its required library, PyATS, by default, so you will need to install them separately via pip install 'pyats[library]' or poetry add 'pyats[library]'. Once they are installed, it is again very similar to enable the Genie parsing:

>>> output = conn.send_command("show interfaces", use_genie=True)
>>> pprint(output)
{'GigabitEthernet1/0/1': {'arp_timeout': '04:00:00',
                          'arp_type': 'arpa',
                          'bandwidth': 100000,
                          'connected': True,
                          'counters': {'in_broadcast_pkts': 41240,
...

Note: Genie does not support custom templates.


Conclusion

As you can see, it doesn’t take much to get started with Netmiko. If you’d like to learn more advanced interactions with Netmiko, such as transferring files via SCP, connecting via SSH keys, or even handling commands that prompt for additional input, the best place to start would be the Netmiko Examples page in the GitHub repository. Another great resource is the #netmiko channel in the Network to Code Slack.

-Joe

New to Python libraries? NTC’s Training Academy is holding a 3-day course Automating Networks with Python I on September 26-28, 2022 with 50% labs to get you up to speed.
Visit our 2022 public course schedule to see our full list.



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Contributing to Open Source Parsers

Blog Detail

As Network Engineers, we get to use plenty of open sourced tools to help with our workflows. Open source projects like Linux, Python, and Netmiko are not only built by the code maintainer—many generous engineers contribute back to the source code. And if you find yourself using any open source tools extensively, it’s good practice (and even good karma) to try to give something back to these projects. However, contributing back to something like the Python source code can seem very daunting and scary if you’ve never contributed to an open source project before. No need to fear, though, because there are projects with an easy and simple way to get started! And all you need to know is a little Python and Regex. Sound exciting? Then let’s get you started on your way to contributing to your first open source project!

Open Source Parsers

I am of course talking about the wonderful world of CLI parsers. Projects like NTC Templates and Cisco Genie Parser aimed to take raw CLI outputs from our network devices and convert them into nice and clean structured data for other programs or scripts to use. They achieve this amazing feat by using custom Regex patterns for each and every command that could possibility be sent to network devices.

If this is your first time ever hearing about the magic of CLI Parsers, then boy am I excited for you! Our very own Mikhail Yohman has created an entire series to help you dive deep into Parsing Strategies! He goes very in depth on how these parsers work under the hood, so I won’t go into specifics here.

What I do want to highlight is that these projects encourage developers to submit custom Regex patterns for commands that are not in the project currently. This is an awesome opportunity to not only give to the community, but also to get your code into a major open source project! The post will walk you through how to develop a Regex pattern that can match an entire output.

Some Advanced Regex Commands

Okay, in order to write custom Regex patterns that can grab entire outputs, we first need to go over some Regex syntax that wasn’t covered in my previous post.

Metacharacters\s\d

First up are some shortcuts we call metacharacters that are built into Regex. The two we are going to talk about are \s and \d.

\s is a shortcut for any whitespace character. This, of course, includes the space character; but it can also capture characters like tab and new line!

\d is a quick one to understand because it just means any digit character. So instead of writing [0-9], we can just drop in \d to represent the same thing.

Any metacharacter can be capitalized to indicate you want to capture the opposite. So just as \s matches a whitespace character, a capital \S matches any NON-whitespace character. Super helpful when you want to grab something that maybe contains letters and numbers!

Quantifiers{}*+

If you want to match something 1 to 3 times, you can use quantifiers {} and write {1,3}. But you don’t have to be limited to just those two numbers. If you omit the last number like {3,}, you would match a pattern three or more times.

Regex has some cool shorthand for common quantifiers. We can use + instead of {1,} to match something one or more times and * instead of {0,} for zero or more times.

Named capture groups(?P<name>)

Capture groups are a really awesome feature in Regex because it allows us to assign the data that we parse with a pattern to a number so we can refer back to it later. Named capture groups are the same thing, but they let us assign a named key to a value instead of a number. We can let Regex know we want to use a named capture group by adding a ?P<some_name> to a normal capture group (). Altogether it would look something like this: (?P<some_name>)

Let’s use the MAC address 0000.0c59.f892 to help explain. Like we discussed before, \S can match any non-whitespace character, so it is perfect for this case since MAC addresses can contain letters, numbers, and other special characters. We can then use the pattern (?P<mac_address>\S+) to capture the value and assign it a key name. This will produce the following key/value pair:

"mac_address": "0000.0c59.f892"

OR operator|

One last thing I want to go over is the OR operator. Sometimes you want to match more than one thing for a particular capture group. In those cases we can use the OR operator represented with |. So if you wanted to find Arista OR Cisco, you can use (Arista|Cisco) to search for both cases.

Let’s Try It Out

All right with all that down, let’s give it a try with an output from the command show ip arp. I like to use Regex101 to test out new parsers, so let’s drop our output in the text box.

In order to tackle the output, we just go piece by piece. Most Regex projects will parse line by line, so we can actually ignore the header of this output for now. We want to capture the value under Protocol Internet. Looks like a good candidate for \S+ for any non-whitespace character occurring one or more times.

Whoa! Everything lit up. But really that makes sense, given what \S+ means. Don’t fret though, let’s keep going. Next is the whitespace that we can capture \s+

Let’s move on to the value under address Address. We can use the handy \d+ to grab any digit one or more times and use \. to backslash out the . in the IPv4 address.

Keep in mind that using \d+ is just a quick way to represent one octet in an IPv4 address. If you are validating user input, you might want to be more precise with the pattern. We can use it in this case because a network device should not output any weird IPv4 address like 999.999.999.999.

But awesome—look at that! For the next few values, I’m going to use our friend \S+ and match the names of our keys to the headers in the output. Remember to include the \s+ for the whitespace in between values.

Looking good! That last line isn’t working because Interface has a case where there may be no output. No worries, we can account for this by using the | for cases with no output.

Awesome! Our final pattern can now match an entire line of different patterns!

Contributing to Projects

I hope that was fun! If this at all interested you, I encourage you to take a look at the contribution pages for NTC Templates and Cisco Genie Parser. Both projects use Regex in very different ways, so you’re going to have to get a little familiar with the particular project you want to contribute to before starting your first parser. Cisco Genie Parser uses the Regex library that is built into Python, which is what we walked through in the example above. NTC Templates leverages TextFSM, which has a language all its own. A lot of the same concepts apply, you just may need to get yourself familiar with TextFSM before starting.

Go More in Dept

If you want to go little more in depth than what we did here, again, Mikhail Yohman has an entire series on different parsing strategies and even talks specifically about both NTC Templates and Cisco Genie Parser.

Knox Hutchinson also has an awesome video on Genie Parser from SCRATCH, in which he walks you through the entire process of making the parser and actually creating a pull request to have it merge into the source code.


Conclusion

I hope you enjoyed this quick peek into the wonderful world of parsers. This post is just meant to give you some inspiration to make your first contribution to an open source project. It can be a rewarding and fulfilling process that I think every engineer should experience in their career. Thanks for reading, and I look forward to seeing whatever you create!

-Robert

Edit – 03/10/2021 – Another great resource you should check out is Juhi Mahajan’s fantastic step-by-step video: How to write a Genie parser for Cisco!



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!