2009-04-02 15:10:16 +08:00
==========
py.execnet
==========
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
``py.execnet`` allows to:
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
* instantiate local or remote Python Processes
* send code for execution in one or many processes
* asynchronously send and receive data between processes through channels
* completely avoid manual installation steps on remote places
2008-08-16 23:26:59 +08:00
2009-07-04 22:14:14 +08:00
There is a `EuroPython2009 talk`_ from July 2009 with
examples and some pictures.
.. _`EuroPython2009 talk`: http://codespeak.net/download/py/ep2009-execnet.pdf
2009-03-23 07:18:49 +08:00
Gateways: immediately spawn local or remote process
2009-04-02 15:10:16 +08:00
===================================================
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
In order to send code to a remote place or a subprocess
you need to instantiate a so-called Gateway object.
There are currently three Gateway classes:
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
* :api:`py.execnet.PopenGateway` to open a subprocess
on the local machine. Useful for making use
of multiple processors to to contain code execution
in a separated environment.
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
* :api:`py.execnet.SshGateway` to connect to
2008-08-16 23:26:59 +08:00
a remote ssh server and distribute execution to it.
* :api:`py.execnet.SocketGateway` a way to connect to
a remote Socket based server. *Note* that this method
requires a manually started
:source:py/execnet/script/socketserver.py
script. You can run this "server script" without
2009-03-23 07:18:49 +08:00
having the py lib installed on the remote system
and you can setup it up as permanent service.
2008-08-16 23:26:59 +08:00
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
remote_exec: execute source code remotely
2009-04-02 15:10:16 +08:00
===================================================
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
All gateways offer remote code execution via this high level function::
2008-08-16 23:26:59 +08:00
def remote_exec(source):
"""return channel object for communicating with the asynchronously
executing 'source' code which will have a corresponding 'channel'
object in its executing namespace."""
With `remote_exec` you send source code to the other
side and get both a local and a remote Channel_ object,
which you can use to have the local and remote site
communicate data in a structured way. Here is
2009-03-23 07:18:49 +08:00
an example for reading the PID::
2008-08-16 23:26:59 +08:00
>>> import py
>>> gw = py.execnet.PopenGateway()
>>> channel = gw.remote_exec("""
... import os
... channel.send(os.getpid())
... """)
>>> remote_pid = channel.receive()
>>> remote_pid != py.std.os.getpid()
True
.. _`Channel`:
.. _`channel-api`:
.. _`exchange data`:
2009-03-23 07:18:49 +08:00
Channels: bidirectionally exchange data between hosts
2009-04-02 16:08:38 +08:00
=======================================================
2008-08-16 23:26:59 +08:00
2009-03-18 19:18:39 +08:00
A channel object allows to send and receive data between
two asynchronously running programs. When calling
`remote_exec` you will get a channel object back and
2009-03-23 08:49:31 +08:00
the code fragment running on the other side will
2009-03-18 19:18:39 +08:00
see a channel object in its global namespace.
Here is the interface of channel objects::
2008-08-16 23:26:59 +08:00
#
# API for sending and receiving anonymous values
#
channel.send(item):
sends the given item to the other side of the channel,
possibly blocking if the sender queue is full.
Note that items need to be marshallable (all basic
2009-03-18 19:18:39 +08:00
python types are).
2008-08-16 23:26:59 +08:00
channel.receive():
receives an item that was sent from the other side,
possibly blocking if there is none.
Note that exceptions from the other side will be
reraised as gateway.RemoteError exceptions containing
a textual representation of the remote traceback.
channel.waitclose(timeout=None):
wait until this channel is closed. Note that a closed
channel may still hold items that will be received or
send. Note that exceptions from the other side will be
reraised as gateway.RemoteError exceptions containing
a textual representation of the remote traceback.
channel.close():
close this channel on both the local and the remote side.
A remote side blocking on receive() on this channel
will get woken up and see an EOFError exception.
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
.. _xspec:
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
XSpec: string specification for gateway type and configuration
2009-04-02 15:10:16 +08:00
===============================================================
2009-03-23 07:18:49 +08:00
``py.execnet`` supports a simple extensible format for
specifying and configuring Gateways for remote execution.
2009-03-24 03:07:02 +08:00
You can use a string specification to instantiate a new gateway,
2009-03-23 07:18:49 +08:00
for example a new SshGateway::
gateway = py.execnet.makegateway("ssh=myhost")
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
Let's look at some examples for valid specifications.
Specification for an ssh connection to `wyvern`, running on python2.4 in the (newly created) 'mycache' subdirectory::
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
ssh=wyvern//python=python2.4//chdir=mycache
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
Specification of a python2.5 subprocess; with a low CPU priority ("nice" level). Current dir will be the current dir of the instantiator (that's true for all 'popen' specifications unless they specify 'chdir')::
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
popen//python=2.5//nice=20
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
Specification of a Python Socket server process that listens on 192.168.1.4:8888; current dir will be the 'pyexecnet-cache' sub directory which is used a default for all remote processes::
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
socket=192.168.1.4:8888
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
More generally, a specification string has this general format::
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
key1=value1//key2=value2//key3=value3
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
If you omit a value, a boolean true value is assumed. Currently
the following key/values are supported:
* ``popen`` for a PopenGateway
* ``ssh=host`` for a SshGateway
* ``socket=address:port`` for a SocketGateway
* ``python=executable`` for specifying Python executables
* ``chdir=path`` change remote working dir to given relative or absolute path
* ``nice=value`` decrease remote nice level if platforms supports it
2009-03-23 08:49:31 +08:00
Examples of py.execnet usage
2009-04-02 15:10:16 +08:00
===============================================================
2009-03-23 07:18:49 +08:00
2009-04-02 15:10:16 +08:00
Compare cwd() of Popen Gateways
----------------------------------------
2009-03-18 19:18:39 +08:00
A PopenGateway has the same working directory as the instantiatior::
>>> import py, os
>>> gw = py.execnet.PopenGateway()
>>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
>>> res = ch.receive()
>>> assert res == os.getcwd()
>>> gw.exit()
2009-04-02 15:10:16 +08:00
Synchronously receive results from two sub processes
-----------------------------------------------------
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
Use MultiChannels for receiving multiple results from remote code::
2009-03-18 19:18:39 +08:00
>>> import py
>>> ch1 = py.execnet.PopenGateway().remote_exec("channel.send(1)")
>>> ch2 = py.execnet.PopenGateway().remote_exec("channel.send(2)")
>>> mch = py.execnet.MultiChannel([ch1, ch2])
>>> l = mch.receive_each()
>>> assert len(l) == 2
>>> assert 1 in l
>>> assert 2 in l
2009-03-23 07:18:49 +08:00
2009-04-02 15:10:16 +08:00
Asynchronously receive results from two sub processes
-----------------------------------------------------
2009-03-18 19:18:39 +08:00
2009-03-23 07:18:49 +08:00
Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving
multiple results from remote code. This standard Queue provides
``(channel, result)`` tuples which allows to determine where
a result comes from::
2009-03-18 19:18:39 +08:00
>>> import py
2009-03-23 07:18:49 +08:00
>>> ch1 = py.execnet.PopenGateway().remote_exec("channel.send(1)")
>>> ch2 = py.execnet.PopenGateway().remote_exec("channel.send(2)")
>>> mch = py.execnet.MultiChannel([ch1, ch2])
>>> queue = mch.make_receive_queue()
>>> chan1, res1 = queue.get() # you may also specify a timeout
>>> chan2, res2 = queue.get()
>>> res1 + res2
3
>>> assert chan1 in (ch1, ch2)
>>> assert chan2 in (ch1, ch2)
>>> assert chan1 != chan2
2009-04-02 15:10:16 +08:00
Receive file contents from remote SSH account
-----------------------------------------------------
2009-03-23 07:18:49 +08:00
Here is a small program that you can use to retrieve
contents of remote files::
2008-08-16 23:26:59 +08:00
import py
2009-03-18 19:18:39 +08:00
# open a gateway to a fresh child process
gw = py.execnet.SshGateway('codespeak.net')
channel = gw.remote_exec("""
2008-08-16 23:26:59 +08:00
for fn in channel:
f = open(fn, 'rb')
2009-03-18 19:18:39 +08:00
channel.send(f.read())
f.close()
""")
2008-08-16 23:26:59 +08:00
for fn in somefilelist:
channel.send(fn)
content = channel.receive()
# process content
# later you can exit / close down the gateway
2009-03-23 07:18:49 +08:00
gw.exit()
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
Instantiate a socket server in a new subprocess
2009-04-02 15:10:16 +08:00
-----------------------------------------------------
2008-08-16 23:26:59 +08:00
The following example opens a PopenGateway, i.e. a python
2009-03-23 07:18:49 +08:00
child process, and starts a socket server within that process
and then opens a second gateway to the freshly started
socketserver::
2008-08-16 23:26:59 +08:00
import py
2009-03-23 07:18:49 +08:00
popengw = py.execnet.PopenGateway()
socketgw = py.execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
2008-08-16 23:26:59 +08:00
2009-03-23 07:18:49 +08:00
print socketgw._rinfo() # print some info about the remote environment
2009-06-26 23:48:46 +08:00
Sending a module / checking if run through remote_exec
--------------------------------------------------------------
You can pass a module object to ``remote_exec`` in which case
its source code will be sent. No dependencies will be transferred
so the module must be self-contained or only use modules that are
installed on the "other" side. Module code can detect if it is
running in a remote_exec situation by checking for the special
``__name__`` attribute like this::
if __name__ == '__channelexec__':
# ... call module functions ...