Skip to main content

Connecting to Multiple Devices with Netmiko Using Python Threads and Queues

tl;dr – Click here to go straight to the Python example.

The journey to automation and scripting is fraught with mental obstacles, and one concept I continued to not really comprehend in Python was the concept of threading, multiprocessing, and queuing.

Python Logo

Up until recently, I felt like I basically had my dunce cap on (relatively speaking, of course) and was restricted to sequential loops and connections — in other words, I was stuck in “for i in x” loop land and could only connect to one device at a time. In order to speed up my scripts and connect to multiple devices at once (using Netmiko, for example), the path to that is through queues, and threading/multiprocessing.

Ultimately I landed on threading instead of multiprocessing because when you’re connecting to devices/APIs over the network, you’re typically waiting for a remote host to process the request, and thus your CPU is sitting there ‘idle’ waiting. To quote a great blog post that breaks down threading versus multiprocessing:

“[t]hreading is game-changing because many scripts related to network/data I/O spend the majority of their time waiting for data from a remote source. Because downloads might not be linked (i.e., scraping separate websites), the processor can download from different data sources in parallel and combine the result at the end.” (source)

While the above link has some great examples, for some reason I still didn’t quite grasp the concept of threads and queues, even after trying the example of other approaches. Why? Well, sometimes we need different perspectives to a problem because we all learn differently, thus my hope here is to provide a different perspective to threading and connecting to multiple devices with Python.

Netmiko Using Threaded Queue

I don’t want to waste to much time, so let’s just cut to the chase and get to the script:I’m going to try a different approach here, so here’s an overly verbose perspective on how the script runs. It’s a step-by-step breakdown of how it processes. That said, as much as a I tried to describe the process in a linear manner, it’s not going to be perfect.

  1. Load and stage the modules and terminal messages relating to hitting ctrl+c. (lines 6-20)
  2. Load the global variables (lines 23-38)
    1. Prompt the user to securely enter a password (var: password) (line 23)
    2. Read a list of IP addresses from text file (vars: ip_addrs_file and ip_addrs) (lines 26-27)
    3. Set up the number of threads to spin up (var: num_threads) (line 30)
    4. Set up the global queue that we’ll use to set up a list of ip addresses to be processed later (var: enclosure_queue) (line 32)
    5. Set up an object that we’ll use to lock up the screen to print the contents of a thread so as to avoid having threads print over each other later (var: print_lock) (line 34)
    6. Set up the command you’ll want to run. This is a simple one command script for the purpose of the demo. (var: command) (line 38)
  3. The two functions deviceconnector() and and main() get loaded and staged. (lines 41-107)
  4. The main() function is called and begins the execution of the main components of the script (line 112)
    1. Loop through a number list (num_threads), and for each number in that list (i): (line 92)
      1. Load a thread that runs an instance of deviceconnector() sending to the function the thread number (i) and the global queue (enclosure_queue) (line 95)
        1. deviceconnector() accepts the variables i as i, and enclosure_queue as q (line 41)
        2. deviceconnector() starts an unending while loop that: (line 45)
          1. Attempts to acquire an IP address from the global queue (line 50)
            1. If there is no IP address in the queue, the while loop will be blocked and wait until there is an ip address in the queue
          2. Sets up dictionary for Netmiko (lines 54-59)
          3. Netmiko attempts to connect to the device (lines 52-53)
            1. If there is a time out, lock the process and print a time-out message, marking the queue item as processed and restarting the while loop (lines 55-58)
            2. If there is an authentication error, print an authentication error and exit the whole script (lines 70-73)
          4. Send command to the device, lock the process and print the output (lines 76-80)
          5. Disconnect from the device, mark the queue item as complete, and loop back (lines 83-86)
      2. Set the thread to run as a daemon/in the background (line 97)
      3. Start the thread (line 99)
    2. Loop through a list of IP addresses (ip_addrs), and for each IP address (ip_addr) (lines 102-103)
      1. Add the IP address (ip_addr) to the global queue  as an individual queue item to be processed (line 103)
    3. Wait for all queue items to be processed before exiting the queue and script (line 106)
    4. Print a statement to the console indicating the script is complete (line 107)

Use this as you wish, and hope it’s helpful.

Here’s the Github version.

Credit and Additional Info

This was inspired by a few different blog posts, so here’s some additional info to follow:

  • Multiprocessing Vs. Threading In Python: What You Need To Know.
    A great breakdown of threading versus multiprocessing, and influential for some of the work I’m doing.
  • How to Make Python Wait
    This one actually reignited my interest in figuring out how to use threading. It’s a good explanation of the different approaches to make a script wait in Python.
  • Queue – A thread-safe FIFO implementation
    Although written in Python 2, this post helped me put everything together so I could understand what the heck is going on. Some of the code I used here, but refactored for Python 3. Below is a crude diagram I did to help me figure out what was going on with this post, and the circles with arrows indicate loops, with the ‘f’ in the middle meaning ‘for’ loops and ‘w’ meaning ‘while’ loops.

Diagram that attempts to show how the thread and queue process works. Too complicated to explain in an alt tag, so look at code.

