Hi everyone, I am having a problem with current twisted trunk (rev 27375). I am trying to implement simple ssh client with conch.ssh but right now my implementation works wrong way. I've made a wrapper class for my client factory, to separate host/creds information and command I want to execute. So in execute method of the wrapper I am calling reactor.run() - (that blocks execute method) and in one of callbacks for success/error calling reactor.stop(). So the problem is that the first call to execute returns from reactor.run() right after reactor.stop(), but second call to same method of same instance do not return ever. I believe I am doing something wrong, so please help with find out what I've missed. Problem code attached.
from twisted.conch.ssh import transport, userauth, connection, common, keys, channel from twisted.internet import defer, protocol, reactor from twisted.python import log import sys, os, re from subprocess import *
class SshException(Exception): pass class SshClient(protocol.ClientFactory): ''' Executes a remote python code ''' def __init__(self, command, server, username, password = None, keyfile = os.path.expanduser('~/.ssh/id_dsa')): ''' Necessary information to connect ''' self.username = username self.password = password self.server = server self.keyfile = keyfile self.command = command self._deferer_ = defer.Deferred() self._transport = None def buildProtocol(self, addr): ''' Create the protocol after connection ''' self._transport = SshTransport(self.username, self.password, self.keyfile, self.command, self._deferer_) return self._transport def connect(self): reactor.connectTCP(self.server, 22, self) self._deferer_.addCallback(self.disconnectOnSuccess) self._deferer_.addErrback(self.disconnectOnError) return self._deferer_ def disconnect(self): if self._transport is not None: self._transport.loseConnection() def disconnectOnError(self, failure): self.disconnect() raise failure def disconnectOnSuccess(self, result): self.disconnect() return result def clientConnectionFailed(self, connector, reason): ''' When connection failed ''' protocol.ClientFactory.clientConnectionFailed(self, connector, reason) log.err(reason) self._deferer_.errback(reason) class SshTransport(transport.SSHClientTransport): def __init__(self, username, password, keyfile, command, _deferer_): ''' Necessary info to start SSH connection ''' self.username = username self.password = password self.keyfile = keyfile self.command = command self._deferer_ = _deferer_ self.timeout = reactor.callLater(30, self.sshTransportTimeout) def verifyHostKey(self, hostKey, fingerprint): ''' Check fingerprint ''' self.timeout.cancel() return defer.succeed(1) def connectionSecure(self): ''' User authentication ''' self.requestService( SshUserAuth(self.username, self.password, self.keyfile, self._deferer_, SshConnection(self.command, self._deferer_))) def sshTransportTimeout(self): ''' If the connection times out ''' self.loseConnection() self._deferer_.errback(SshException('408', 'Connection timed out.')) class SshUserAuth(userauth.SSHUserAuthClient): def __init__(self, username, password, keyfile, _deferer_, connection): ''' User authentication ''' userauth.SSHUserAuthClient.__init__(self, username, connection) self.keyfile = keyfile self.password = password self._deferer_ = _deferer_ def getPassword(self): ''' Password auth is not provided, give error ''' if self.password: return self.password else: self.transport.sendDisconnect(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 'no more auths') self._deferer_.errback(SshException('401', 'Wrong Key / Key authentication failed')) return def getGenericAnswers(self, name, instruction, questions): ''' Keyboard interactive password is not provided, give erro ''' self.transport.sendDisconnect(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 'no more auths') self._deferer_.errback(SshException('401', 'User authentication failed')) return defer.fail(None) def getPublicKey(self): ''' Create the public key ''' if not os.path.exists(self.keyfile) or self.lastPublicKey: raise SshException('401', 'Auth key %s doesn\'t exist' % self.keyfile) return keys.Key.fromFile(self.keyfile + '.pub') def getPrivateKey(self): ''' Create the private key ''' return defer.succeed(keys.Key.fromFile(self.keyfile)) class SshConnection(connection.SSHConnection): def __init__(self, command, _deferer_, *args, **kwargs): ''' Tracks the SSH connection ''' connection.SSHConnection.__init__(self) self.command = command self._deferer_ = _deferer_ def serviceStarted(self): ''' Runs just after the authentication finished ''' self.openChannel(SshChannel(self.command, self._deferer_, \ 2**16, 2**15, conn=self)) def serviceStopped(self): ''' Runs if the service stopped ''' class SshChannel(channel.SSHChannel): name = 'session' _buffer = '' delimiter = '\n' MAX_LENGTH = 16384 def __init__(self, command, _deferer_, *args, **kwargs): ''' SSH channel parameters ''' channel.SSHChannel.__init__(self, *args, **kwargs) self.command, self.input = command self._deferer_ = _deferer_ self.success = True self.output = '' def openFailed(self, reason): self._deferer_.errback(reason) def channelOpen(self, ignoredData): d = self.conn.sendRequest(self, 'exec', common.NS(self.command), wantReply = True) d.addCallback(self._cbRequest) def _cbRequest(self, ignored): if self.input: self.write(self.input) self.conn.sendEOF(self) def dataReceived(self, data): """ Splits the data to lines """ lines = (self._buffer+data).split(self.delimiter) self._buffer = lines.pop(-1) for line in lines: if len(line) > self.MAX_LENGTH: return self.lineLengthExceeded(line) else: self.lineReceived(line) if len(self._buffer) > self.MAX_LENGTH: return self.lineLengthExceeded(self._buffer) def lineReceived(self, line): self.output += line + self.delimiter def lineLengthExceeded(line): self.success = False self._deferer_.errback(SshException('500', 'Very long line')) def extReceived(self, dataType, data): print "500 Extended data:", dataType, data def closed(self): if self.success: self._deferer_.callback(self.output) class SimpleClient(object): def __init__(self, host, creds): self._host = host self._creds = creds self._result = None self._success = False def _on_done(self, result): self._result = result self._success = True if reactor.running: reactor.stop() def _on_error(self, failure): self._result = failure.getErrorMessage() print 'ERROR:', self._result self._success = False if reactor.running: reactor.stop() def execute(self, command): self._client = SshClient(command, self._host, self._creds) d = self._client.connect() d.addCallback(self._on_done) d.addErrback(self._on_error) reactor.run() return self._success, self._result if __name__ == "__main__": cl = SimpleClient('172.16.75.50', 'root') #first command s, r = cl.execute( ('python', 'print "Hello World"') ) print 'S:', s, 'R:', r #second command s, r = cl.execute( ('python', """import os os.system("ls /")""") ) print 'S:', s, 'R:', r
_______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python