'Reading command output with Paramiko invoke_shell/send/recv never finishes

I am trying to use send/recv function in Paramiko. According to what I see, the line throws an exception of timeout

Evaluating: self.shell.recv(1024) did not finish after 3.00 seconds.

tmp = shell.recv(1024)

What is wrong with the function implementation? My exit condition from while True is an exception, how can I change that to exit without an exception?

Full code:

self.shell = self.SSHConnect(ip, username, password)

def SSHConnect(self, ip, username, passowrd):
    ssh = paramiko.SSHClient()
    LOGGER.debug(msg="Open SSH Client to :" + str(ip))
    try:
        ssh.set_missing_host_key_policy(policy=paramiko.AutoAddPolicy())
        ssh.connect(ip, port=22, username=username, password=passowrd, allow_agent=False, look_for_keys=True)
        if self.device_type != 'linux_host':
            session = ssh.invoke_shell()
            return session
    except Exception as ex:
        LOGGER.critical(msg="SSH Client wasn't established! Device name : " + str(self.device_name))
        return None 
    
    LOGGER.info(msg="Open SSH Client to :" + str(ip) + " established!")
    return ssh

def run_command(self,cmd):
    # New run command without throwing exception at the end:
    LOGGER.debug('Start new Run command with cmd =  ' + str(cmd))
    try:
        #Check the shell is activated before sending command:
        LOGGER.debug('Check the shell is activated before sending command: ' + cmd)
        if self.shell.get_transport().active:
            LOGGER.debug('Shell is Activated ! Running the command ')
            if self.device_type == 'linux_host':
                stdin, stdout, stderr = self.shell.exec_command(cmd)
            else:
                try:
                    #Command for switch of UFMAPL
                    LOGGER.debug('Sending command to UFMAPL/Switch with send()')
                    out = ''
                    self.shell.send(cmd)
                    while not self.shell.recv_ready():
                        time.sleep(3)
                    counter = 1
                    print('\ncommand is : ' + cmd + '\n' )
                    while True:
                        try:
                            print('iteration number is : #' + str(counter))
                            tmp = self.shell.recv(1024)
                            counter = counter + 1 
                            if not tmp:
                                break
                        except Exception as e:
                            break
                        out += tmp.decode("utf-8")
                        print('After iteration #' + str(counter) + ' out = ' + out + '\n\n')
                    ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
                    out = ansi_escape.sub('', out)
                    print('Printing final value before return : ' + str(out +'\n'))
                    return out
                except Exception as e:
                    LOGGER.error('Exception in send() : ' +str(e) )
                    return None
        else:
            LOGGER.critical('Shell is not activated !')
            return ""
    
        if stderr.read():
            LOGGER.critical('stderr is not empty which means the last command was failed, the command might not exist on host/switch ' )
            return stderr.read().decode('utf-8')
    
        out = stdout.read()
        if out:
            return out.decode("utf-8")
        else:
            LOGGER.critical('Run command sucussfully but return empty...')
            return out.decode("utf-8")
    except Exception as e:
        LOGGER.error('Exception received in run command : ' + str(e))

Logs (Printing to screen):

IM HEREEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE


command is : enable


iteration number is : #1

After iteration #2 out =
UFM Appliance

UFM is configured as standalone.
UFM mode: Management.
RAID state is: Degraded ( DRIVE1:Online,SpunUp, DRIVE2:Failed )
smg-ib-apl008-gen2 [ mgmt-sa ] >


iteration number is : #2

After iteration #3 out =
UFM Appliance

UFM is configured as standalone.
UFM mode: Management.
RAID state is: Degraded ( DRIVE1:Online,SpunUp, DRIVE2:Failed )
smg-ib-apl008-gen2 [ mgmt-sa ] > e


iteration number is : #3

After iteration #4 out =
UFM Appliance

UFM is configured as standalone.
UFM mode: Management.
RAID state is: Degraded ( DRIVE1:Online,SpunUp, DRIVE2:Failed )
smg-ib-apl008-gen2 [ mgmt-sa ] > enable
smg-ib-a


iteration number is : #4

After iteration #5 out = 
UFM Appliance

UFM is configured as standalone.
UFM mode: Management.
RAID state is: Degraded ( DRIVE1:Online,SpunUp, DRIVE2:Failed )
smg-ib-apl008-gen2 [ mgmt-sa ] > enable
smg-ib-apl00


iteration number is : #5

After iteration #6 out =
UFM Appliance

UFM is configured as standalone.
UFM mode: Management.
RAID state is: Degraded ( DRIVE1:Online,SpunUp, DRIVE2:Failed )
smg-ib-apl008-gen2 [ mgmt-sa ] > enable
smg-ib-apl008-gen2 [ mgmt-sa ] #


iteration number is : #6

As you can see the debugger is stuck on iteration #6 (freeze). Why does it freeze and doesn't send output?

Enviroment details:

  • Windows 10
  • Eclipse latest

I would appreciate any help here. Let me know if you need more details.



Solution 1:[1]

Your code gets to the point, where the server stops waiting for another input. At the same time you wait for the server to output something. What it never will. That's called a deadlock.

You might have expected some kind of signal that the first command execution has finished. There's no such signal. You are using a "shell" (SSHClient.invoke_shell). The shell is a black box with an input and an output. There are no other signals except for the output, wihhc you are reading already.

The "shell" should not be used to automate command execution. For command automation, there's the "exec" channel (SSHClient.exec_command in Paramiko).

Though I understand that with some special devices, what your server seems to be, you might not have any other option (see Executing command using Paramiko exec_command on device is not working). Moreover I'm not ever sure how the enable command works. Whether it's a command that has finished, or whether it started a kind of a new shell, which is still running and is waiting for subcommands.

So in the end all you can do is to parse the output, waiting for the command prompt (smg-ib-apl008-gen2 [ mgmt-sa ] #).


Yes, it's ugly. You are trying to automate something that was not intended for automation. Maybe your server has a better API then enable shell command, that would be nicer to automate. But that's for another question. From SSH/Python/Paramiko point of view, there's no better solution, if you need to stick with executing the enable command in the shell.


Related questions:

Though they are about more regular servers, like Linux. So they won't really help. I'm linking them just to provide broader picture and context.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1