Packet Pushers

Where Too Much Technology Would Be Barely Enough

  • HOME
  • Podcasts
    • Heavy Networking
    • Priority Queue
    • Network Break
    • Briefings In Brief
    • Datanauts
    • Full Stack
    • IPv6 Buzz
    • Community
  • News
  • Hosts
  • Subscribe
  • Sponsor
  • Contact

IGNITION MEMBERSMEMBER LOGIN

You are here: Home / Blogs / Using Python Context Managers for SSH connections

Using Python Context Managers for SSH connections

Pablo Lucena March 1, 2015

In this post, I will cover basic usage of Python’s context managers to connect to a network device using SSH. I will use them to abstract the connection establishment and teardown logic that is needed when making an SSH connection.

Note: This post will not cover context manager details, as great explanations can already be found online. Instead, this article is focused on showing how they can be used to facilitate the SSH connection logic to networking devices.

One of the simplest ways to connect to a network device programitically using Python is with Kirk Byer’s netmiko library. netmiko is a multi-vendor SSH Python library which makes connecting to network devices via SSH a breeze.  This library adds some vendor specific logic to paramiko, which is the de-facto SSH library in Python.  netmiko simplifies connecting to a network device via SSH and taking actions on the device – it allows you to use simple method calls such as “send_command” to execute commands on a device, and will properly parse the response according to the device being connected to. The vendor specific logic in netmiko allows the responses received from each device to be parsed according to the device type (i.e Juniper devices are parsed differently than Arista)

Context managers allow us to make the connection establishment process even simpler.  Establishing a connection to a device consists of the following workflow:

  1. Create an object representing the device we are going to connect to. This “network device” object contains attributes such as IP address, SSH port, username, password, and hostname. For example, you can read in the data from a json file containing your network device data, create a dictionary with key-value pairs,  read it from a database, etc. The end result being that we create an object with the attributes required to connect to it.
  2. Instantiate a netmiko/paramiko object to which we pass in the network device object.  The netmiko/paramiko object will use the network device attributes to establish an SSH session to the device.
  3. Establish the SSH session.
  4. Take actions on the device (send commands, parse output, etc)
  5. Disconnect from the device.

The code for the workflow described would look something like this:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Step 1 - create device object using a dictionary
net_device = dict(
     ip          = '192.168.1.254',
     device_type = 'cisco_ios',
     username    = 'admin',
     password    = 'password',
    )
 
# Step 2 - create CiscoIosSSH netmiko object
SSHClass = netmiko.ssh_dispatcher(device_type=net_device['device_type'])
 
 
# Step 3 - establish the SSH connection
ssh_connection = SSHClass(**net_device)
 
 
# Step 4 - Perform task on device
rib = ssh_connection.send_command("show ip route")
print(rib)
 
 
# Step 5 - close the connection
ssh_connection.disconnect()

This code is simple, and it works well for connecting to a single device from the Python interpreter shell. But what if you wanted to connect to hundreds of devices? What if you were writing a library with several tools which all need to the same basic function of connecting to a device via SSH? The code would start to get very repetitive if this same routine needs to be done in several places within the code base. What about error handling? You would not want your script that is processing hundreds of devices to crash due to an unhanded error, or have to have the same repetitive error handling code all over the place.

Here is where context managers come into play! Does this syntax look familiar?

1
2
with open("/etc/passwd", "r") as f:
   data = f.read()

The “open” builtin function Python was implemented to support the Context Manager protocol (called using “with”). Open can also be called in the traditional way, however we need to close the file handle manually after we are done.

1
2
3
fh = open("/etc/passwd", "r")
data = fh.read()
fh.close()

However, if there is an exception that occurs before the close() call, the program will terminate and the file handle will remain open. To take care of that we would need to use a try…except…finally block so that the file handle is closed regardless of how the program terminates (successfully or with errors).

1
2
3
4
5
6
7
try:
    fh = open("/etc/passwd", "r")
    data = fh.read()
except Exception as e:
    pass
finally:
    fh.close()

This gets ugly, repetitive, and easy to forget. Thankfully context managers take care of this administrative logic for us.  When we call open using “with”, we are calling open’s context manager. No matter what happens in the body of the “with” block, the file handle will always get closed.  Open’s context manager takes care of this for us, without us having to manually handle it in our code.

Context managers can be used for anything that requires a setup and teardown logic  in-between the actual “body” of the code.  What is our end goal when we open a file? We either want to read the contents of the file or write something to the file.  Our code will look much cleaner if we can separate the setup/teardown code (opening the file, handling errors, closing the file) from the “doing something” code (reading or writing to the file).

This is perfect for our SSH connection logic. We can make an SSH context manager that takes care of doing all of the session setup and teardown, and we can ensure that the SSH session will be closed even if exceptions are raised.  Opening an SSH session can be thought of as opening a file handle, or a socket. Our setup logic consists of building the netmiko/paramiko object, passing it our network_device object, and establishing the connection (opening the socket). If there is an error, the setup logic should be able to handle it. The teardown logic consists of closing the SSH session (i,e cleaning up, closing the socket).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from contextlib import contextmanager
import netmiko
from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException
 
