Python Twisted Conch - 如何使用多个连接停止Reactor?

时间:2014-10-09 22:17:57

标签: python ssh twisted

让我先从这开始......我根本不懂Python;我在圈子里,我根本就没有得到它。我完全乐于接受其他更简单的方法。

我的目标:连接到不同的服务器,在每个服务器上运行相同的命令,然后(就像现在还没有)使用输出进行生产。真棒。

我拥有的东西:在某处找到了一些代码(我会尝试找到一个链接并更新它)。我修改了一下。它连接到不同的服务器,运行相同的命令。

问题:一切都完成后,我不知道如何停止反应堆。我真的希望在不按cntrl+c的情况下停止它。我想我需要推迟一些事情,但我不知道在哪里或哪里。我觉得当SSHChannel关闭的时候,需要以某种方式冒泡到SSHConnection,停止服务......所以传输可以知道什么了?并且我一直希望以某种方式以某种方式将每个reactor.connectTCP(server, 22, factory)包裹起来。我觉得我可能需要一个控制器类。我试过这些东西,但我没有正确尝试。也许gatherResults可能会有所帮助,但是,我再也不知道该怎么说。

 
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()

我确实推迟了getPage的http请求(确实有效),但我似乎无法用反应器和ssh连接重新应用它。

这些是我真正希望能够理解的资源:


下面给出了一个答案......我测试了传递对工厂的引用并最终停止了SSHChannel closed()中的反应器,如果工厂的阵列中没有连接(或任何python调用数组)。

我现在更新工厂也包括这个方法:

class ClientCommandFactory(protocol.ClientFactory): 

    def clientConnectionLost(self, connector, reason):
        print reason

我看了一下日志,因为我一般都对正在发生的事情感兴趣...(其中一些是我自己的陈述,有些是默认的)

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.

所以......它说连接以不洁的方式丢失了。有没有更好的办法让我停下来......?

1 个答案:

答案 0 :(得分:0)

首先,这不会起作用,因为connectTCP接受一个带有IP address的字符串作为第一个参数,并且您要从此列表中传递元素:

masters = ['server1','server2','server3','server4','server5']

完成所有任务后停止反应堆是扭曲的常见用例。一种方法是存储要在工厂中执行的任务计数器。每次实例化该工厂协议的实例时,每次协议实例(任务)返回结果时,将该数字增加1,减少计数器,当计数器达到0时停止反应器。示例代码:< / p>

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, factory):
        self.username = username
        self.password = password
        self.command  = command
        self.factory = factory

    def verifyHostKey(self, pubKey, fingerprint):
        print fingerprint 
        return defer.succeed(True)

    def connectionSecure(self):
        self.requestService(
            PasswordAuth(self.username, self.password,
                         ClientConnection(self.command, self.factory)))

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
        self.factory = factory

    def serviceStarted(self):
        self.openChannel(CommandChannel(self.command, self.factory, conn=self))

class CommandChannel(channel.SSHChannel):
    name = 'session'

    def __init__(self, command, factory, *args, **kwargs):
        channel.SSHChannel.__init__(self, *args, **kwargs)
        self.command = command
        self.data = ''
        self.factory = factory
        self.factory.num_connections += 1
        self.factory.connections.append(self)

    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()
        self.factory.num_connections -= 1
        self.factory.connections.remove(self)
        if self.factory.num_connections == 0:
            reactor.stop()

    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
        self.connections = []
        self.num_connections = 0

    def buildProtocol(self, addr):
        protocol = ClientCommandTransport(
            self.username, self.password, self.command, self)
        return protocol    


masters = ['server1','server2','server3','server4','server5']

factory = ClientCommandFactory()

for server in masters:
    print server
    reactor.connectTCP(server, 22, factory)

reactor.run()

我在这里做的是将两个变量添加到工厂self.connectionsself.num_connections以存储对工厂中的连接的引用并计算连接数。然后在工厂的buildProtocol工厂将自己传递给ClientCommandTransportClientConnection又将工厂的参考传递给CommandChannel,最后将参考传递给工厂,需要的地方 - CommandChannel。每次实例化_gotResponse的实例时,它都会引用工厂,因此它将连接数增加一个,并将其自身添加到存储在工厂中的连接列表中。当完成任务/命令时,我假设Factory回调被触发。因此,无论何时触发,它都会像以前一样失去连接,但现在,它还会减少连接计数器并从工厂中删除对自身的引用。它还检查是否有任何其他打开的连接,如果没有它会停止反应堆。

我还没有测试过这段代码,但它是Twisted中的常见模式,工厂会保留一个对它创建的协议实例的引用列表,这样每个实例都可以通过工厂访问其他实例并且能够停止所有实例都完成了他们的工作。

请注意,此层次结构也有点深,ClientCommandTransport - &gt; ClientConnection - &gt; CommandChannel - &gt; self.num_connections并且我不确定它是否是将引用全部传递给工厂的最佳解决方案。

其中一个变量实际上是多余的 - 您可以只存储self.connections并增加/减少它或len(self.connections),从列表中添加/删除实例并使用{{1}}查看是否还有任何开放的联系。