1# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
3#
4# This file is part of logilab-common.
5#
6# logilab-common is free software: you can redistribute it and/or modify it under
7# the terms of the GNU Lesser General Public License as published by the Free
8# Software Foundation, either version 2.1 of the License, or (at your option) any
9# later version.
10#
11# logilab-common is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
14# details.
15#
16# You should have received a copy of the GNU Lesser General Public License along
17# with logilab-common.  If not, see <http://www.gnu.org/licenses/>.
18"""Python Remote Object utilities
19
20Main functions available:
21
22* `register_object` to expose arbitrary object through pyro using delegation
23  approach and register it in the nameserver.
24* `ns_unregister` unregister an object identifier from the nameserver.
25* `ns_get_proxy` get a pyro proxy from a nameserver object identifier.
26"""
27
28__docformat__ = "restructuredtext en"
29
30import logging
31import tempfile
32
33from Pyro import core, naming, errors, util, config
34
35_LOGGER = logging.getLogger('pyro')
36_MARKER = object()
37
38config.PYRO_STORAGE = tempfile.gettempdir()
39
40def ns_group_and_id(idstr, defaultnsgroup=_MARKER):
41    try:
42        nsgroup, nsid = idstr.rsplit('.', 1)
43    except ValueError:
44        if defaultnsgroup is _MARKER:
45            nsgroup = config.PYRO_NS_DEFAULTGROUP
46        else:
47            nsgroup = defaultnsgroup
48        nsid = idstr
49    if nsgroup is not None and not nsgroup.startswith(':'):
50        nsgroup = ':' + nsgroup
51    return nsgroup, nsid
52
53def host_and_port(hoststr):
54    if not hoststr:
55        return None, None
56    try:
57        hoststr, port = hoststr.split(':')
58    except ValueError:
59        port = None
60    else:
61        port = int(port)
62    return hoststr, port
63
64_DAEMONS = {}
65_PYRO_OBJS = {}
66def _get_daemon(daemonhost, start=True):
67    if not daemonhost in _DAEMONS:
68        if not start:
69            raise Exception('no daemon for %s' % daemonhost)
70        if not _DAEMONS:
71            core.initServer(banner=0)
72        host, port = host_and_port(daemonhost)
73        daemon = core.Daemon(host=host, port=port)
74        _DAEMONS[daemonhost] = daemon
75    return _DAEMONS[daemonhost]
76
77
78def locate_ns(nshost):
79    """locate and return the pyro name server to the daemon"""
80    core.initClient(banner=False)
81    return naming.NameServerLocator().getNS(*host_and_port(nshost))
82
83
84def register_object(object, nsid, defaultnsgroup=_MARKER,
85                    daemonhost=None, nshost=None, use_pyrons=True):
86    """expose the object as a pyro object and register it in the name-server
87
88    if use_pyrons is False, then the object is exposed, but no
89    attempt to register it to a pyro nameserver is made.
90
91    return the pyro daemon object
92    """
93    nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup)
94    daemon = _get_daemon(daemonhost)
95    if use_pyrons:
96        nsd = locate_ns(nshost)
97        # make sure our namespace group exists
98        try:
99            nsd.createGroup(nsgroup)
100        except errors.NamingError:
101            pass
102        daemon.useNameServer(nsd)
103    # use Delegation approach
104    impl = core.ObjBase()
105    impl.delegateTo(object)
106    qnsid = '%s.%s' % (nsgroup, nsid)
107    uri = daemon.connect(impl, qnsid)
108    _PYRO_OBJS[qnsid] = str(uri)
109    _LOGGER.info('registered %s a pyro object using group %s and id %s',
110                 object, nsgroup, nsid)
111    return daemon
112
113def get_object_uri(qnsid):
114    return _PYRO_OBJS[qnsid]
115
116def ns_unregister(nsid, defaultnsgroup=_MARKER, nshost=None):
117    """unregister the object with the given nsid from the pyro name server"""
118    nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup)
119    try:
120        nsd = locate_ns(nshost)
121    except errors.PyroError as ex:
122        # name server not responding
123        _LOGGER.error('can\'t locate pyro name server: %s', ex)
124    else:
125        try:
126            nsd.unregister('%s.%s' % (nsgroup, nsid))
127            _LOGGER.info('%s unregistered from pyro name server', nsid)
128        except errors.NamingError:
129            _LOGGER.warning('%s not registered in pyro name server', nsid)
130
131
132def ns_reregister(nsid, defaultnsgroup=_MARKER, nshost=None):
133    """reregister a pyro object into the name server. You only have to specify
134    the name-server id of the object (though you MUST have gone through
135    `register_object` for the given object previously).
136
137    This is especially useful for long running server while the name server may
138    have been restarted, and its records lost.
139    """
140    nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup)
141    qnsid = '%s.%s' % (nsgroup, nsid)
142    nsd = locate_ns(nshost)
143    try:
144        nsd.unregister(qnsid)
145    except errors.NamingError:
146        # make sure our namespace group exists
147        try:
148            nsd.createGroup(nsgroup)
149        except errors.NamingError:
150            pass
151    nsd.register(qnsid, _PYRO_OBJS[qnsid])
152
153def ns_get_proxy(nsid, defaultnsgroup=_MARKER, nshost=None):
154    """
155    if nshost is None, the nameserver is found by a broadcast.
156    """
157    # resolve the Pyro object
158    nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup)
159    try:
160        nsd = locate_ns(nshost)
161        pyrouri = nsd.resolve('%s.%s' % (nsgroup, nsid))
162    except errors.ProtocolError as ex:
163        raise errors.PyroError(
164            'Could not connect to the Pyro name server (host: %s)' % nshost)
165    except errors.NamingError:
166        raise errors.PyroError(
167            'Could not get proxy for %s (not registered in Pyro), '
168            'you may have to restart your server-side application' % nsid)
169    return core.getProxyForURI(pyrouri)
170
171def get_proxy(pyro_uri):
172    """get a proxy for the passed pyro uri without using a nameserver
173    """
174    return core.getProxyForURI(pyro_uri)
175
176def set_pyro_log_threshold(level):
177    pyrologger = logging.getLogger('Pyro.%s' % str(id(util.Log)))
178    # remove handlers so only the root handler is used
179    pyrologger.handlers = []
180    pyrologger.setLevel(level)
181