So I had to write a VAPI-XP test for Mercury/HP Quality Center, and the way that this particular test type works is that you write a script in Python, VBScript, or Perl. This script must have a TestMain function that is called and as soon as that function exits the test is over and everything exits. This test had to SSH into one of the machines in our virtualized test bed, and tell the machine to netboot in order to pick up the newest build of our server product.
I could have just used a call to os.popen() to some SSH client on our intermediary testing host but I had have bad experiences with that in the past, mostly weird behavior with OpenSSH and plink. I had written some SSH code with the Twisted module Conch before so I decided to just go ahead and use that. So got the SSH code up and running no problem, but how was I to block execution until the event was triggered letting me know that the session was complete? Furthermore how was I going to get my data back without doing something dirty like a global variable?
I talked to some guys on IRC and came up with the solution below. The SSH code itself isn't that interesting and I've only really included it for completeness the important bits to note is the call to "threads.blockingCallFromThread" in RunSSHCommand and note that I'm passing the deferred that I create in "_runSSHCommand" through each of the SSH classes (usually 'd' or 'self._d') and that it is finally being called in the method "CommandChannel.closed".
#!/usr/bin/python class ClientTransport(transport.SSHClientTransport): def __init__(self, user, command, d): self._user = user self._command = command self._d = d def verifyHostKey(self, pubKey, fingerprint): #we don't care accept any host key return defer.succeed(1) def connectionSecure(self): #connection made, instiantiate the class that will handle authentication #and pass an instance of the class that embodies the user behavior as a param self.requestService(ClientUserAuth(self._user, ClientConnection(self._command, self._d))) class ClientUserAuth(userauth.SSHUserAuthClient): def getPassword(self, prompt = None): # this says we won't do password authentication return def getPublicKey(self): #return the public key which is defined up top as a string return keys.getPublicKeyString(data=SSH_PUB_KEY) def getPrivateKey(self): #return the pricate key which is also defined up top as a string return defer.succeed(keys.getPrivateKeyObject(data=SSH_PRIV_KEY)) class ClientConnection(connection.SSHConnection): def __init__(self, cmd, d, *args, **kwargs): connection.SSHConnection.__init__(self) self._command = cmd self._d = d def serviceStarted(self): self.openChannel(CommandChannel(self._command, self._d, conn=self)) class CommandChannel(channel.SSHChannel): name = 'session' def __init__(self, command, d, *args, **kwargs): channel.SSHChannel.__init__(self, *args, **kwargs) self.command = command self.d = d self.data = "" def channelOpen(self, data): #send an execute request passing the user supplied string. self.conn.sendRequest(self, 'exec', common.NS(self.command), wantReply=True ).addCallback(self._gotResponse) def _gotResponse(self, _): #command has returned, send an EOF in order to terminate the connection self.conn.sendEOF(self) def dataReceived(self, data): #append the output data to the string self.data = self.data + data def closed(self): #connection closed, execute the callback on the deferred #that we have been passing around self.d.callback(self.data) class ClientCommandFactory(protocol.ClientFactory): #factory class for our SSH protocol def __init__(self, user, command, d): self._user = user self._command = command self._d = d def buildProtocol(self, addr): protocol = ClientTransport(self._user, self._command, self._d) return protocol def RunSSHCommand(hostname, port, user, cmd): #Run an SSH command as a user on a given host, we only use key authentication thread.start_new_thread(reactor.run, (False,)) try: return threads.blockingCallFromThread(reactor, _runSSHCommand, hostname, port, user, cmd) except: return None reactor.stop() def _runSSHCommand(hostname, port, user, cmd): d = defer.Deferred() factory = ClientCommandFactory(user, cmd, d) reactor.connectTCP(hostname, 22, factory) return d if __name__ == "__main__": RunSSHCommand(hostname, port, user, "register_netboot")Print this post
No comments:
Post a Comment