|
@@ -0,0 +1,127 @@
|
|
|
+import fcntl
|
|
|
+import msgpack
|
|
|
+import os
|
|
|
+import paramiko
|
|
|
+import select
|
|
|
+import sys
|
|
|
+import getpass
|
|
|
+
|
|
|
+from .store import Store
|
|
|
+
|
|
|
+
|
|
|
+BUFSIZE = 1024 * 1024
|
|
|
+
|
|
|
+
|
|
|
+class StoreServer(object):
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.store = None
|
|
|
+
|
|
|
+ def serve(self):
|
|
|
+ # Make stdin non-blocking
|
|
|
+ fl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
|
|
+ fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
|
|
+ unpacker = msgpack.Unpacker()
|
|
|
+ while True:
|
|
|
+ r, w, es = select.select([sys.stdin], [], [], 10)
|
|
|
+ if r:
|
|
|
+ data = os.read(sys.stdin.fileno(), BUFSIZE)
|
|
|
+ if not data:
|
|
|
+ return
|
|
|
+ unpacker.feed(data)
|
|
|
+ for type, msgid, method, args in unpacker:
|
|
|
+ try:
|
|
|
+ if method == 'open':
|
|
|
+ self.store = Store(*args)
|
|
|
+ res = self.store.id, self.store.tid
|
|
|
+ else:
|
|
|
+ res = getattr(self.store, method)(*args)
|
|
|
+ except Exception, e:
|
|
|
+ sys.stdout.write(msgpack.packb((1, msgid, e.__class__.__name__, None)))
|
|
|
+ else:
|
|
|
+ sys.stdout.write(msgpack.packb((1, msgid, None, res)))
|
|
|
+ sys.stdout.flush()
|
|
|
+ if es:
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+class RemoteStore(object):
|
|
|
+
|
|
|
+ class DoesNotExist(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+ class AlreadyExists(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+ class RPCError(Exception):
|
|
|
+
|
|
|
+ def __init__(self, name):
|
|
|
+ self.name = name
|
|
|
+
|
|
|
+
|
|
|
+ def __init__(self, location, create=False):
|
|
|
+ self.client = paramiko.SSHClient()
|
|
|
+ self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
+ params = {'username': location.user or getpass.getuser(),
|
|
|
+ 'hostname': location.host}
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ self.client.connect(**params)
|
|
|
+ break
|
|
|
+ except paramiko.PasswordRequiredException:
|
|
|
+ params['password'] = getpass.getpass('Password for %(username)s@%(hostname)s:' % params)
|
|
|
+
|
|
|
+ self.unpacker = msgpack.Unpacker()
|
|
|
+ self.transport = self.client.get_transport()
|
|
|
+ self.channel = self.transport.open_session()
|
|
|
+ self.channel.exec_command('darc serve')
|
|
|
+ self.msgid = 0
|
|
|
+ self.id, self.tid = self._cmd('open', (location.path, create))
|
|
|
+
|
|
|
+ def _cmd(self, cmd, args):
|
|
|
+ self.msgid += 1
|
|
|
+ self.channel.sendall(msgpack.packb((0, self.msgid, cmd, args)))
|
|
|
+ while True:
|
|
|
+ r, w, e = select.select([self.channel], [], [self.channel], 10)
|
|
|
+ if r:
|
|
|
+ if self.channel.recv_stderr_ready():
|
|
|
+ raise Exception(self.channel.recv_stderr(BUFSIZE))
|
|
|
+ elif self.channel.recv_ready():
|
|
|
+ self.unpacker.feed(self.channel.recv(BUFSIZE))
|
|
|
+ for type, msgid, error, res in self.unpacker:
|
|
|
+ if error:
|
|
|
+ raise self.RPCError(error)
|
|
|
+ return res
|
|
|
+ else:
|
|
|
+ raise Exception('Read event but no data?!?')
|
|
|
+ if e:
|
|
|
+ raise Exception('ssh channel error')
|
|
|
+
|
|
|
+ def commit(self, *args):
|
|
|
+ self._cmd('commit', args)
|
|
|
+ self.tid += 1
|
|
|
+
|
|
|
+ def rollback(self, *args):
|
|
|
+ return self._cmd('rollback', args)
|
|
|
+
|
|
|
+ def get(self, *args):
|
|
|
+ try:
|
|
|
+ return self._cmd('get', args)
|
|
|
+ except self.RPCError, e:
|
|
|
+ if e.name == 'DoesNotExist':
|
|
|
+ raise self.DoesNotExist
|
|
|
+ raise
|
|
|
+
|
|
|
+ def put(self, *args):
|
|
|
+ try:
|
|
|
+ return self._cmd('put', args)
|
|
|
+ except self.RPCError, e:
|
|
|
+ if e.name == 'AlreadyExists':
|
|
|
+ raise self.AlreadyExists
|
|
|
+
|
|
|
+ def delete(self, *args):
|
|
|
+ return self._cmd('delete', args)
|
|
|
+
|
|
|
+ def list(self, *args):
|
|
|
+ return self._cmd('list', args)
|
|
|
+
|