You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@qpid.apache.org by Bill Freeman <ke...@gmail.com> on 2013/12/10 21:11:10 UTC

FYI: Using a paramiko channel as a (python) qpid.messaging socket

Background:

We need to run qpid.messaging code on a box (call it "control box") that
has only ssh connectivity to the box running the broker call it "broker
box").

Port forwarding using SSH does work, but the local port created can be
connected to by any process on the control box.  This is a reduction in
security since such other process would not need the ssh key nor its
pasphrase.

We are loath to install "extra" software on the broker boxes (several),
including qpid-python (from which qpid.messaging comes) or application
specific code.  Among other things, it exacerbates upgrades, since they
have to be done on many boxes.

We are actually using paramiko, a python implementation of SSH.  It turns
out that I can, inside my python app, create a paramiko "channel", which
remotely connects to a given address (such as localhost:5672, where
localhost is relative to the target of the SSH connection, the broker box),
and which behaves, inside my python app, much like a connected socket.

So I created an SSHTransport class, in the sense of those in the mold of
those in qpid.messaging.transports, and plug it into
qpid.messaging.transports.TRANSPORTS under the key 'tcp+ssh'.

(I take it that there is no approved and documented method of registering
one's own transports, but at least none of these names begin with an
underscore, so they're at least not marked as internal interfaces in the
usual python way.)

Next, when getting a connection, I pass transport='tcp+ssh' as an option,
causing the driver to use my transport.  When instantiated, my transport
establishes the ssh connection using global state (common in fabric),
though it appears I could add private options to the connection, which is
passed to the transport constructor, so one could pass the desired ssh host
and port there.  Then a channel is opened on the ssh connection, targeting
(on the remote end) the host and port specified to the
qpid.messaging.Connection constructor.  The channel object, which supports
send(), recv(), close(), and fileno(), among other things, is saved as the
"socket" attribute of my transport.  The like named methods of my transport
are little more than wrappers for the channel methods.

This almost works.  But while the paramiko channel does manage to control
the read ready state, from select's point of view, of its (one) file
description (for correct interaction with the qpid.selector.Selector
instance that qpid.messaging uses), it can't control the write ready
status.  (It's using the pipe trick - this is *nix - so the FD is the read
end of a pipe, which is never writable.)  So the AMQP connection never
lights off.

After stumbling around a bit, I settled on an additional thread that is
given a reference to the driver, and which calls writing() on the driver in
a loop, calling writeable() on the driver each time writing() returns True,
and sleeping for 0.05 seconds each time writing returns false.  This thread
is created in the transport's constructor, fishing the driver reference
from the _driver attribute (definitely an internal interface) of the
Connection instance that is passed to it.  The thread is stopped when the
transport's close() method is called.

The driver writing() method eventually winds up calling my transport's
writing method with a boolean argument that says whether the protocol
engine has anything to send.  I "and" that with the result of the channel's
send_ready() method, to be sure that the extra process sleeps to wait for
sending space (though I doubt that's ever triggered in my usage).

This works well enough for my purposes.

A sanitized (but untested after sanitization) version is attached.

Bill