Esempio n. 1
0
class AMQPService(object):
    """
    Class to serve all available queues and commands to the AMQP broker. It
    makes use of a couple of configuration flags provided by the gosa
    configurations file ``[amqp]`` section:

    ============== =============
    Key            Description
    ============== =============
    url            AMQP URL to connect to the broker
    id             User name to connect with
    key            Password to connect with
    command-worker Number of worker processes
    ============== =============

    Example::

        [amqp]
        url = amqps://amqp.intranet.gonicus.de:5671
        id = node1
        key = secret

    """
    implements(IInterfaceHandler)
    _priority_ = 1

    def __init__(self):
        env = Environment.getInstance()
        self.log = logging.getLogger(__name__)
        self.log.info("initializing AMQP service provider")
        self.env = env

        self.__cr = None
        self.__zeroconf = None
        self.__cmdWorker = None

    def serve(self):
        """ Start AMQP service for this GOsa service provider. """
        # Load AMQP and Command registry instances
        amqp = PluginRegistry.getInstance('AMQPHandler')
        self.__cr = PluginRegistry.getInstance('CommandRegistry')

        # Create a list of queues we need here
        queues = {}
        for dsc in self.__cr.commands.values():
            queues[dsc['target']] = True

        # Finally create the queues
        for queue in queues:
            # Add round robin processor for queue
            self.__cmdWorker = AMQPWorker(self.env, connection=amqp.getConnection(),
                r_address='%s.command.%s; { create:always, node:{ type:queue, x-bindings:[ { exchange:"amq.direct", queue:"%s.command.%s" } ] } }' % (self.env.domain, queue, self.env.domain, queue),
                workers=self.env.config.get('amqp.command-worker', default=1),
                callback=self.commandReceived)

            # Add private processor for queue
            self.__cmdWorker = AMQPWorker(self.env, connection=amqp.getConnection(),
                    r_address='%s.command.%s.%s; { create:always, delete:receiver, node:{ type:queue, x-bindings:[ { exchange:"amq.direct", queue:"%s.command.%s.%s" } ] } }' % (self.env.domain, queue, self.env.id, self.env.domain, queue, self.env.id),
                workers=self.env.config.get('amqp.command-worker', default=1),
                callback=self.commandReceived)

        # Announce service
        url = parseURL(self.env.config.get("amqp.url"))
        self.__zeroconf = ZeroconfService(name="GOsa AMQP command service",
                port=url['port'],
                stype="_%s._tcp" % url['scheme'],
                text="path=%s\001service=gosa" % url['path'])
        self.__zeroconf.publish()

        self.log.info("ready to process incoming requests")

    def stop(self):
        """ Stop AMQP service for this GOsa service provider. """
        self.__zeroconf.unpublish()

    def commandReceived(self, ssn, message):
        """
        Process incoming commands, coming in with session and message
        information.

        ================= ==========================
        Parameter         Description
        ================= ==========================
        ssn               AMQP session object
        message           Received AMQP message
        ================= ==========================

        Incoming messages are coming from an
        :class:`gosa.common.components.amqp_proxy.AMQPServiceProxy` which
        is providing a *reply to* queue as a return channel. The command
        result is written to that queue.
        """

        # Check for id
        if not message.user_id:
            raise ValueError("incoming message without user_id")

        err = None
        res = None
        id_ = ''

        try:
            req = loads(message.content)
        except ServiceRequestNotTranslatable, e:
            err = str(e)
            req = {'id': id_}

        if err == None:
            try:
                id_ = req['id']
                name = req['method']
                args = req['params']

            except KeyError:
                err = str(BadServiceRequest(message.content))

        if not isinstance(args, list) and not isinstance(args, dict):
            raise ValueError("bad params %r: must be a list or dict" % args)

        # Extract source queue
        p = re.compile(r';.*$')
        queue = p.sub('', message._receiver.source)

        self.log.info("received call [%s/%s] for %s: %s(%s)" % (id_, queue, message.user_id, name, args))

        # Don't process messages if the command registry thinks it's not ready
        if not self.__cr.processing.is_set():
            self.log.warning("waiting for registry to get ready")
            if not self.__cr.processing.wait(5):
                self.log.error("releasing call [%s/%s] for %s: %s(%s) - timed out" % (id_, queue, message.user_id, name, args))
                ssn.acknowledge(message, Disposition(RELEASED, set_redelivered=True))
                return

        # Try to execute either with or without keyword arguments
        if err == None:
            try:
                if isinstance(args, dict):
                    res = self.__cr.dispatch(message.user_id, queue, name, **args)
                else:
                    res = self.__cr.dispatch(message.user_id, queue, name, *args)

            # Catch everything that might happen in the dispatch, pylint: disable=W0703
            except Exception as e:
                text = traceback.format_exc()
                self.log.exception(text)
                err = str(e)
                exc_value = sys.exc_info()[1]

                # If message starts with [, it's a translateable message in
                # repr format
                if err.startswith("[") or err.startswith("("):
                    if err.startswith("("):
                        err = "[" + err[1:-1] + "]"
                    err = loads(repr2json(err))
                    err = dict(
                        name='JSONRPCError',
                        code=100,
                        message=str(exc_value),
                        error=err)

        self.log.debug("returning call [%s]: %s / %s" % (id_, res, err))

        response = dumps({"result": res, "id": id_, "error": err})
        ssn.acknowledge(message)

        # Talk to client generated reply queue
        sender = ssn.sender(message.reply_to)

        # Get rid of it...
        sender.send(Message(response))
Esempio n. 2
0
class JSONRPCService(object):
    """
    This is the JSONRPC GOsa agent plugin which is registering an
    instance of :class:`gosa.agent.jsonrpc_service.JsonRpcApp` into the
    :class:`gosa.agent.httpd.HTTPService`.

    It is configured thru the ``[jsonrpc]`` section of your GOsa
    configuration:

    =============== ============
    Key             Description
    =============== ============
    path            Path to register the service in HTTP
    cookie-lifetime Seconds of authentication cookie lifetime
    =============== ============

    Example::

        [jsonrpc]
        path = /rpc
        cookie-lifetime = 3600
    """

    implements(IInterfaceHandler)
    _priority_ = 11

    __proxy = {}

    def __init__(self):
        env = Environment.getInstance()
        self.env = env
        self.log = logging.getLogger(__name__)
        self.log.info("initializing JSON RPC service provider")
        self.path = self.env.config.get("jsonrpc.path", default="/rpc")

        self.__zeroconf = None
        self.__http = None

    def serve(self):
        """ Start JSONRPC service for this GOsa service provider. """

        # Get http service instance
        self.__http = PluginRegistry.getInstance("HTTPService")
        cr = PluginRegistry.getInstance("CommandRegistry")

        # Register ourselves
        app = JsonRpcApp(cr)
        self.__http.app.register(
            self.path,
            AuthCookieHandler(
                app,
                timeout=self.env.config.get("jsonrpc.cookie-lifetime", default=1800),
                cookie_name="GOsaRPC",
                secret=self.env.config.get("http.cookie_secret", default="TecloigJink4"),
            ),
        )

        # Announce service
        self.__zeroconf = ZeroconfService(
            name="GOsa JSONRPC command service",
            port=self.__http.port,
            stype="_%s._tcp" % self.__http.scheme,
            text="path=%s\001service=gosa" % self.path,
        )
        self.__zeroconf.publish()

        self.log.info("ready to process incoming requests")

    def stop(self):
        """ Stop serving the JSONRPC service for this GOsa service provider. """
        self.log.debug("shutting down JSON RPC service provider")
        self.__http.app.unregister(self.path)