@contextmanager
def ssh_manager(net_device):
    '''
    args -> network device mappings
    returns -> ssh connection ready to be used
    '''
    try:
        SSHClient = netmiko.ssh_dispatcher(device_type=net_device["device_type"])
        conn = SSHClient(**net_device)
        connected = True
    except (NetMikoTimeoutException, NetMikoAuthenticationException) as e:
        print("could not connect to {}, due to {}".format(net_device["ip"], e))
        connected = False
    try:
        if connected:
            yield conn
        else:
            yield False
    finally:
        if connected:
            conn.disconnect()

The Python standard library ships with the “contextlib” module, which has tools that make it very simple to make your own context managers. We make use of the “contextmanager” decorator to “wrap” our soon to be context manager function.  The “ssh_manager” function presented above takes care of creating the required netmiko SSH object, connecting to the end device, and handling setup errors, and closing the session. We can call our context manager as follows:

1
2
3
4
5
6
with ssh_manager(net_device) as conn:
    try:
        rib = conn.send_command("show ip route")
        print(rib)
    except Exception as e:
        print("Enountered a non setup/teardown error", e)

When the first line of this code runs :

1
with ssh_manager(net_device) as conn:

the ssh_manager context manager function is triggered.  The ssh_manager function will run up until the “yield” statement, yielding the netmiko SSH session handle. The SSH handle is stored in the “conn” variable (as conn) Now the body of the with block will run, which in our case we are sending the “show ip route” command to the network device and printing it. As soon as the with block finishes execution, control is passed back to the the ssh_manager function right after the yield statement. The rest of the ssh_manager function completes (teardown code).

Now the code in all our of functions/scripts can simply call the “ssh_manager” context manager and pass it in a network_device object with the required attributes in order to establish a connection. This greatly simplifies the rest of our code and makes it re-usable.

More information about context managers can be found on PEP 343

3 Comments

About Pablo Lucena

Comments

  1. Ian R. says

    March 3, 2015 at 5:04 pm

    This article has given me some interesting ideas. Thank you.

    Reply
    • Pablo Lucena says

      March 6, 2015 at 6:26 pm

      Glad I was able to help!

      Reply
      • oldcreek says

        May 14, 2015 at 12:29 am

        Hi, Pablo,

        Thank you for this article, it is very helpful, however I found that “conn.send_config_set” is extremely slow, I have about 100 ACL entries, it takes more than 5 minutes to push the configuration to a device directly connected to my laptop

        Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Email
  • Facebook
  • LinkedIn
  • RSS
  • Twitter
  • YouTube

RSS YouTube

  • A Data Center Down Story February 19, 2019

RSS The Weekly Show

  • Heavy Networking 430: The Future Of Networking With Guido Appenzeller February 15, 2019

RSS Priority Queue

  • PQ 161: Inside Juniper’s Programmable Silicon (Sponsored) December 13, 2018

RSS Network Break

  • Network Break 222: SnapRoute Launches Network OS; Carbonite Buys Webroot February 18, 2019

RSS Briefings In Brief

  • Tech Bytes: Thousand Eyes Shares Lessons Learned From A CenturyLink Outage (Sponsored) February 18, 2019

RSS Datanauts

  • Datanauts 158: Creating, Operating, And Collaborating On Open Source February 13, 2019

RSS Full Stack Journey

  • Full Stack Journey 028: Turning The Mic On Scott Lowe December 18, 2018

RSS IPv6 Buzz

  • IPv6 Buzz 019: IPv6 And Broadband Internet Cable Providers February 7, 2019

RSS The Community Show

  • Day Two Cloud 003: Building And Automating A Private Cloud Underlay February 20, 2019

Recent Comments

  • Martin on Fortinet Stitches New Firewalls Into Its Security Fabric
  • Ethan Banks on BiB 071: SnapRoute CN-NOS For Whitebox Focuses On Operators
  • Glenn Sullivan on BiB 071: SnapRoute CN-NOS For Whitebox Focuses On Operators
  • Ethan Banks on BiB 071: SnapRoute CN-NOS For Whitebox Focuses On Operators
  • Ethan Banks on BiB 071: SnapRoute CN-NOS For Whitebox Focuses On Operators
  • michael marrione on BiB 071: SnapRoute CN-NOS For Whitebox Focuses On Operators

PacketPushers Podcast

  • Heavy Networking
  • Network Break
  • Priority Queue
  • Briefings In Brief
  • Datanauts
  • Full Stack Journey
  • IPv6 Buzz
  • Community Podcast

PacketPushers Articles

  • All the News & Blogs
  • Only the Latest News
  • Only the Community Blogs
  • Virtual Toolbox

Search

Website Information

  • Frequently Asked Questions
  • Subscribe
  • Sponsorship
  • How To Pitch Us
  • Meet the Hosts
  • Terms & Conditions
  • Privacy Policy

Connect

  • Contact PacketPushers
  • Ask Me Anything
  • Subscribe to Podcasts
  • Sponsorship
  • Facebook
  • LinkedIn
  • RSS
  • Twitter
  • YouTube

© Copyright 2019 Packet Pushers Interactive, LLC · All Rights Reserved