15 thoughts to “Connecting to Multiple Devices with Netmiko Using Python Threads and Queues”

  1. thank you. would you know if deviceconnector will automatically connect to any kind of host whether its’s ssh or telnet?

  2. Thanks for a great work and documentation.
    Some of the challenge is the printing of result are being printed randomly.
    Not much of issue – for small list of devices, but hard to keep up with longer a longer list.

    1. @Gio Yeah, the seemingly random timing of results being printed out is because the script, in it’s current configuration, is printing output as soon as the device completes the task and disconnects. It can get a little wild with the output, and there are a few issues with it that need some addressing (e.g., for Juniper EX VC stacks, especially large ones, I use send_command_timing and have delay_factor set to 8).

      That said, I didn’t need structured output, but if you do the printing of output can certainly be customized to your needs (you could send output to a file, with the output structured with TextFSM to a dictionary that’s later (or concurrently) sent to a CSV file).

      1. I just thought i can go back here and share another way to do it. i had a project that requires about 40K devices..and been happy with results since.

        with concurrent.futures.ThreadPoolExecutor() as executor:
        executor.map(deviceconnector, list(ip_addrs))

        This substantially shrink the code, dont have to worry about the queue …what length of queue.. as they are being created and being tear down on the fly. Additionally you can remove – print lock, loop, enclosing the queue joining the queue.

        As to the logging.. i have created a function… and place to the bottom of try and all the Exception Clauses .. i can build csv to html for proper reporting ( panda for data frames and quick analysis.. how many is successful, error with SSH, Authentication issue, timed-out… yeah i meant base from those exception clause )

        def save_report(ip, errcode, status, connect_button):
        with open(my_report, ‘a+’) as getreport:
        csv_writer = csv.writer(getreport)
        csv_writer.writerows([(ip, errcode, status)])

        1. I like it! Code reduction is nice. Thanks for coming back and sharing this. Definitely going to play with this and maybe rewrite this in a new post. Admittedly, I’ve learned a bit more since I first wrote this, so this definitely could be improved.

          While the queue stuff and loops could be removed with the ThreadPoolExecutor/map method, the console printing is optional and is there to give a status if you’re running it from the console (as I was then).

  3. Hi there Jimmy,

    This script is looking like exactly what I need – although maybe slightly more complicated than what I can handle right now 😉 I’m pretty new @ Python and one thing I can’t quite grasp is the modules part. So I have Netmiko installed and working just fine, but want to make this next step to connect to multiple devices safely in one script such as you’ve created.

    Now if I look at your import section:
    from pprint import pprint
    import signal,os
    from queue import Queue
    import threading

    If I use ‘pip3 list’ I see I have none of these modules installed. However if I do a ‘pip3 search pprint’ for example it does not list it. All it does is list pprintpp – so how do I get these modules? Same goes for ‘signal’, ‘os’, ‘queue’ and ‘threading’ (if these even are modules which I’m starting to doubt more and more).

    Hope you can clear this up for me. Thanks for sharing your script and knowledge.

    Kind regards,
    Jimmy

    1. To be honest, you’ll only need to install one of those modules (IIRC): pprint. Edit: this script was intended to run in a Linux environment. If you’re running on Windows, this will need to be reconfigured.

      What I typically do is I create a Python virtual environment, which is basically an instance or container of Python that only contains the modules I need. The idea is that I don’t want my system’s Python installation to have all these modules installed but instead keep them in compartments so that I can mess with different versions. For example, Netmiko 3.0 does some things differently than 2.6 (or something to that effect), so I created different virtual environments for that.

      Here’s a good write-up on that:
      https://realpython.com/python-virtual-environments-a-primer/

      So basically I do the following:

      python3 -m venv ~/.venv/networkscripting_1_0 # This creates the venv

      Then I activate it:

      source ~/.venv/networkscripting_1_0

      Then, to get to your question, *to install these modules*, I run the following:

      python3 -m pip install pprint

      Or, you can always just install them all (it doesn’t hurt if they’re already there lies. Python will bark at you. Try anyways if you don’t know.):

      python3 -m pip install pprint queue threading

      To be honest, I don’t always know what are native modules and what’s not in Python. So to be safe, I just try to install them all.

      Also, I cheat when I’m install Netmiko and just install napalm because it usually catches everything, and sometimes I use Napalm:

      python3 -m pip install napalm

      Hopefully that helps.

  4. Hi, awesome work.

    I have a question, Is it possible to save output of different devices to separate .txt files?

    For example, I have 3 different routers added to the ips txt file:
    10.10.10.1
    10.10.10.2
    10.10.10.3

    What i do is to connect to each device (which is perfectly done by your code)
    issue the command “show ip int brief” in each one (which is also perfectly done)

    but print the results of those 3 devices command into 3 separate .txt files (taking the IP as the name of each file generated)

    Result:
    “show ip int brief” output from ips in separate files:

    10.10.10.1.txt
    10.10.10.2.txt
    10.10.10.3.txt

    Regards…

  5. maybe it could help someone, I manage to do that with this in the print zone:

    with print_lock:
    print(“{}: Printing output…”.format(i))
    open(‘your_root_txt_folder -‘ + ip + ‘.txt’, ‘w’).write(str(output))

    This writes all the threads outputs to separated .txt files adding the IP (or DNS) as the name of the file. Tried with 30 devices at the same time, obtained the “show tech” which is a big file, from all the 30 devices without issues.

    1. Great job. Looks like you figured it out.

      That’s what I have done in the past with this script.

  6. with print_lock:
    print(“{}: Printing output for {}…”.format(i,ip))
    outF = open(“{}.txt”.format(ip), “w”)
    outF.writelines(output)
    outF.close()

  7. Hi : I new to python and was wondering is there a simple scipt.
    That use tkinter in conjuction with netmiko .
    So put in IP Address.
    Then logs onto Router , then do sh ip int brief

Leave a Reply

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