diff options
| author | Tomás Touceda <chiiph@leap.se> | 2013-04-02 10:48:41 -0300 | 
|---|---|---|
| committer | Tomás Touceda <chiiph@leap.se> | 2013-04-02 10:48:41 -0300 | 
| commit | 138def37372c44815ebc6e8cdb117e1158044d8e (patch) | |
| tree | e6085b25f0372ed20b0112420ddb0a26b5d34d92 | |
| parent | 20c2b8986df3451d9d1f9e45a266731805f564b6 (diff) | |
| parent | 9b5a6961a5662edf11ecee36caa5accbbda0281d (diff) | |
Merge remote-tracking branch 'drebs/feature/events-signals' into develop
Conflicts:
	.gitignore
	src/leap/common/__init__.py
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | changes/feature_events_signals | 1 | ||||
| -rw-r--r-- | setup.py | 16 | ||||
| -rw-r--r-- | src/leap/common/__init__.py | 3 | ||||
| -rw-r--r-- | src/leap/common/events/Makefile | 31 | ||||
| -rw-r--r-- | src/leap/common/events/README | 45 | ||||
| -rw-r--r-- | src/leap/common/events/__init__.py | 100 | ||||
| -rw-r--r-- | src/leap/common/events/component.py | 233 | ||||
| -rw-r--r-- | src/leap/common/events/daemon.py | 208 | ||||
| -rw-r--r-- | src/leap/common/events/events.proto | 69 | ||||
| -rw-r--r-- | src/leap/common/events/events_pb2.py | 364 | ||||
| -rw-r--r-- | src/leap/common/events/mac_auth.py | 31 | ||||
| -rw-r--r-- | src/leap/common/events/server.py | 150 | ||||
| -rw-r--r-- | src/leap/common/tests/__init__.py | 0 | ||||
| -rw-r--r-- | src/leap/common/tests/test_events.py | 200 | 
15 files changed, 1450 insertions, 5 deletions
| @@ -1,3 +1,5 @@ -*.pyc  +*.pyc +*.egg  *.egg-info  dist/ +build/ diff --git a/changes/feature_events_signals b/changes/feature_events_signals new file mode 100644 index 0000000..ae0b7b0 --- /dev/null +++ b/changes/feature_events_signals @@ -0,0 +1 @@ +  o Add a mechanism for events signaling between components. @@ -19,9 +19,18 @@ setup file for leap.common  """  from setuptools import setup, find_packages +  requirements = [ +    'protobuf', +    'protobuf.socketrpc',  ] + +dependency_links = [ +    'https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc', +] + +  # XXX add classifiers, docs  setup( @@ -37,7 +46,8 @@ setup(      ),      namespace_packages=["leap"],      package_dir={'': 'src'}, -    packages=find_packages('src'), -    #test_suite='leap.common.tests', -    #install_requires=requirements, +    packages=find_packages('src', exclude=['leap.common.tests']), +    test_suite='leap.common.tests', +    install_requires=requirements, +    dependency_links=dependency_links,  ) diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 9467c46..5702ca1 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -3,6 +3,7 @@ import logging  from leap.common import certs  from leap.common import check  from leap.common import files +from leap.common import events  logger = logging.getLogger(__name__) @@ -13,6 +14,6 @@ except ImportError:      logger.debug('PyGeoIP not found. Disabled Geo support.')      HAS_GEOIP = False -__all__ = ["certs", "check", "files"] +__all__ = ["certs", "check", "files", "events"]  __version__ = "0.2.0-dev" diff --git a/src/leap/common/events/Makefile b/src/leap/common/events/Makefile new file mode 100644 index 0000000..4f73dea --- /dev/null +++ b/src/leap/common/events/Makefile @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Makefile +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This file is used to generate protobuf python files that are used for IPC: +# +#   https://developers.google.com/protocol-buffers/docs/pythontutorial + +PROTOC = protoc + +all: events_pb2.py + +%_pb2.py: %.proto +	$(PROTOC) --python_out=./ $< +	autopep8 --in-place --aggressive $@ + +clean: +	rm -f *_pb2.py diff --git a/src/leap/common/events/README b/src/leap/common/events/README new file mode 100644 index 0000000..61b320d --- /dev/null +++ b/src/leap/common/events/README @@ -0,0 +1,45 @@ +Events mechanism +================ + +The events mechanism allows for "components" to send signal events to each +other by means of a centralized server. Components can register with the +server to receive signals of certain types, and they can also send signals to +the server that will then redistribute these signals to registered components. + + +Listening daemons +----------------- + +Both components and the server listen for incoming messages by using a +listening daemon that runs in its own thread. The server daemon has to be +started explicitly, while components daemon will be started whenever a +component registers with the server to receive messages. + + +How to use it +------------- + +To start the events server: + +>>> from leap.common.events import server +>>> server.ensure_server(port=8090) + +To register a callback to be called when a given signal is raised: + +>>> from leap.common.events import ( +>>>     register, +>>>     events_pb2 as proto, +>>> ) +>>> +>>> def mycallback(sigreq): +>>>     print str(sigreq) +>>> +>>> events.register(signal=proto.CLIENT_UID, callback=mycallback) + +To signal an event: + +>>> from leap.common.events import ( +>>>     signal, +>>>     events_pb2 as proto, +>>> ) +>>> signal(proto.CLIENT_UID) diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py new file mode 100644 index 0000000..b8441bd --- /dev/null +++ b/src/leap/common/events/__init__.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +An events mechanism that allows for signaling of events between components. +""" + +import logging +import socket + + +from leap.common.events import ( +    events_pb2, +    server, +    component, +    daemon, +) + + +logger = logging.getLogger(__name__) + + +def register(signal, callback, uid=None, replace=False, reqcbk=None, +             timeout=1000): +    """ +    Register a callback to be called when the given signal is received. + +    Will timeout after timeout ms if response has not been received. The +    timeout arg is only used for asynch requests. If a reqcbk callback has +    been supplied the timeout arg is not used. The response value will be +    returned for a synch request but nothing will be returned for an asynch +    request. + +    @param signal: the signal that causes the callback to be launched +    @type signal: int (see the `events.proto` file) +    @param callback: the callback to be called when the signal is received +    @type callback: function +    @param uid: a unique id for the callback +    @type uid: int +    @param replace: should an existent callback with same uid be replaced? +    @type replace: bool +    @param reqcbk: a callback to be called when a response from server is +        received +    @type reqcbk: function +        callback(leap.common.events.events_pb2.EventResponse) +    @param timeout: the timeout for synch calls +    @type timeout: int + +    @return: the response from server for synch calls or nothing for asynch +        calls +    @rtype: leap.common.events.events_pb2.EventsResponse or None +    """ +    return component.register(signal, callback, uid, replace, reqcbk, timeout) + + +def signal(signal, content="", mac_method="", mac="", reqcbk=None, +           timeout=1000): +    """ +    Send `signal` event to events server. + +    Will timeout after timeout ms if response has not been received. The +    timeout arg is only used for asynch requests.  If a reqcbk callback has +    been supplied the timeout arg is not used. The response value will be +    returned for a synch request but nothing will be returned for an asynch +    request. + +    @param signal: the signal that causes the callback to be launched +    @type signal: int (see the `events.proto` file) +    @param content: the contents of the event signal +    @type content: str +    @param mac_method: the method used to auth mac +    @type mac_method: str +    @param mac: the content of the auth mac +    @type mac: str +    @param reqcbk: a callback to be called when a response from server is +        received +    @type reqcbk: function +        callback(leap.common.events.events_pb2.EventResponse) +    @param timeout: the timeout for synch calls +    @type timeout: int + +    @return: the response from server for synch calls or nothing for asynch +        calls +    @rtype: leap.common.events.events_pb2.EventsResponse or None +    """ +    return component.signal(signal) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py new file mode 100644 index 0000000..4fcd6e7 --- /dev/null +++ b/src/leap/common/events/component.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# component.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +The component end point of the events mechanism. + +Components are the communicating parties of the events mechanism. They +communicate by sending messages to a server, which in turn redistributes +messages to other components. + +When a component registers a callback for a given signal, it also tells the +server that it wants to be notified whenever signals of that type are sent by +some other component. +""" + + +import logging +import threading + + +from protobuf.socketrpc import RpcService +from leap.common.events import ( +    events_pb2 as proto, +    server, +    daemon, +    mac_auth, +) + + +logger = logging.getLogger(__name__) + + +# the `registered_callbacks` dictionary below should have the following +# format: +# +#     { event_signal: [ (uid, callback), ... ], ... } +# +registered_callbacks = {} + + +class CallbackAlreadyRegistered(Exception): +    """ +    Raised when trying to register an already registered callback. +    """ + + +def ensure_component_daemon(): +    """ +    Ensure the component daemon is running and listening for incoming +    messages. + +    @return: the daemon instance +    @rtype: EventsComponentDaemon +    """ +    daemon = EventsComponentDaemon.ensure(0) +    import time +    # Because we use a random port we want to wait until a port is assigned to +    # local component daemon. +    while not (EventsComponentDaemon.get_instance() and +               EventsComponentDaemon.get_instance().get_port()): +        time.sleep(0.1) +    return daemon + + +def register(signal, callback, uid=None, replace=False, reqcbk=None, +             timeout=1000): +    """ +    Registers a callback to be called when a specific signal event is +    received. + +    Will timeout after timeout ms if response has not been received. The +    timeout arg is only used for asynch requests. If a reqcbk callback has +    been supplied the timeout arg is not used. The response value will be +    returned for a synch request but nothing will be returned for an asynch +    request. + +    @param signal: the signal that causes the callback to be launched +    @type signal: int (see the `events.proto` file) +    @param callback: the callback to be called when the signal is received +    @type callback: function +        callback(leap.common.events.events_pb2.SignalRequest) +    @param uid: a unique id for the callback +    @type uid: int +    @param replace: should an existent callback with same uid be replaced? +    @type replace: bool +    @param reqcbk: a callback to be called when a response from server is +        received +    @type reqcbk: function +        callback(leap.common.events.events_pb2.EventResponse) +    @param timeout: the timeout for synch calls +    @type timeout: int + +    Might raise a CallbackAlreadyRegistered exception if there's already a +    callback identified by the given uid and replace is False. + +    @return: the response from server for synch calls or nothing for asynch +        calls +    @rtype: leap.common.events.events_pb2.EventsResponse or None +    """ +    ensure_component_daemon()  # so we can receive registered signals +    # register callback locally +    if signal not in registered_callbacks: +        registered_callbacks[signal] = [] +    cbklist = registered_callbacks[signal] +    if uid and filter(lambda (x, y): x == uid, cbklist): +        if not replace: +            raise CallbackAlreadyRegisteredException() +        else: +            registered_callbacks[signal] = filter(lambda(x, y): x != uid, +                                                  cbklist) +    registered_callbacks[signal].append((uid, callback)) +    # register callback on server +    request = proto.RegisterRequest() +    request.event = signal +    request.port = EventsComponentDaemon.get_instance().get_port() +    request.mac_method = mac_auth.MacMethod.MAC_NONE +    request.mac = "" +    service = RpcService(proto.EventsServerService_Stub, +                         server.SERVER_PORT, 'localhost') +    logger.info("Sending registration request to server: %s", str(request)) +    return service.register(request, callback=reqcbk, timeout=timeout) + + +def signal(signal, content="", mac_method="", mac="", reqcbk=None, +           timeout=1000): +    """ +    Send `signal` event to events server. + +    Will timeout after timeout ms if response has not been received. The +    timeout arg is only used for asynch requests.  If a reqcbk callback has +    been supplied the timeout arg is not used. The response value will be +    returned for a synch request but nothing will be returned for an asynch +    request. + +    @param signal: the signal that causes the callback to be launched +    @type signal: int (see the `events.proto` file) +    @param content: the contents of the event signal +    @type content: str +    @param mac_method: the method used for auth mac +    @type mac_method: str +    @param mac: the content of the auth mac +    @type mac: str +    @param reqcbk: a callback to be called when a response from server is +        received +    @type reqcbk: function +        callback(leap.common.events.events_pb2.EventResponse) +    @param timeout: the timeout for synch calls +    @type timeout: int + +    @return: the response from server for synch calls or nothing for asynch +        calls +    @rtype: leap.common.events.events_pb2.EventsResponse or None +    """ +    request = proto.SignalRequest() +    request.event = signal +    request.content = content +    request.mac_method = mac_method +    request.mac = mac +    service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT, +                         'localhost') +    logger.info("Sending signal to server: %s", str(request)) +    return service.signal(request, callback=reqcbk, timeout=timeout) + + +class EventsComponentService(proto.EventsComponentService): +    """ +    Service for receiving signal events in components. +    """ + +    def __init__(self): +        proto.EventsComponentService.__init__(self) + +    def signal(self, controller, request, done): +        """ +        Receive a signal and run callbacks registered for that signal. + +        This method is called whenever a signal request is received from +        server. + +        @param controller: used to mediate a single method call +        @type controller: protobuf.socketrpc.controller.SocketRpcController +        @param request: the request received from the component +        @type request: leap.common.events.events_pb2.SignalRequest +        @param done: callback to be called when done +        @type done: protobuf.socketrpc.server.Callback +        """ +        logger.info('Received signal from server: %s' % str(request)) + +        # run registered callbacks +        # TODO: verify authentication using mac in incoming message +        if request.event in registered_callbacks: +            for (_, cbk) in registered_callbacks[request.event]: +                # callbacks should be prepared to receive a +                # events_pb2.SignalRequest. +                cbk(request) + +        # send response back to server +        response = proto.EventResponse() +        response.status = proto.EventResponse.OK +        done.run(response) + + +class EventsComponentDaemon(daemon.EventsSingletonDaemon): +    """ +    A daemon that listens for incoming events from server. +    """ + +    @classmethod +    def ensure(cls, port): +        """ +        Make sure the daemon is running on the given port. + +        @param port: the port in which the daemon should listen +        @type port: int + +        @return: a daemon instance +        @rtype: EventsComponentDaemon +        """ +        return cls.ensure_service(port, EventsComponentService()) diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py new file mode 100644 index 0000000..09f3c2f --- /dev/null +++ b/src/leap/common/events/daemon.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# daemon.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +A singleton daemon for running RPC services using protobuf.socketrpc. +""" + + +import logging +import threading + + +from protobuf.socketrpc.server import ( +    SocketRpcServer, +    ThreadedTCPServer, +    SocketHandler, +) + + +logger = logging.getLogger(__name__) + + +class ServiceAlreadyRunningException(Exception): +    """ +    Raised whenever a service is already running in this process but someone +    attemped to start it in a different port. +    """ + + +class EventsRpcServer(SocketRpcServer): +    """ +    RPC server used in server and component interfaces to receive messages. +    """ + +    def __init__(self, port, host='localhost'): +        """ +        Initialize a RPC server. + +        @param port: the port in which to listen for incoming messages +        @type port: int +        @param host: the address to bind to +        @type host: str +        """ +        SocketRpcServer.__init__(self, port, host) +        self._server = None + +    def run(self): +        """ +        Run the server. +        """ +        logger.info('Running server on port %d.' % self.port) +        # parent implementation does not hold the server instance, so we do it +        # here. +        self._server = ThreadedTCPServer((self.host, self.port), +                                         SocketHandler, self) +        # if we chose to use a random port, fetch the port number info. +        if self.port is 0: +            self.port = self._server.socket.getsockname()[1] +        self._server.serve_forever() + +    def stop(self): +        """ +        Stop the server. +        """ +        self._server.shutdown() + + +class EventsSingletonDaemon(threading.Thread): +    """ +    Singleton class for for launching and terminating a daemon. + +    This class is used so every part of the mechanism that needs to listen for +    messages can launch its own daemon (thread) to do the job. +    """ + +    # Singleton instance +    __instance = None + +    def __new__(cls, *args, **kwargs): +        """ +        Return a singleton instance if it exists or create and initialize one. +        """ +        if len(args) is not 2: +            raise TypeError("__init__() takes exactly 2 arguments (%d given)" +                            % len(args)) +        if cls.__instance is None: +            cls.__instance = object.__new__( +                EventsSingletonDaemon, *args, **kwargs) +            cls.__initialize(cls.__instance, args[0], args[1]) +        return cls.__instance + +    @staticmethod +    def __initialize(self, port, service): +        """ +        Initialize a singleton daemon. + +        This is a static method disguised as instance method that actually +        does the initialization of the daemon instance. + +        @param port: the port in which to listen for incoming messages +        @type port: int +        @param service: the service to provide in this daemon +        @type service: google.protobuf.service.Service +        """ +        threading.Thread.__init__(self) +        self._port = port +        self._service = service +        self._server = EventsRpcServer(self._port) +        self._server.registerService(self._service) +        self.daemon = True + +    def __init__(self): +        """ +        Singleton placeholder initialization method. + +        Initialization is made in __new__ so we can always return the same +        instance upon object creation. +        """ +        pass + +    @classmethod +    def ensure(cls, port): +        """ +        Make sure the daemon instance is running. + +        Each implementation of this method should call `self.ensure_service` +        with the appropriate service from the `events.proto` definitions, and +        return the daemon instance. + +        @param port: the port in which the daemon should be listening +        @type port: int + +        @return: a daemon instance +        @rtype: EventsSingletonDaemon +        """ +        raise NotImplementedError(self.ensure) + +    @classmethod +    def ensure_service(cls, port, service): +        """ +        Start the singleton instance if not already running. + +        Might return ServiceAlreadyRunningException + +        @param port: the port in which the daemon should be listening +        @type port: int + +        @return: a daemon instance +        @rtype: EventsSingletonDaemon +        """ +        daemon = cls(port, service) +        if not daemon.is_alive(): +            daemon.start() +        elif port and port != cls.__instance._port: +            # service is running in this process but someone is trying to +            # start it in another port +            raise ServiceAlreadyRunningException( +                "Service is already running in this process on port %d." +                % self.__instance._port) +        return daemon + +    @classmethod +    def get_instance(cls): +        """ +        Retrieve singleton instance of this daemon. + +        @return: a daemon instance +        @rtype: EventsSingletonDaemon +        """ +        return cls.__instance + +    def run(self): +        """ +        Run the server. +        """ +        self._server.run() + +    def stop(self): +        """ +        Stop the daemon. +        """ +        self._server.stop() + +    def get_port(self): +        """ +        Retrieve the value of the port to which the service running in this +        daemon is binded to. + +        @return: the port to which the daemon is binded to +        @rtype: int +        """ +        if self._port is 0: +            self._port = self._server.port +        return self._port diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto new file mode 100644 index 0000000..29388b8 --- /dev/null +++ b/src/leap/common/events/events.proto @@ -0,0 +1,69 @@ +// signal.proto +// Copyright (C) 2013 LEA +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package leap.common.events; + +enum Event { +  CLIENT_SESSION_ID = 1; +  CLIENT_UID = 2; +  SOLEDAD_CREATING_KEYS = 3; +  SOLEDAD_DONE_CREATING_KEYS = 4; +  SOLEDAD_UPLOADING_KEYS = 5; +  SOLEDAD_DONE_UPLOADING_KEYS = 6; +  SOLEDAD_DOWNLOADING_KEYS = 7; +  SOLEDAD_DONE_DOWNLOADING_KEYS = 8; +  SOLEDAD_NEW_DATA_TO_SYNC = 9; +  SOLEDAD_DONE_DATA_SYNC = 10; +  UPDATER_NEW_UPDATES = 11; +  UPDATER_DONE_UPDATING = 12; +} + +message SignalRequest { +  required Event event = 1; +  required string content = 2; +  required string mac_method = 3; +  required bytes mac = 4; +  optional string enc_method = 5; +  optional bool error_occurred = 6; +} + +message RegisterRequest { +  required Event event = 1; +  required int32 port = 2; +  required string mac_method = 3; +  required bytes mac = 4; +} + +message EventResponse { + +  enum Status { +    OK = 1; +    UNAUTH = 2; +    ERROR = 3; +  } + +  required Status status = 1; +  optional string result = 2; +} + +service EventsServerService { +  rpc register(RegisterRequest) returns (EventResponse); +  rpc signal(SignalRequest) returns (EventResponse); +} + +service EventsComponentService { +  rpc signal(SignalRequest) returns (EventResponse); +} diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py new file mode 100644 index 0000000..1d278cc --- /dev/null +++ b/src/leap/common/events/events_pb2.py @@ -0,0 +1,364 @@ +# Generated by the protocol buffer compiler.  DO NOT EDIT! + +from google.protobuf import descriptor +from google.protobuf import message +from google.protobuf import reflection +from google.protobuf import service +from google.protobuf import service_reflection +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + + +DESCRIPTOR = descriptor.FileDescriptor( +    name='events.proto', +    package='leap.common.events', +    serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xd5\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x32\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse') + +_EVENT = descriptor.EnumDescriptor( +    name='Event', +    full_name='leap.common.events.Event', +    filename=None, +    file=DESCRIPTOR, +    values=[ +        descriptor.EnumValueDescriptor( +            name='CLIENT_SESSION_ID', index=0, number=1, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='CLIENT_UID', index=1, number=2, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_CREATING_KEYS', index=2, number=3, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='UPDATER_NEW_UPDATES', index=10, number=11, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='UPDATER_DONE_UPDATING', index=11, number=12, +            options=None, +            type=None), +    ], +    containing_type=None, +    options=None, +    serialized_start=432, +    serialized_end=773, +) + + +CLIENT_SESSION_ID = 1 +CLIENT_UID = 2 +SOLEDAD_CREATING_KEYS = 3 +SOLEDAD_DONE_CREATING_KEYS = 4 +SOLEDAD_UPLOADING_KEYS = 5 +SOLEDAD_DONE_UPLOADING_KEYS = 6 +SOLEDAD_DOWNLOADING_KEYS = 7 +SOLEDAD_DONE_DOWNLOADING_KEYS = 8 +SOLEDAD_NEW_DATA_TO_SYNC = 9 +SOLEDAD_DONE_DATA_SYNC = 10 +UPDATER_NEW_UPDATES = 11 +UPDATER_DONE_UPDATING = 12 + + +_EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( +    name='Status', +    full_name='leap.common.events.EventResponse.Status', +    filename=None, +    file=DESCRIPTOR, +    values=[ +        descriptor.EnumValueDescriptor( +            name='OK', index=0, number=1, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='UNAUTH', index=1, number=2, +            options=None, +            type=None), +        descriptor.EnumValueDescriptor( +            name='ERROR', index=2, number=3, +            options=None, +            type=None), +    ], +    containing_type=None, +    options=None, +    serialized_start=390, +    serialized_end=429, +) + + +_SIGNALREQUEST = descriptor.Descriptor( +    name='SignalRequest', +    full_name='leap.common.events.SignalRequest', +    filename=None, +    file=DESCRIPTOR, +    containing_type=None, +    fields=[ +        descriptor.FieldDescriptor( +            name='event', full_name='leap.common.events.SignalRequest.event', index=0, +            number=1, type=14, cpp_type=8, label=2, +            has_default_value=False, default_value=1, +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='content', full_name='leap.common.events.SignalRequest.content', index=1, +            number=2, type=9, cpp_type=9, label=2, +            has_default_value=False, default_value=unicode("", "utf-8"), +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, +            number=3, type=9, cpp_type=9, label=2, +            has_default_value=False, default_value=unicode("", "utf-8"), +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, +            number=4, type=12, cpp_type=9, label=2, +            has_default_value=False, default_value="", +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, +            number=5, type=9, cpp_type=9, label=1, +            has_default_value=False, default_value=unicode("", "utf-8"), +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, +            number=6, type=8, cpp_type=7, label=1, +            has_default_value=False, default_value=False, +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +    ], +    extensions=[ +    ], +    nested_types=[], +    enum_types=[ +    ], +    options=None, +    is_extendable=False, +    extension_ranges=[], +    serialized_start=37, +    serialized_end=188, +) + + +_REGISTERREQUEST = descriptor.Descriptor( +    name='RegisterRequest', +    full_name='leap.common.events.RegisterRequest', +    filename=None, +    file=DESCRIPTOR, +    containing_type=None, +    fields=[ +        descriptor.FieldDescriptor( +            name='event', full_name='leap.common.events.RegisterRequest.event', index=0, +            number=1, type=14, cpp_type=8, label=2, +            has_default_value=False, default_value=1, +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='port', full_name='leap.common.events.RegisterRequest.port', index=1, +            number=2, type=5, cpp_type=1, label=2, +            has_default_value=False, default_value=0, +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, +            number=3, type=9, cpp_type=9, label=2, +            has_default_value=False, default_value=unicode("", "utf-8"), +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, +            number=4, type=12, cpp_type=9, label=2, +            has_default_value=False, default_value="", +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +    ], +    extensions=[ +    ], +    nested_types=[], +    enum_types=[ +    ], +    options=None, +    is_extendable=False, +    extension_ranges=[], +    serialized_start=190, +    serialized_end=296, +) + + +_EVENTRESPONSE = descriptor.Descriptor( +    name='EventResponse', +    full_name='leap.common.events.EventResponse', +    filename=None, +    file=DESCRIPTOR, +    containing_type=None, +    fields=[ +        descriptor.FieldDescriptor( +            name='status', full_name='leap.common.events.EventResponse.status', index=0, +            number=1, type=14, cpp_type=8, label=2, +            has_default_value=False, default_value=1, +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +        descriptor.FieldDescriptor( +            name='result', full_name='leap.common.events.EventResponse.result', index=1, +            number=2, type=9, cpp_type=9, label=1, +            has_default_value=False, default_value=unicode("", "utf-8"), +            message_type=None, enum_type=None, containing_type=None, +            is_extension=False, extension_scope=None, +            options=None), +    ], +    extensions=[ +    ], +    nested_types=[], +    enum_types=[ +        _EVENTRESPONSE_STATUS, +    ], +    options=None, +    is_extendable=False, +    extension_ranges=[], +    serialized_start=299, +    serialized_end=429, +) + + +_SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT +_REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT +_EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS +_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE + + +class SignalRequest(message.Message): +    __metaclass__ = reflection.GeneratedProtocolMessageType +    DESCRIPTOR = _SIGNALREQUEST + +    # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) + + +class RegisterRequest(message.Message): +    __metaclass__ = reflection.GeneratedProtocolMessageType +    DESCRIPTOR = _REGISTERREQUEST + +    # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) + + +class EventResponse(message.Message): +    __metaclass__ = reflection.GeneratedProtocolMessageType +    DESCRIPTOR = _EVENTRESPONSE + +    # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) + + +_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( +    name='EventsServerService', +    full_name='leap.common.events.EventsServerService', +    file=DESCRIPTOR, +    index=0, +    options=None, +    serialized_start=776, +    serialized_end=961, +    methods=[ +        descriptor.MethodDescriptor( +            name='register', +            full_name='leap.common.events.EventsServerService.register', +            index=0, +            containing_service=None, +            input_type=_REGISTERREQUEST, +            output_type=_EVENTRESPONSE, +            options=None, +        ), +        descriptor.MethodDescriptor( +            name='signal', +            full_name='leap.common.events.EventsServerService.signal', +            index=1, +            containing_service=None, +            input_type=_SIGNALREQUEST, +            output_type=_EVENTRESPONSE, +            options=None, +        ), +    ]) + + +class EventsServerService(service.Service): +    __metaclass__ = service_reflection.GeneratedServiceType +    DESCRIPTOR = _EVENTSSERVERSERVICE + + +class EventsServerService_Stub(EventsServerService): +    __metaclass__ = service_reflection.GeneratedServiceStubType +    DESCRIPTOR = _EVENTSSERVERSERVICE + + +_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor( +    name='EventsComponentService', +    full_name='leap.common.events.EventsComponentService', +    file=DESCRIPTOR, +    index=1, +    options=None, +    serialized_start=963, +    serialized_end=1067, +    methods=[ +        descriptor.MethodDescriptor( +            name='signal', +            full_name='leap.common.events.EventsComponentService.signal', +            index=0, +            containing_service=None, +            input_type=_SIGNALREQUEST, +            output_type=_EVENTRESPONSE, +            options=None, +        ), +    ]) + + +class EventsComponentService(service.Service): +    __metaclass__ = service_reflection.GeneratedServiceType +    DESCRIPTOR = _EVENTSCOMPONENTSERVICE + + +class EventsComponentService_Stub(EventsComponentService): +    __metaclass__ = service_reflection.GeneratedServiceStubType +    DESCRIPTOR = _EVENTSCOMPONENTSERVICE + +# @@protoc_insertion_point(module_scope) diff --git a/src/leap/common/events/mac_auth.py b/src/leap/common/events/mac_auth.py new file mode 100644 index 0000000..49d48f7 --- /dev/null +++ b/src/leap/common/events/mac_auth.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# mac_auth.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Authentication system for events. + +This is not implemented yet. +""" + + +class MacMethod(object): +    """ +    Representation of possible MAC authentication methods. +    """ + +    MAC_NONE = 'none' +    MAC_HMAC = 'hmac' diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py new file mode 100644 index 0000000..5cc1add --- /dev/null +++ b/src/leap/common/events/server.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# server.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +A server for the events mechanism. + +A server can receive different kinds of requests from components: + +  1. Registration request: store component port number to be notified when +     a specific signal arrives. + +  2. Signal request: redistribute the signal to registered components. +""" + + +import logging +import sets + + +from protobuf.socketrpc import RpcService +from leap.common.events import ( +    events_pb2 as proto, +    daemon, +) + + +logger = logging.getLogger(__name__) + + +SERVER_PORT = 8090 + +# the `registered_components` dictionary below should have the following +# format: +# +#     { event_signal: [ port, ... ], ... } +# +registered_components = {} + + +def ensure_server(port=SERVER_PORT): +    """ +    Make sure the server is running on the given port. + +    Attempt to connect to given local port. Upon success, assume that the +    events server has already been started. Upon failure, start events server. + +    @param port: the port in which server should be listening +    @type port: int + +    @return: the daemon instance or nothing +    @rtype: EventsServerDaemon or None +    """ +    try: +        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +        s.connect(('localhost', port)) +        s.close() +        logger.info('Server is already running on port %d.', port) +        return None +    except socket.error: +        logger.info('Launching server on port %d.', port) +        return EventsServerDaemon.ensure(port) + + +class EventsServerService(proto.EventsServerService): +    """ +    Service for receiving events in components. +    """ + +    def register(self, controller, request, done): +        """ +        Register a component port to be signaled when specific events come in. + +        @param controller: used to mediate a single method call +        @type controller: protobuf.socketrpc.controller.SocketRpcController +        @param request: the request received from the component +        @type request: leap.common.events.events_pb2.RegisterRequest +        @param done: callback to be called when done +        @type done: protobuf.socketrpc.server.Callback +        """ +        logger.info("Received registration request: %s" % str(request)) +        # add component port to signal list +        if request.event not in registered_components: +            registered_components[request.event] = sets.Set() +        registered_components[request.event].add(request.port) +        # send response back to component +        response = proto.EventResponse() +        response.status = proto.EventResponse.OK +        done.run(response) + +    def signal(self, controller, request, done): +        """ +        Perform an RPC call to signal all components registered to receive a +        specific signal. + +        @param controller: used to mediate a single method call +        @type controller: protobuf.socketrpc.controller.SocketRpcController +        @param request: the request received from the component +        @type request: leap.common.events.events_pb2.SignalRequest +        @param done: callback to be called when done +        @type done: protobuf.socketrpc.server.Callback +        """ +        logger.info('Received signal from component: %s', str(request)) +        # send signal to all registered components +        # TODO: verify signal auth +        if request.event in registered_components: +            for port in registered_components[request.event]: + +                def callback(req, resp): +                    logger.info("Signal received by " + str(port)) + +                service = RpcService(proto.EventsComponentService_Stub, +                                     port, 'localhost') +                service.signal(request, callback=callback) +        # send response back to component +        response = proto.EventResponse() +        response.status = proto.EventResponse.OK +        done.run(response) + + +class EventsServerDaemon(daemon.EventsSingletonDaemon): +    """ +    Singleton class for starting an events server daemon. +    """ + +    @classmethod +    def ensure(cls, port): +        """ +        Make sure the daemon is running on the given port. + +        @param port: the port in which the daemon should listen +        @type port: int + +        @return: a daemon instance +        @rtype: EventsServerDaemon +        """ +        return cls.ensure_service(port, EventsServerService()) diff --git a/src/leap/common/tests/__init__.py b/src/leap/common/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/leap/common/tests/__init__.py diff --git a/src/leap/common/tests/test_events.py b/src/leap/common/tests/test_events.py new file mode 100644 index 0000000..8c0bd36 --- /dev/null +++ b/src/leap/common/tests/test_events.py @@ -0,0 +1,200 @@ +## -*- coding: utf-8 -*- +# test_events.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import sets +import time +from protobuf.socketrpc import RpcService +from leap.common import events +from leap.common.events import ( +    server, +    component, +    mac_auth, +) +from leap.common.events.events_pb2 import ( +    EventsServerService, +    EventsServerService_Stub, +    EventResponse, +    SignalRequest, +    RegisterRequest, +    SOLEDAD_CREATING_KEYS, +    CLIENT_UID, +) + + +port = 8090 + +received = False +local_callback_executed = False + + +def callback(request, reponse): +    return True + + +class EventsTestCase(unittest.TestCase): + +    @classmethod +    def setUpClass(cls): +        server.EventsServerDaemon.ensure(8090) +        cls.callbacks = events.component.registered_callbacks + +    @classmethod +    def tearDownClass(cls): +        # give some time for requests to be processed. +        time.sleep(1) + +    def setUp(self): +        super(EventsTestCase, self).setUp() + +    def tearDown(self): +        #events.component.registered_callbacks = {} +        server.registered_components = {} +        super(EventsTestCase, self).tearDown() + +    def test_service_singleton(self): +        """ +        Ensure that there's always just one instance of the server daemon +        running. +        """ +        service1 = server.EventsServerDaemon.ensure(8090) +        service2 = server.EventsServerDaemon.ensure(8090) +        self.assertEqual(service1, service2, +                         "Can't get singleton class for service.") + +    def test_component_register(self): +        """ +        Ensure components can register callbacks. +        """ +        self.assertTrue(1 not in self.callbacks, +                        'There should should be no callback for this signal.') +        events.register(1, lambda x: True) +        self.assertTrue(1 in self.callbacks, +                        'Could not register signal in local component.') +        events.register(2, lambda x: True) +        self.assertTrue(1 in self.callbacks, +                        'Could not register signal in local component.') +        self.assertTrue(2 in self.callbacks, +                        'Could not register signal in local component.') + +    def test_register_signal_replace(self): +        """ +        Make sure components can replace already registered callbacks. +        """ +        sig = 3 +        cbk = lambda x: True +        events.register(sig, cbk, uid=1) +        self.assertRaises(Exception, events.register, sig, lambda x: True, +                          uid=1) +        events.register(sig, lambda x: True, uid=1, replace=True) +        self.assertTrue(sig in self.callbacks, 'Could not register signal.') +        self.assertEqual(1, len(self.callbacks[sig]), +                         'Wrong number of registered callbacks.') + +    def test_signal_response_status(self): +        """ +        Ensure there's an appropriate response from server when signaling. +        """ +        sig = 4 +        request = SignalRequest() +        request.event = sig +        request.content = 'my signal contents' +        request.mac_method = mac_auth.MacMethod.MAC_NONE +        request.mac = "" +        service = RpcService(EventsServerService_Stub, port, 'localhost') +        # test synch +        response = service.signal(request, timeout=1000) +        self.assertEqual(EventResponse.OK, response.status, +                         'Wrong response status.') +        # test asynch + +        def local_callback(request, response): +            global local_callback_executed +            local_callback_executed = True + +        events.register(sig, local_callback) +        service.signal(request, callback=local_callback) +        time.sleep(0.1) +        self.assertTrue(local_callback_executed, +                        'Local callback did not execute.') + +    def test_events_server_service_register(self): +        """ +        Ensure the server can register components to be signaled. +        """ +        sig = 5 +        request = RegisterRequest() +        request.event = sig +        request.port = 8091 +        request.mac_method = mac_auth.MacMethod.MAC_NONE +        request.mac = "" +        service = RpcService(EventsServerService_Stub, port, 'localhost') +        complist = server.registered_components +        self.assertEqual({}, complist, +                         'There should be no registered_ports when ' +                         'server has just been created.') +        response = service.register(request, timeout=1000) +        self.assertTrue(sig in complist, "Signal not registered succesfully.") +        self.assertTrue(8091 in complist[sig], +                        'Failed registering component port.') + +    def test_component_request_register(self): +        """ +        Ensure components can register themselves with server. +        """ +        sig = 6 +        complist = server.registered_components +        self.assertTrue(sig not in complist, +                        'There should be no registered components for this ' +                        'signal.') +        events.register(sig, lambda x: True) +        time.sleep(0.1) +        port = component.EventsComponentDaemon.get_instance().get_port() +        self.assertTrue(sig in complist, 'Failed registering component.') +        self.assertTrue(port in complist[sig], +                        'Failed registering component port.') + +    def test_component_receives_signal(self): +        """ +        Ensure components can receive signals. +        """ +        sig = 7 + +        def getsig(param=None): +            global received +            received = True + +        events.register(sig, getsig) +        request = SignalRequest() +        request.event = sig +        request.content = "" +        request.mac_method = mac_auth.MacMethod.MAC_NONE +        request.mac = "" +        service = RpcService(EventsServerService_Stub, port, 'localhost') +        response = service.signal(request, timeout=1000) +        self.assertTrue(response is not None, 'Did not receive response.') +        time.sleep(0.5) +        self.assertTrue(received, 'Did not receive signal back.') + +    def test_component_send_signal(self): +        """ +        Ensure components can send signals. +        """ +        sig = 8 +        response = events.signal(sig) +        self.assertTrue(response.status == response.OK, +                        'Received wrong response status when signaling.') | 
