Let me just start with this... I don't know Python at all; I am going in circles and I simply don't get it. I am completely open to alternative and easier methods.
My goal: connect to different servers, run the same command on each, and later (as in not now/yet) use the output for productive things. Awesome.
What I have: found some code somewhere (I'll try and find a link and update this). I modified it a little. It connects to different servers, runs same command.
Problem: I don't know how to stop the reactor once everything is done. And I really want to stop it without pressing cntrl+c
. I think I need to defer something but I have no idea what or where. I feel like the when the SSHChannel closes, that needs to somehow bubble up to SSHConnection, to stop the service... so the transport can know what's up? And I keep wanting to somehow wrap each reactor.connectTCP(server, 22, factory)
in a deferred somehow. And I feel like I maybe need a controller class. I tried these things but I did not try them correctly. And maybe gatherResults
might help but, again, I don't know what to put it on exactly.
from twisted.conch.ssh import transport, connection, userauth, channel, common
from twisted.internet import defer, protocol, reactor
import sys, struct
USER = 'username'
PASS = 'thisisforpersonalusesoicanstoreit!'
CMD = 'echo "merely this and nothing more"'
from twisted.python import log
import sys
log.startLogging(sys.stdout)
class ClientCommandTransport(transport.SSHClientTransport):
def __init__(self, username, password, command):
self.username = username
self.password = password
self.command = command
def verifyHostKey(self, pubKey, fingerprint):
print fingerprint
return defer.succeed(True)
def connectionSecure(self):
self.requestService(
PasswordAuth(self.username, self.password,
ClientConnection(self.command)))
class PasswordAuth(userauth.SSHUserAuthClient):
def __init__(self, user, password, connection):
userauth.SSHUserAuthClient.__init__(self, user, connection)
self.password = password
def getPassword(self, prompt=None):
return defer.succeed(self.password)
class ClientConnection(connection.SSHConnection):
def __init__(self, cmd, *args, **kwargs):
connection.SSHConnection.__init__(self)
self.command = cmd
def serviceStarted(self):
self.openChannel(CommandChannel(self.command, conn=self))
class CommandChannel(channel.SSHChannel):
name = 'session'
def __init__(self, command, *args, **kwargs):
channel.SSHChannel.__init__(self, *args, **kwargs)
self.command = command
self.data = ''
def channelOpen(self, data):
self.conn.sendRequest(
self, 'exec', common.NS(self.command), wantReply=True).addCallback(
self._gotResponse)
def _gotResponse(self, _):
self.conn.sendEOF(self)
self.loseConnection()
def dataReceived(self, data):
#self.data += data
print data
def request_exit_status(self, data):
(status,) = struct.unpack('>L', data)
# print 'exit status = ', status
class ClientCommandFactory(protocol.ClientFactory):
def __init__(self, command=CMD):
self.username = USER
self.password = PASS
self.command = command
def buildProtocol(self, addr):
protocol = ClientCommandTransport(
self.username, self.password, self.command)
return protocol
masters = ['server1','server2','server3','server4','server5']
factory = ClientCommandFactory()
for server in masters:
print server
reactor.connectTCP(server, 22, factory)
reactor.run()
I did play with deferring getPage
for an http request (which did work) but I can't seem to reapply it with reactors and ssh connections.
These are the resources I really wish that I could make sense of:
- http://twistedmatrix.com/documents/current/api/twisted.internet.defer.gatherResults.html
- http://mumak.net/stuff/twisted-disconnect.html
- Python Twisted Stopping The Reactor With Multiple Clients
- Best way to run remote commands thru ssh in Twisted?
- What is the correct way to close a Twisted conch SSH connection?
- and all the twisted class definitions..
With the one answer below... I tested out passing down a reference to the factory and ended up stopping the rector in SSHChannel
closed()
if the factory didn't have anymore connections in its array (or whatever python calls arrays).
I updated the factory to now also include this method:
class ClientCommandFactory(protocol.ClientFactory):
def clientConnectionLost(self, connector, reason):
print reason
I took a look at logging because I'm generally interested in what is happening and... (some of these are my own statements, some are default)
014-10-16 13:42:58-0500 [SSHChannel session (0) on SSHService ssh-connection on ClientCommandTransport,client] closed last TCP connection
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] service stopped
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] connection lost
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionLost'>: Connection to the other side was lost in a non-clean fashion: Connection lost.
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] ]
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] connection lost
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionLost'>: Connection to the other side was lost in a non-clean fashion: Connection lost.
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] ]
2014-10-16 13:42:58-0500 [ClientCommandTransport,client] Stopping factory <__main__.ClientCommandFactory instance at 0x02323030>
2014-10-16 13:42:58-0500 [-] Main loop terminated.
So... it says the connection was lost in an unclean way. Is there a better way I should be stopping things..?