Skip to content

Commit

Permalink
zerodep
Browse files Browse the repository at this point in the history
  • Loading branch information
tomerfiliba committed Jun 15, 2013
1 parent 78ed7f2 commit ed1306b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 84 deletions.
13 changes: 8 additions & 5 deletions demos/splits/splits.py
@@ -1,24 +1,27 @@
import sys
import rpyc
from plumbum import SshMachine
from rpyc.utils.zerodeploy import deployment
from rpyc.utils.zerodeploy import DeployedServer
from rpyc.utils.splitbrain import splitbrain

mach = SshMachine("192.168.1.117")

print sys.platform

with deployment(mach) as dep:
with DeployedServer(mach) as dep:
conn = dep.classic_connect()
print conn.modules.sys.platform

try:
import posix
except ImportError:
pass
except ImportError as ex:
print ex

with splitbrain(conn):
import posix
posix.stat
print posix.stat("/boot")

print posix



114 changes: 113 additions & 1 deletion docs/docs/zerodeploy.rst
Expand Up @@ -2,4 +2,116 @@

Zero-Deploy RPyC
================
TBD

Setting up and managing servers is a headache. You need to start the server process, monitor it throughout its
life span, make sure it doesn't hog up memory over time (or restart it if it does), make sure it comes up
automatically after reboots, manage user permissions and make sure everything remains secure. Enter zero-deploy.

Zero-deploy RPyC does all of the above, but doesn't stop there: it allows you to dispatch an RPyC server on a machine
that doesn't have RPyC installed, and even allows multiple instances of the server (each of a different port),
while keeping it all 100% secure. In fact, because of the numerous benefits of zero-deploy, it is now considered
the preferred way to deploy RPyC.

How It Works
------------

