Paramiko hangs when running on 150+ servers while running simple commands - python

I'm trying to run multiple commands on bunch of remote servers using python's Paramiko module.
The commands I'm trying to run are simple commands, such as cat, lspci(with grep) and also small script with only 1 line output.
The thing is, if I provide few machines (~50), it works just fine.
The problem starts when I try run the script on many machines.
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, username='root', password='pass')
transport = ssh.get_transport()
channel = transport.open_session()
stdin, stdout, stderr = ssh.exec_command(cmd)
for line in stdout.readlines():
line = line.strip()
sheet1.write(row_line,0,host,style_cell) # writing to xls file
sheet1.write(row_line,1,line,style_cell) # writing to xls file
while channel.recv_ready():
channel.recv(1024)
ssh.close()
expect:
print stdout
print stderr
This is the stdout,stderr I get:
paramiko.ChannelFile from paramiko.Channel 2 (EOF received) (open) window=2097152
paramiko.Transport at 0xce44c9d0L (cipher aes128-ctr, 128 bits) (active; 2 open channel(s))
Please advice,
Thanks!

Maybe it's a problem with specific servers. Try outputting which server it is that causes errors and see if you can recreate the problem if running the script only with that specific server.

Related

Extract and save Cisco devices output with Python

so I m very new to python
I have a devices.txt file that includes all the IPs and nothing else (ex 10.10.10.1, 10.10.10.2 etc ..)
I managed to get the output I want but I cannot find a way to get the script to go through the list and save the output to a txt file
so far I have
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#bypass the key missing issue
ssh.connect('10.10.10.1', username='cisco', password='xxxxxxx')
#connect to device
stdin, stdout, stderr = dssh.exec_command('sh ip route')
#execute the Cisco command on the device
output = stdout.readlines()
print(output)
#print the output on the command above
if ssh.get_transport().is_active() == True:
print('Closing Connection')
ssh.close()
#Check if the connection is still open and then close it
So yeah ..
any idea how to grab the IP's from that list instead of me running manually changing the IP and running the script and ..also .. saving the output in a txt file (1 file for all the devices)
Thanks !!!

paramiko equivalent of "cat File.gz | ssh addres script.sh" in python 3.7

Command i'm trying to run using paramiko in python 3.7:
Windows:
type file.ext4.gz | ssh user#address sudo update.sh
Mac:
cat file.ext4.gz | ssh user#address sudo update.sh
From the cmd / terminals and from .bat / .sh this works, after entering the password. I've been working on a simple python gui (PysimpleGui) to allow the user to fo this, but without the need to enter the password (this is saved from initial connection).
I've tried:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(config["IP_ADDRESS"], username=config["USERNAME"], password=config["PASSWORD"], timeout=5)
a = client.open_sftp()
a.put(file_location, "sh update.sh", callback=sent)
While this works to send the file, it doesn't run it and gives the error:
OSError: Failure
I don't want to do this in subprocess, as this tool is to prevent the use of terminal for the "end user"
I've been beating my head against this for 2 days now. Thank you.
EDIT:
Here is the STDIO Code:
def send_ssh(value, input=None):
if input:
transport = client.get_transport()
channel = transport.open_session()
channel.exec_command(value)
with open(input, "rb") as file:
for chunk in iter(functools.partial(file.read, read_size), b''):
if channel.send_ready():
channel.sendall(chunk)
if channel.recv_ready():
print(channel.recv(1024).decode().strip())
if channel.recv_stderr_ready():
print(channel.recv_stderr(1024).decode().strip())
while not channel.exit_status_ready():
if channel.recv_ready():
print(channel.recv(1024).decode().strip())
if channel.recv_stderr_ready():
print(channel.recv_stderr(1024).decode().strip())
else:
w, r, e = client.exec_command(value, get_pty=True)
error = e.read().strip().decode()
if error != "":
return error
else:
return r.read().strip().decode()
Once the file is cat to the script it's the verified by the script. I worked around this by just using SFTP to send the file and running my
cat file | sudo script.sh
this works, but does require that i transfer a 600mb file (thankfully always over a local connection (LAN)) each time. The above code does transfer the file, but it doesn't complete. If i just try sending it via for line in file: i'll corrupt.
Keeping things simpler, below we're using threading to allow synchronous APIs to be used rather than needing to write explicit asynchronous code:
import shutil
client = SSHClient()
client.load_system_host_keys()
client.connect('user#address')
# here's the important part: we're using the file handles returned by exec_command()
update_stdin, update_stdout, update_stderr = client.exec_command('sudo update.sh')
# copy stdout and stderr from the remote thread to our own process's stdout and stderr
t_out = Thread(target=shutil.copyfileobj, args=[update_stdout, sys.stdout]); t_out.start()
t_err = Thread(target=shutil.copyfileobj, args=[update_stderr, sys.stderr]); t_err.start()
# write your local file to the remote stdin, in the foreground: we don't exit until done.
shutil.copyfileobj(open('file.ext4.gz', 'r'), update_stdin)
update_stdin.close()
# optional, but let's be graceful: wait for the threads to exit, and collect exit status
t_out.join(); t_err.join()
result = stdout.channel.recv_exit_status()
print(f"Remote process exited with status {result}")

