I wrote a script that telnets to some network equipment and pulls the inventory and places it in a file. I used threading to speed up the script as I have around 160 nodes. I wrote a function that telnets to each node, retrieves the inventory, writes the output to a file, then disconnects the session. The script worked great when I had those three steps under a single function. However, I decided to create a class and break those three steps into different methods. Now when I run the script, it only retrieves data from the first node in the list. I need some help figuring out why the script isn't working.
import concurrent.futures
import time
import telnetlib
from myFunctions import get_node_list_TA5K
class Ta5kTelnet:
def __init__(self):
self.tn = telnetlib.Telnet()
def connect(self, hostname, username, password):
self.tn.open(hostname, 23, 5)
self.tn.read_until(b'Username:', 5)
self.tn.write(username.encode('ascii') + b'\n')
self.tn.read_until(b'Password:', 5)
self.tn.write(password.encode('ascii') + b'\n')
if b'>' in self.tn.read_until(b'>', 5):
self.tn.write(b'en\n')
self.tn.read_until(b'#', 5)
self.tn.write(b'term len 0\n')
output = self.tn.read_until(b'#', 5)
return output
else:
print(f'{hostname} is spare shelf')
def send(self, command, waitfor):
self.tn.write(command + b'\n')
result = self.tn.read_until(waitfor, 180).decode()
return result
def disconnect(self):
self.tn.close()
tlnt = Ta5kTelnet()
def inventory(node_names):
tlnt.connect(node_names, 'username', 'password')
shelf_inventory = tlnt.send(b'show system inventory', b'#')
tlnt.disconnect()
with open(f'{node_names}_inventory.txt', 'a') as f:
f.write(shelf_inventory)
#adtran_nodes = get_node_list_TA5K()
adtran_nodes = ['BXR1-NODE1-COT1', 'BXR6-NODE1-COT6']
start = time.perf_counter()
with concurrent.futures.ThreadPoolExecutor() as executor:
results = executor.map(inventory, adtran_nodes)
print(time.perf_counter() - start)
You are sharing a single telnet connection across two threads, and there's no locking or synchronization.
There's no earthly way of knowing which order that connect, login, send, read_until sequence will be run.
Each thread needs its own connection object.
In addition, I'd refactor things to derive from telnetlib.Telnet
, and while at it, to ensure the connection gets closed correctly all the time, make it a context manager with __enter__
and __exit__
:
import concurrent.futures
import telnetlib
USERNAME = "username"
PASSWORD = "password"
class Ta5kTelnet(telnetlib.Telnet):
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def login(self, hostname, username, password):
self.open(hostname, 23, 5)
self.read_until(b"Username:", 5)
self.write(username.encode("ascii") + b"\n")
self.read_until(b"Password:", 5)
self.write(password.encode("ascii") + b"\n")
if b">" in self.read_until(b">", 5):
self.write(b"en\n")
self.read_until(b"#", 5)
self.write(b"term len 0\n")
return self.read_until(b"#", 5)
raise RuntimeError(f"{hostname} is spare shelf")
def send_command(self, command, waitfor):
self.write(command + b"\n")
return self.read_until(waitfor, 180).decode()
def get_node_inventory(node_name):
with Ta5kTelnet() as tlnt:
tlnt.login(node_name, USERNAME, PASSWORD)
shelf_inventory = tlnt.send_command(b"show system inventory", b"#")
with open(f"{node_name}_inventory.txt", "a") as f:
f.write(shelf_inventory)
adtran_nodes = ["BXR1-NODE1-COT1", "BXR6-NODE1-COT6"]
with concurrent.futures.ThreadPoolExecutor() as executor:
results = executor.map(get_node_inventory, adtran_nodes)