Zero-deploy only requires that you have [Plumbum](http://plumbum.readthedocs.org/) (1.2 and later) installed on
your client machine and that you can connect to the remote machine over SSH. It takes care of the rest:

1. Create a temporary directory on the remote machine
2. Copy the RPyC distribution (from the local machine) to that temp directory
3. Create a server file in the temp directory and run it (over SSH)
4. The server binds to an arbitrary port (call it *port A*) on the ``localhost`` interfaces of the remote
machine, so it will only accept in-bound connections
5. The client machine sets up an SSH tunnel from a local port, *port B*, on the ``localhost`` to *port A* on the
remote machine.
6. The client machine can now establish secure RPyC connections to the deployed server by connecting to
``localhost``:*port B* (forwarded by SSH)
7. When the deployment is finalized (or when the SSH connection drops for any reason), the deployed server will
remove the temporary directory and shut down, leaving no trace on the remote machine

Usage
-----

There's a lot of detail here, of course, but the good thing is you don't have to bend your head around it --
it requires only two lines of code::

from rpyc.utils.zerodeploy import DeployedServer
from plumbum import SshMachine
# create the deployment
mach = SshMachine("somehost", user="someuser", keyfile="/path/to/keyfile")
server = DeployedServer(mach)
# and now you can connect to it the usual way
conn1 = server.classic_connect()
print conn1.modules.sys.platform

# you're not limited to a single connection, of course
conn2 = server.classic_connect()
print conn1.modules.os.getpid()

# when you're done - close the server and everything will disappear
server.close()

The ``DeployedServer`` class can be used as a context-manager, so you can also write::

with DeployedServer(mach) as server:
conn = server.classic_connect()
# ...

Here's a capture of the interactive prompt:

>>> sys.platform
'win32'
>>>
>>> mach = SshMachine("192.168.1.100")
>>> server = DeployedServer(mach)
>>> conn = server.classic_connect()
>>> conn.modules.sys.platform
'linux2'
>>> conn2 = server.classic_connect()
>>> conn2.modules.os.getpid()
8148
>>> server.close()
>>> conn2.modules.os.getpid()
Traceback (most recent call last):
...
EOFError

You can deploy multiple instances of the server (each will live in a separate temporary directory), and create
multiple RPyC connections to each. They are completely isolated from each other (up to the fact you can use
them to run commands like ``ps`` to learn about their neighbors).

MultiServerDeployment
---------------------
If you need to deploy on a group of machines a cluster of machines, you can also use ``MultiServerDeployment``::

from plumbum.utils.zerodeploy import MultiServerDeployment
m1 = SshMachine("host1")
m2 = SshMachine("host2")
m3 = SshMachine("host3")
dep = MultiServerDeployment([m1, m2, m3])
conn1, conn2, conn3 = dep.classic_connect_all()
# ...
dep.close()

Short-lived Servers
-------------------
Zero-deploy is ideal for use-once, on-demand servers. For instance, suppose you need to connect to one of your
machines periodically or only when a certain event takes place. Keeping an RPyC server up and running at all times
is a waste of memory and a potential security hole. Using zero-deploy on demand is the best approach for
such scenarios.

Security
--------
Zero-deploy relies on SSH for security, in two ways. First, SSH authenticates the user and runs the RPyC server
under the user's permissions. You can connect as an unprivileged user to make sure strayed RPyC processes can't
``rm -rf /``. Second, it creates an SSH tunnel for the transport, so everything is kept encrypted on the wire.
And you get these features for free -- just configuring SSH accounts will do.


7 changes: 6 additions & 1 deletion docs/index.rst
Expand Up @@ -42,6 +42,9 @@ RPyC - Transparent, Symmetric Distributed Computing
<a class="reference external" href="https://github.com/tomerfiliba/rpyc/issues">github issues</a>
to report problems. <strong>Please do not email me directly</strong>.

<br/>
Learn about the new :ref:`Zero-Deploy <zerodeploy>` feature in the up-coming 3.3 release

</div>

**RPyC** (pronounced as *are-pie-see*), or *Remote Python Call*, is a **transparent**
Expand Down Expand Up @@ -94,7 +97,9 @@ Features
protocol, and requiring no complex setup (name servers, HTTP, URL-mapping, etc.)

* **Secure** - employs a `Capability based <http://en.wikipedia.org/wiki/Capability-based_security>`_
security model
security model; intergrates easily with SSH

* **Zero-Deploy Enabled** -- Read more about :ref:`Zero-Deploy RPyC <zerodeploy>`

* **Integrates** with `TLS/SSL <http://en.wikipedia.org/wiki/Transport_Layer_Security>`_,
`SSH <http://en.wikipedia.org/wiki/Secure_Shell>`_ and `inetd <http://en.wikipedia.org/wiki/inetd>`_.
Expand Down
10 changes: 6 additions & 4 deletions rpyc/utils/zerodeploy.py
@@ -1,5 +1,7 @@
"""
.. versionadded:: 3.3
Requires [plumbum](http://plumbum.readthedocs.org/)
"""
from __future__ import with_statement
import rpyc
Expand Down Expand Up @@ -66,7 +68,7 @@ class DeployedServer(object):
:param remote_machine: a ``plumbum.SshMachine`` instance, representing an SSH
connection to the desired remote machine
:param server_class: the server to create (e.g., ``ThreadedServer``, ``ForkingServer``)
:param server_class: the server to create (e.g., ``"ThreadedServer"``, ``"ForkingServer"``)
"""

def __init__(self, remote_machine, server_class = "ThreadedServer"):
Expand Down Expand Up @@ -140,9 +142,7 @@ class MultiServerDeployment(object):
def __init__(self, remote_machines, server_class = "ThreadedServer"):
self.remote_machines = remote_machines
# build the list incrementally, so we can clean it up if we have an exception
self.servers = []
for mach in remote_machines:
self.servers.append(DeployedServer(mach, server_class))
self.servers = [DeployedServer(mach, server_class) for mach in remote_machines]

def __del__(self):
self.close()
Expand All @@ -163,8 +163,10 @@ def close(self):
s.close()

def connect_all(self, service = VoidService, config = {}):
"""connects to all deployed servers; returns a list of connections (order guaranteed)"""
return [s.connect(service, config) for s in self.servers]
def classic_connect_all(self):
"""connects to all deployed servers using classic_connect; returns a list of connections (order guaranteed)"""
return [s.classic_connect() for s in self.servers]


Expand Down
2 changes: 1 addition & 1 deletion rpyc/version.py
@@ -1,4 +1,4 @@
version = (3, 3, 0)
version_string = "3.3.0"
release_date = "2013.03.01"
release_date = "2013.07.01"

72 changes: 0 additions & 72 deletions tests/ugly.py

This file was deleted.

0 comments on commit ed1306b

Please sign in to comment.