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#
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)