tags:

views:

267

answers:

3

The class below is designed to manipulate a cisco-like device interface, for the purpose of executing commands and updating configuration elements.

As is currently stands, I can instantiate the class, make a call to the ssh_to_aos_expsh function and get back valid output (e.g. get the config when the command is 'show running-config'). However, when I make a call to the ssh_to_aos_config function (which calls the ssh_to_aos_expsh function), I get a pexpect timeout error.

I've compared the pexpect object (the 'child' in the _ssh_connect, ssh_to_aos_expsh and ssh_to_aos_config) returned by _ssh_connect to ssh_to_aos_expsh to the object returned by ssh_to_aos_expsh to ssh_toaos_config and it appears to be at the same memory location, so I'm not clear why I can't continue to manipulate the object with pexpect.

I'm not the most sophisticated python coder, so it's possible I've made some inadvertent mistake whilst trying to pass the pexpect object between functions, and if so, I would appreciate someone pointing out my mistake.

#!/usr/bin/env python

import os
import traceback

import pexpect

class SSHTool():

    def __init__(self):
        self.aos_user = 'some_user'
        self.aos_passwd = 'some_passwd'
        self.aos_init_prompt = 'accelerator>'
        self.aos_enable_prompt = 'accelerator#'
        self.aos_lnxsh_prompt = 'ACC#'
        self.linux_passwd = 'linux_passwd'
        self.root_prompt = ''

    def _timeout_error(self, child):
        print 'SSH could not login.  Timeout error.'
        print child.before, child.after
        return None

    def _password_error(self, child):
        print 'SSH could not login.  Password error.'
        print child.before, child.after
        return None

    def _ssh_connect(self, user, address, passwd):
        self.root_prompt = "root@%s's password: " % address
        ssh_newkey = "Are you sure you want to continue connecting"
        child = pexpect.spawn('ssh -l %s %s' % (user, address))
        i = child.expect([pexpect.TIMEOUT, \
                            ssh_newkey, \
                            'Password: ', \
                            self.root_prompt])
        if i == 0: # Timeout
            return self._timeout_error(child)
        elif i == 1: # SSH does not have the public key. Just accept it.
            child.sendline ('yes')
            i = child.expect([pexpect.TIMEOUT, \
                            'Password: ', \
                            self.root_prompt])
            if i == 0: # Timeout
                return self._timeout_error(child)
            else:
                child.sendline(passwd)
                return child
        elif i == 2 or i == 3:
            child.sendline(passwd)
            return child
        else:
            return self._password_error(child)

    def ssh_to_aos_expsh(self, ip_address, command = ''):
        child = self._ssh_connect(self.aos_user, \
                                    ip_address, \
                                    self.aos_passwd)
        i = child.expect([pexpect.TIMEOUT, \
                            self.aos_init_prompt])
        if i == 0:
            return self._timeout_error(child)
        child.sendline('enable')
        i = child.expect([pexpect.TIMEOUT, \
                            self.aos_enable_prompt])
        if i == 0:
            return self._timeout_error(child)
        if command:
            child.sendline(command)
            i = child.expect([pexpect.TIMEOUT, \
                                self.aos_enable_prompt])
            if i == 0:
                return self._timeout_error(child)
            else:
                return child.before
        else:
            return child

    def ssh_to_aos_config(self, ip_address, command):
        child = self.ssh_to_aos_expsh(ip_address)
        i = child.expect([pexpect.TIMEOUT, \
                            self.aos_enable_prompt])
        if i == 0:
            return self._timeout_error(child)
        child.sendline('config')
        i = child.expect([pexpect.TIMEOUT, \
                            self.aos_config_prompt])
        if i == 0:
            return self._timeout_error(child)
        child.sendline(command)
        i = child.expect([pexpect.TIMEOUT, \
                            self.aos_config_prompt])
        if i == 0:
            return self._timeout_error(child)
        else:
            return child.before
A: 

I would guess that the timeout happens because ssh_to_aos_config() does not get all the input it expects: the call to ssh_to_aos_expsh() might well work, while subsequent calls to expect would not.

So a question would be: where does the timeout happen? You could track this by raising an exception instead of returning self._timeout_error(child). The location you find would point to an input that pexpect never gets (hence the timeout), and you could update your code there.

EOL
A: 

If you are getting a timeout it is because you are not getting any of the strings you are expecting. It may be that you are getting an error message instead, or the prompt you are expecting is wrong.

Enable logging to see the whole interaction - in pexpect 2.3 this is done by assigning a file object to the child.logfile attribute - then you can see exactly what is happening. Check the docs for earlier versions, since I think this has changed.

I notice a couple of things in your code:

1) the root_prompt is an empty string. This will always match immediately, even if nothing has been returned from the client. This may be the cause of your problem - the ssh connect function thinks it has seen the prompt and successfully logged in, while the client is still waiting for some other input.

2) there is a syntax error in your code - in ssh_connect you have the sequence:

if i == 0: # Timeout
    return self._timeout_error(child)
else:
    child.sendline(passwd)
    return child
elif i == 2 or i == 3:
    child.sendline(passwd)
    return child
else:
    return self._password_error(child)

The elif does not match up with an if statement, so AFAIK this would never compile. I presume it is a cut & paste error, since you say you have been running the code.

Dave Kirby
Actually, self.root_prompt is set when _ssh_connect is called. Since the root prompt is different for each device I connect to, I need to build it dynamically.I fixed the indentation problem in the _ssh_connect, it was just an artifact of how I pasted the text in.
Rob Carr
+1  A: 

It turns out that there were two problems, both easy to fix once I knew what the issues were. First, the __init__ method contains no self.aos_config_prompt - something which the pexpect exception stated pretty clearly when I commented out my exception handling code. Second, given a self.aos_config_prompt that looked like 'accelerator(config)#', pexpect compiles that into re module matching code, which will only then match a prompt containing the contents of the parentheses. Simply escape the parentheses in the string and the match works as desired.

Rob Carr