How to wait for a process that has been started on a remote computer using ssh in a script?

I have a script that, in a specific case is required to run a very time-consuming script on a remote computer. I currently do this with:
if specific_case:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('10.0.0.1', username='username', password='password')
stdin, stdout, stderr = client.exec_command('tmux new-session -d -s session_name \'python /home/username/flamethrower.py'\'')
Now - so far so good.. How could I wait for python /home/username/flamethrower.py in the script that initially started it and possibly have a minimalistic feedback?
The computer running the script that starts flamethrower.py on the remote computer is a windows 8.1, the computer where flamethrower.py is run, is a Debian wheezy.
pseudo coded:
[...] # as above
while not stdin, stdout, stderr = client.exec_command('tmux new-session -d -s session_name \'python /home/username/flamethrower.py'\''):
#while it's not done, print a dot every minute
sys.stdout.print('.')
sys.flush()
sleep(60)
Have your remote script output to some log file, including when finished, then poll the remote connection every so often for the finish output, (and fetch the remaining output).

get_pty() via sshClient sometimes hangs forever with paramiko

In an automation script, i need to ssh into several servers to gather some data. Unfortunately, few of them seems to have intermittent issues where the connection hangs forever on get_pty().
Here's a snippet of my code. Is there something I can do to kill the connection? I'm using python of course, and unfortunately with python, there is no easy way to kill a thread either :(
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connect(hostname, port=port, username=username, password=password, timeout=30)
self.__chan = self.__client.get_transport().open_session()
print self.__chan
print ' after'
print self.__chan.get_pty()
output
<paramiko.Channel 1 (open) window=0 -> <paramiko.Transport at 0xd49410L (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>
after
I believe the issue has to do with a failed attempt to get_transport() or open_session() properly. If so, how can i detect if an issue occurred?
Per the documentation for paramiko regarding get_pty, "It isn’t necessary (or desirable) to call this method if you’re going to exectue a single command with exec_command." http://docs.paramiko.org/en/1.13/api/channel.html#paramiko.channel.Channel.get_pty
Do you have a specific reason for invoking the pseudo-terminal? If you use paramiko.client.SSHClient.exec_command you do not need to invoke one. Depending on your version of paramiko, exec_command also accepts a timeout parameter so that the command will only block for a certain amount of time. Should your version not allow this parameter, you can subclass the client to add it.
from paramiko import SSHClient
class SSHClient_with_Timeout(SSHClient):
## overload the exec_command method
def exec_command(self, command, bufsize=-1, timeout=None):
chan = self._transport.open_session()
chan.settimeout(timeout)
chan.exec_command(command)
stdin = chan.makefile('wb', bufsize)
stdout = chan.makefile('rb', bufsize)
stderr = chan.makefile_stderr('rb', bufsize)
return stdin, stdout, stderr
If that's not the solution you're looking for, you could also set up logging. paramiko logs to the logger "paramiko", so invoking logging.basicConfig(level=logging.DEBUG) will allow you to see what paramiko is up to right before the 'hang.'

Python paramiko module using multiple commands

I have a class that creates the connection. I can connect and execute 1 command before the channel is closed. On another system i have i can execute multiple commands and the channel does not close. Obviously its a config issue with the systems i am trying to connect to.
class connect:
newconnection = ''
def __init__(self,username,password):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect('somehost', username=username,password=password,port=2222,timeout=5)
except:
print "Count not connect"
sys.exit()
self.newconnection = ssh
def con(self):
return self.newconnection
Then i use 'ls' command just to print some output
sshconnection = connect('someuser','somepassword').con()
stdin, stdout, stderr = sshconnection.exec_command("ls -lsa")
print stdout.readlines()
print stdout
stdin, stdout, stderr = sshconnection.exec_command("ls -lsa")
print stdout.readlines()
print stdout
sshconnection.close()
sys.exit()
After the first exec_command runs it prints the expected output of the dir list. When i print stdout after the first exec_command it looks like the channel is closed
<paramiko.ChannelFile from <paramiko.Channel 1 (closed) -> <paramiko.Transport at 0x2400f10L (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>>>
Like i said on another system i am able to keep running commands and the connection doesn't close. Is there a way i can keep this open? or a better way i can see the reason why it closes?
edit: So it looks like you can only run 1 command per SSHClient.exec_command... so i decided to get_transport().open_session() and then run a command. The first one always works. The second one always fails and the scripts just hangs
With just paramiko after the exec_command executes the channel is closed and the ssh returns an auth prompt.
Seems its not possible with just paramiko, try fabric or another tool.
** fabric did not work out too.
Please see the following referece as it provides a way to do this in Paramiko:
How do you execute multiple commands in a single session in Paramiko? (Python)
it's possible with netmiko (tested on windows).
this example is written for connecting to cisco devices but the principle is adaptable for others as well.
import netmiko
from netmiko import ConnectHandler
import json
def connect_enable_silent(ip_address,ios_command):
with open ("credentials.txt") as line:
line_1 = json.load(line)
for k,v in line_1.items():
router=(k,v)
try:
ssh = ConnectHandler(**router[1],device_type="cisco_ios",ip=ip_address)
ssh.enable()
except netmiko.ssh_exception.NetMikoAuthenticationException:
#incorrect credentials
continue
except netmiko.ssh_exception.NetMikoTimeoutException:
#oddly enough if it can log in but not able to authenticate to enable mode the ssh.enable() command does not give an authentication error
#but a time-out error instead
try:
ssh = ConnectHandler(username = router[1]['username'],password = router[1]['password'],device_type="cisco_ios", ip=ip_address)
except netmiko.ssh_exception.NetMikoTimeoutException:
# connection timed out (ssh not enabled on device, try telnet)
continue
except Exception:
continue
else:
output = ssh.send_command(ios_command)
ssh.disconnect()
if "at '^' marker." in output:
#trying to run a command that requires enble mode but not authenticated to enable mode
continue
return output
except Exception:
continue
else:
output = ssh.send_command(ios_command)
ssh.disconnect()
return output
output = connect_enable_silent(ip_address,ios_command)
for line in output.split('\n'):
print(line)
Credentials text is meant to store different credentials in case you are planning to call this function to access multiple devices and not all of them using the same credentials. It is in the format:
{"credentials_1":{"username":"username_1","password":"password_1","secret":"secret_1"},
"credentials_2":{"username":"username_2","password":"password_2","secret":"secret_2"},
"credentials_3": {"username": "username_3", "password": "password_3"}
}
The exceptions can be changed to do different things, in my case i just needed it to not return an error and continue trying the next set, which is why most exceptions are silenced.

Categories

Resources