[dbapi] add possibility to connect to a remote ZMQRepository (closes #2290126)
authorVincent Michel <vincent.michel@logilab.fr>
Tue, 10 Apr 2012 17:09:04 +0200
changeset 8352 0e3b41118631
parent 8351 02f4f01375e8
child 8353 c1cc2f1cd177
[dbapi] add possibility to connect to a remote ZMQRepository (closes #2290126)
cwctl.py
dbapi.py
misc/migration/3.15.0_common.py
zmqclient.py
--- a/cwctl.py	Tue Apr 10 17:07:03 2012 +0200
+++ b/cwctl.py	Tue Apr 10 17:09:04 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -38,6 +38,8 @@
 
 from os.path import exists, join, isfile, isdir, dirname, abspath
 
+from urlparse import urlparse
+
 from logilab.common.clcommands import CommandLine
 from logilab.common.shellutils import ASK
 
@@ -867,31 +869,45 @@
           'group': 'local'
           }),
 
-        ('pyro',
-         {'short': 'P', 'action' : 'store_true',
-          'help': 'connect to a running instance through Pyro.',
-          'group': 'remote',
-          }),
-        ('pyro-ns-host',
-         {'short': 'H', 'type' : 'string', 'metavar': '<host[:port]>',
-          'help': 'Pyro name server host. If not set, will be detected by '
-          'using a broadcast query.',
+        ('repo-uri',
+         {'short': 'H', 'type' : 'string', 'metavar': '<protocol>://<[host][:port]>',
+          'help': 'URI of the CubicWeb repository to connect to. URI can be \
+pyro://[host:port] the Pyro name server host; if the pyro nameserver is not set, \
+it will be detected by using a broadcast query, a ZMQ URL or \
+inmemory:// (default) use an in-memory repository.',
           'group': 'remote'
           }),
         )
 
     def run(self, args):
         appid = args.pop(0)
-        if self.config.pyro:
+        if self.config.repo_uri:
+            uri = urlparse(self.config.repo_uri)
+            if uri.scheme == 'pyro':
+                cnxtype = uri.scheme
+                hostport = uri.netloc
+            elif uri.scheme == 'inmemory':
+                cnxtype = ''
+                hostport = ''
+            else:
+                cnxtype = 'zmq'
+                hostport = self.config.repo_uri
+        else:
+            cnxtype = ''
+
+        if cnxtype:
             from cubicweb import AuthenticationError
-            from cubicweb.dbapi import connect
+            from cubicweb.dbapi import connect, ConnectionProperties
             from cubicweb.server.utils import manager_userpasswd
             from cubicweb.server.migractions import ServerMigrationHelper
+            cnxprops = ConnectionProperties(cnxtype=cnxtype)
+
             while True:
                 try:
                     login, pwd = manager_userpasswd(msg=None)
                     cnx = connect(appid, login=login, password=pwd,
-                                  host=self.config.pyro_ns_host, mulcnx=False)
+                                  host=hostport, mulcnx=False,
+                                  cnxprops=cnxprops)
                 except AuthenticationError, ex:
                     print ex
                 except (KeyboardInterrupt, EOFError):
@@ -927,7 +943,7 @@
             else:
                 mih.interactive_shell()
         finally:
-            if not self.config.pyro:
+            if not cnxtype: # shutdown in-memory repo
                 mih.shutdown()
             else:
                 cnx.close()
--- a/dbapi.py	Tue Apr 10 17:07:03 2012 +0200
+++ b/dbapi.py	Tue Apr 10 17:09:04 2012 +0200
@@ -93,7 +93,7 @@
     Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
     argument should be given
     """
-    assert method in ('pyro', 'inmemory')
+    assert method in ('pyro', 'inmemory', 'zmq')
     assert vreg or config
     if vreg and not config:
         config = vreg.config
@@ -102,7 +102,9 @@
         from cubicweb.server.repository import Repository
         from cubicweb.server.utils import TasksManager
         return Repository(config, TasksManager(), vreg=vreg)
-
+    elif method == 'zmq':
+        from cubicweb.zmqclient import ZMQRepositoryClient
+        return ZMQRepositoryClient(config, vreg=vreg)
     else: # method == 'pyro'
         # resolve the Pyro object
         from logilab.common.pyro_ext import ns_get_proxy, get_proxy
@@ -147,8 +149,8 @@
       the user login to use to authenticate.
 
     :host:
-      the pyro nameserver host. Will be detected using broadcast query if
-      unspecified.
+      - pyro: nameserver host. Will be detected using broadcast query if unspecified
+      - zmq: repository host socket address
 
     :group:
       the instance's pyro nameserver group. You don't have to specify it unless
@@ -185,6 +187,8 @@
             config.global_set_option('pyro-ns-host', host)
         if group:
             config.global_set_option('pyro-ns-group', group)
+    elif method == 'zmq':
+        config = cwconfig.CubicWebNoAppConfiguration()
     else:
         assert database
         config = cwconfig.instance_configuration(database)
--- a/misc/migration/3.15.0_common.py	Tue Apr 10 17:07:03 2012 +0200
+++ b/misc/migration/3.15.0_common.py	Tue Apr 10 17:09:04 2012 +0200
@@ -1,2 +1,5 @@
 undo_actions = config.cfgfile_parser.get('MAIN', 'undo-support', False)
 config.global_set_option('undo-enabled', bool(undo_actions))
+pyro_actions = config.cfgfile_parser.get('REMOTE', 'pyro', False)
+if pyro_actions:
+    config.global_set_option('repo-uri', 'pyro://')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zmqclient.py	Tue Apr 10 17:09:04 2012 +0200
@@ -0,0 +1,61 @@
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Source to query another RQL repository using pyro"""
+
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from functools import partial
+import zmq
+
+
+# XXX hack to overpass old zmq limitation that force to have
+# only one context per python process
+try:
+    from cubicweb.server.cwzmq import ctx
+except ImportError:
+    ctx = zmq.Context()
+
+class ZMQRepositoryClient(object):
+    """
+    This class delegate the overall repository stuff to a remote source.
+
+    So calling a method of this repository will results on calling the
+    corresponding method of the remote source repository.
+
+    Any raised exception on the remote source is propagated locally.
+
+    ZMQ is used as the transport layer and cPickle is used to serialize data.
+    """
+
+    def __init__(self, config, vreg=None):
+        self.config = config
+        self.vreg = vreg
+        self.socket = ctx.socket(zmq.REQ)
+        self.host = config.get('base-url')
+        self.socket.connect(self.host)
+
+    def __zmqcall__(self, name, *args, **kwargs):
+         self.socket.send_pyobj([name, args, kwargs])
+         result = self.socket.recv_pyobj()
+         if isinstance(result, BaseException):
+             raise result
+         return result
+
+    def __getattr__(self, name):
+        return partial(self.__zmqcall__, name)