pythoncsvdictionarynetmiko

Netmiko Script not connecting to multiple hosts


I'm working on a script that pulls data from a CSV file and connects to multiple hosts to run commands. The script acts like it's connecting to all of the different devices, however in reality it's only connecting to the first device in the list. What do I need to change to get this working correctly?

Example CSV file:

hostnames,device_platform,device_role
co-acc-v1.nunya.com,cisco_ios,co-acc-v
co-agg-r1.nunya.com,cisco_ios,co-agg-r
co-edg-fw1.nunya.com,cisco_asa,co-edg-fw
co-acc-sw1.nunya.com,cisco_ios,co-acc-sw
co-acc-rsw1.nunya.com,broadcom_icos,co-acc-rsw

Here's the script:

from netmiko import ConnectHandler
import concurrent.futures
import csv
import datetime
import time

username = 'Cisco'
pwd = 'Cisco123'
t1 = time.perf_counter()


def fetch_hostnames():
    with open('devices.csv', 'r') as file:
        reader = csv.reader(file)
        keys = next(reader)[1:]
        hostnames = {key: dict(zip(keys, values)) for key, *values in reader}
    return hostnames


def verification_file(filename, output, hostname):
    with open(filename, 'w') as output_file:
        output_file.write(output)
        print(f'Verification commands were successfully captured on {hostname}!')
    return


def run_verification_commands(hostname):
    today_date = datetime.datetime.now()
    year = today_date.year
    day = today_date.day
    month = today_date.month

    connection_info = {
        'port': 22,
        'username': username.lower(),
        'password': pwd,
        'secret': pwd,
        'fast_cli': False
    }

    for device, info in fetch_hostnames().items():
        platform = info['device_platform']
        print(f'Connecting to host {hostname}...')
        ssh_connection = ConnectHandler(ip=device, device_type=platform, banner_timeout=200, **connection_info)
        ssh_connection.enable()
        ssh_connection.send_command('terminal length 0', strip_prompt=False, strip_command=False)
        print(f'Generating running configuration for host {hostname}...')
        output = ssh_connection.send_command('show running-config', strip_prompt=False, strip_command=False)
        prompt_hostname = ssh_connection.find_prompt()[0:-1]
        filename = f'{prompt_hostname}_{month}_{day}_{year}_verification.txt'

        print(f'Backing up configuration for host {hostname}')
        time.sleep(1)
        verification_file(filename, output, hostname)
        ssh_connection.disconnect()
        return


with concurrent.futures.ThreadPoolExecutor() as exe:
    hosts = fetch_hostnames()
    results = exe.map(run_verification_commands, hosts)

t2 = time.perf_counter()
print(f'The script finished executing in {round(t2-t1,2)} seconds.')

Here's the output from the script:

"C:\Program Files\Python39\python.exe" "C:/Scripts/Python/NetChecks/NC/test.py"
Connecting to host co-acc-v1.nunya.com...
Connecting to host co-agg-r1.nunya.com...
Connecting to host co-edg-fw1.nunya.com...
Connecting to host co-acc-sw1.nunya.com...
Connecting to host co-acc-rsw1.nunya.com...
Generating running configuration for host co-acc-v1.nunya.com...
Generating running configuration for host co-agg-r1.nunya.com...
Generating running configuration for host co-edg-fw1.nunya.com...
Generating running configuration for host co-acc-sw1.nunya.com...
Generating running configuration for host co-acc-rsw1.nunya.com...
Backing up configuration for host co-acc-v1.nunya.com...
Backing up configuration for host co-agg-r1.nunya.com...
Backing up configuration for host co-edg-fw1.nunya.com...
Backing up configuration for host co-acc-sw1.nunya.com...
Backing up configuration for host co-acc-rsw1.nunya.com...
Verification commands were successfully captured on co-acc-v1.nunya.com!
Verification commands were successfully captured on co-agg-r1.nunya.com!
Verification commands were successfully captured on co-edg-fw1.nunya.com!
Verification commands were successfully captured on co-acc-sw1.nunya.com!
Verification commands were successfully captured on co-acc-rsw1.nunya.com!

But when the script runs and if I do a show users on co-acc-v1.nunya.com it shows me connected multiple times:

co-acc-v1#show users
    Line       User       Host(s)              Idle       Location
*  2 vty 0     cisco       idle               00:00:01     1.1.1.1
   3 vty 1     cisco       idle               00:00:02     1.1.1.1
   4 vty 2     cisco       idle               00:00:00     1.1.1.1
   5 vty 3     cisco       idle               00:00:00     1.1.1.1
   6 vty 4     cisco       idle               00:00:01     1.1.1.1
   7 vty 5     cisco       idle               00:00:01     1.1.1.1

  Interface    User               Mode         Idle     Peer Address

co-acc-v1#

Solution

  • for device, info in fetch_hostnames().items():

    This for loop is unnecessary, as this is already performing the action to each one of the hosts:

    results = exe.map(run_verification_commands, hosts)

    Every time run_verification_commands is called, regardless of what was being passed to it, because the for loop is going against the original data, it will always grab the first host.

    This can be resolved by instead passing in the data when you set up the concurrent futures:

    with concurrent.futures.ThreadPoolExecutor() as exe:
        hostinfos = fetch_hostnames().items()
        results = exe.map(run_verification_commands, hostinfos)
    

    hostinfos in this case is a list of tuples, with the tuples consisting of:

    (<hostname>, <info>)

    Then run_verification_commands can break out the hostname and the info separately:

    def run_verification_commands(hostinfo):
        hostname, info = hostinfo
    

    Now this line can be removed, with the code within being pulled back one level:

    for device, info in fetch_hostnames().items():

    And this line modified:

    ssh_connection = ConnectHandler(ip=device ... to ssh_connection = ConnectHandler(ip=hostname ...

    This will make it loop through as you're looking to do.

    One last note:

    open(filename, 'w')

    will overwrite the file each time. If you want to append, change the w (write) to an a (append)