Beispiel #1
0
    def __init__(self, address, args, device=None):
        name = args['name']
        service = args['service']
        url = args.get('url', 'ws://127.0.0.1:9400/services')

        super(ServiceDelegateTile, self).__init__(address, name)

        self._service = service
        self._client = ServiceStatusClient(url)
Beispiel #2
0
 def _try_initialize_supervisor(self):
     """Check for the existence of a supervisor"""
     try:
         self.supervisor = ServiceStatusClient('ws://localhost:9400/services')
         self.supervisor.register_service('gateway', 'Device Gateway')
         self.supervisor.post_info('gateway', "Service started successfully")
         self.supervisor.post_headline('gateway', states.INFO_LEVEL, 'Started successfully')
         return True
     except Exception:  # pylint: disable=W0703
         self._logger.info("No supervisor present")
         return False
Beispiel #3
0
def rpc_agent(supervisor):
    """Register an RPC agent on one of the clients."""

    visor, _client1, client2 = supervisor

    port = visor.port
    client1 = ServiceStatusClient('ws://127.0.0.1:%d/services' % port,
                                  dispatcher=BasicRPCDispatcher(),
                                  agent='service_1')

    yield visor, client1, client2

    client1.stop()
Beispiel #4
0
def supervisor():
    """A running supervisor with two connected status clients."""

    info = {
        'expected_services': [{
            "short_name": "service_1",
            "long_name": "A test service"
        }, {
            "short_name": "service_2",
            "long_name": "A second test service"
        }],
        'port':
        'unused'  # Bind an unused port for testing, the value will appear on visor.port after visor.loaded is set
    }

    visor = IOTileSupervisor(info)

    visor.start()
    signaled = visor.loaded.wait(2.0)
    if not signaled:
        raise ValueError("Could not start supervisor service")

    port = visor.port

    client1 = ServiceStatusClient('ws://127.0.0.1:%d/services' % port)
    client2 = ServiceStatusClient('ws://127.0.0.1:%d/services' % port)

    yield visor, client1, client2

    client1.stop()
    client2.stop()
    visor.stop()
Beispiel #5
0
    def _try_report_status(self):
        """Periodic callback to report our gateway's status."""

        if self.supervisor is None:
            try:
                self.supervisor = ServiceStatusClient(
                    'ws://localhost:9400/services')
                self.supervisor.register_service('gateway', 'Device Gateway')
                self.supervisor.post_info('gateway',
                                          "Service started successfully")
                self.supervisor.post_headline('gateway', states.INFO_LEVEL,
                                              'Started successfully')
            except Exception:  # pylint: disable=W0703
                self._logger.exception(
                    "Exception trying to create a ServiceStatusClient")
                return

        self.supervisor.update_state('gateway', states.RUNNING)
        self.supervisor.send_heartbeat('gateway')
Beispiel #6
0
class ServiceDelegateTile(VirtualTile):
    """A tile that delegates all RPCs to a service using the IOTileSupervisor.

    Args:
        address (int): The address that we are being created at
        args (dict): Configuration arguments with the following required keys:
            name (str): The 6 byte name we should return for this tile
            service (str): The short name of the service that we should forward
                RPCs on to when someone calls an RPC on us.
            url (str): The URL of the supervisor that we should connect to.  If
                no url is specified, the default is 127.0.0.1:9400/services which
                is the supervisor default.
    """
    def __init__(self, address, args, device=None):
        name = args['name']
        service = args['service']
        url = args.get('url', 'ws://127.0.0.1:9400/services')

        super(ServiceDelegateTile, self).__init__(address, name)

        self._service = service
        self._client = ServiceStatusClient(url)

    def has_rpc(self, rpc_id):
        """Check if an RPC is defined.

        Args:
            rpc_id (int): The RPC to check

        Returns:
            bool: Whether it exists
        """

        # Since we don't know what RPCs are defined in the service on the
        # other side of the supervisor, we need to actually try to call
        # each RPC and fail at that point if they don't exist.
        return True

    def call_rpc(self, rpc_id, payload=bytes()):
        """Call an RPC by its ID.

        Args:
            rpc_id (int): The number of the RPC
            payload (bytes): A byte string of payload parameters up to 20 bytes

        Returns:
            str: The response payload from the RPC
        """

        # If we define the RPC locally, call that one.  We use this for reporting
        # our status
        if super(ServiceDelegateTile, self).has_rpc(rpc_id):
            return super(ServiceDelegateTile, self).call_rpc(rpc_id, payload)

        # FIXME: We set the timeout here to a very large number since we don't
        # know what an appropriate timeout is and don't want to restrict the
        # run time of RPCs that could be long running.  The caller of the RPC
        # through the tile will know what an appropriate timeout is for the
        # RPC that they are trying to call.
        resp = self._client.send_rpc(self._service,
                                     rpc_id,
                                     payload,
                                     timeout=120.0)
        result = resp['result']

        if result == 'success':
            return resp['response']
        elif result == 'service_not_found':
            raise TileNotFoundError("Could not find service by name",
                                    name=self._service)
        elif result == 'rpc_not_found':
            raise RPCNotFoundError("Could not find RPC on service",
                                   name=self._service,
                                   rpc_id=rpc_id)
        elif result == 'invalid_arguments':
            raise RPCInvalidArgumentsError("Invalid arguments to RPC",
                                           name=self._service,
                                           rpc_id=rpc_id)
        elif result == 'invalid_response':
            raise RPCInvalidReturnValueError("Invalid response from RPC",
                                             name=self._service,
                                             rpc_id=rpc_id)
        elif result == 'execution_exception':
            raise InternalError("Exception raised during processing RPC",
                                name=self._service,
                                rpc_id=rpc_id)
        else:
            raise InternalError("Unknown response received from delegated RPC",
                                name=self._service,
                                rpc_id=rpc_id,
                                result=result)
Beispiel #7
0
class IOTileGateway(threading.Thread):
    """A gateway that finds IOTile devices using device adapters and serves them using agents.

    The gateway runs in separate thread in a tornado IOLoop and you can call the synchronous
    wait function to wait for it to quit.  It will loop forever unless you stop it by calling
    the stop() or stop_from_signal() methods.  These functions add a task to the gateway's
    event loop and implicitly call wait to synchronously wait until the gateway loop actually
    stops.

    IOTileGateway should be thought of as a turn-key gateway object that translates requests
    for IOTile Device access received from one or more GatewayAgents into commands sent to
    one or more DeviceAdapters.  It is a multi-device, multi-user, multi-protocol system that
    can have many connections in flight at the same time, limited only by the available resources
    on the computer that hosts it.

    The arguments dictionary to IOTileGateway class has the same format as the json parameters
    passed to the iotile-gateway script that is just a thin wrapper around this class.

    Args:
        config (dict): The configuration of the gateway.  There should be two keys set:

            agents (list):
                a list of dictionaries with the name of the agent and any arguments that
                should be passed to create it.
            adapters (list):
                a list of dictionaries with the device adapters to add into the gateway
                and any arguments that should be use to create each one.
    """

    def __init__(self, config):
        self.loop = None
        self.device_manager = None
        self.agents = []
        self.supervisor = None
        self.loaded = threading.Event()

        self._config = config
        self._logger = logging.getLogger(__name__)

        if 'agents' not in config:
            self._config['agents'] = []
            self._logger.warn("No agents defined in arguments to iotile-gateway, this is likely not what you want")
        elif 'adapters' not in config:
            self._config['adapters'] = []
            self._logger.warn("No device adapters defined in arguments to iotile-gateway, this is likely not what you want")

        super(IOTileGateway, self).__init__()

    def run(self):
        """Start the gateway and run it to completion in another thread."""

        self.loop = tornado.ioloop.IOLoop(make_current=True)  # To create a loop for each thread
        self.device_manager = device.DeviceManager(self.loop)

        # If we have an initialization error, stop trying to initialize more things and
        # just shut down cleanly
        should_close = False

        # Load in all of the gateway agents that are supposed to provide access to
        # the devices in this gateway
        for agent_info in self._config['agents']:
            if 'name' not in agent_info:
                self._logger.error("Invalid agent information in gateway config, info=%s, missing_key=%s", str(agent_info), 'name')
                should_close = True
                break

            agent_name = agent_info['name']
            agent_args = agent_info.get('args', {})

            self._logger.info("Loading agent by name '%s'", agent_name)
            agent_class = find_entry_point('iotile.gateway_agent', agent_name)
            try:
                agent = agent_class(agent_args, self.device_manager, self.loop)
                agent.start()
                self.agents.append(agent)
            except Exception:  # pylint: disable=W0703
                self._logger.exception("Could not load gateway agent %s, quitting", agent_name)
                should_close = True
                break

        # Load in all of the device adapters that provide access to actual devices
        if not should_close:
            for adapter_info in self._config['adapters']:
                if 'name' not in adapter_info:
                    self._logger.error("Invalid adapter information in gateway config, info=%s, missing_key=%s", str(adapter_info), 'name')
                    should_close = True
                    break

                adapter_name = adapter_info['name']
                port_string = adapter_info.get('port', None)

                self._logger.info("Loading device adapter by name '%s' and port '%s'", adapter_name, port_string)

                try:
                    adapter_class = find_entry_point('iotile.device_adapter', adapter_name)
                    adapter = adapter_class(port_string)
                    self.device_manager.add_adapter(adapter)
                except Exception:  # pylint: disable=W0703
                    self._logger.exception("Could not load device adapter %s, quitting", adapter_name)
                    should_close = True

        if should_close:
            self.loop.add_callback(self._stop_loop)
        else:
            # Notify that we have now loaded all plugins and are starting operation (once the loop starts)
            self.loop.add_callback(lambda: self.loaded.set())

            # Try to regularly update a supervisor about our status if a supervisor is running
            if self._try_initialize_supervisor():
                callback = tornado.ioloop.PeriodicCallback(self._try_report_status, 60000)
                callback.start()

        self.loop.start()

        # The loop has been closed, finish and quit
        self._logger.critical("Done stopping loop")

    def _try_initialize_supervisor(self):
        """Check for the existence of a supervisor"""
        try:
            self.supervisor = ServiceStatusClient('ws://localhost:9400/services')
            self.supervisor.register_service('gateway', 'Device Gateway')
            self.supervisor.post_info('gateway', "Service started successfully")
            self.supervisor.post_headline('gateway', states.INFO_LEVEL, 'Started successfully')
            return True
        except Exception:  # pylint: disable=W0703
            self._logger.info("No supervisor present")
            return False

    def _try_report_status(self):
        """Periodic callback to report our gateway's status."""
        self.supervisor.update_state('gateway', states.RUNNING)
        self.supervisor.send_heartbeat('gateway')

    def _stop_loop(self):
        """Cleanly stop the gateway and close down the IOLoop.

        This function must be called only by being added to our event loop using add_callback.
        """

        self._logger.critical("Stopping gateway")
        self._logger.info("Stopping gateway agents")

        for agent in self.agents:
            try:
                agent.stop()
            except Exception:  # pylint: disable=W0703
                self._logger.exception("Error stopping gateway agent")

        self._logger.critical('Stopping device adapters')

        try:
            self.device_manager.stop()
        except Exception:  # pylint: disable=W0703
            self._logger.exception("Error stopping device adapters")

        if self.supervisor:
            try:
                self.supervisor.update_state('gateway', states.STOPPED)
                self.supervisor.post_headline('gateway', states.INFO_LEVEL, 'Stoppped by supervisor')
            except Exception:  # pylint: disable=W0703
                self._logger.exception("Error updating service status to stopped")

            try:
                self.supervisor.stop()
            except Exception:  # pylint: disable=W0703
                self._logger.exception("Error stopping IOLoop")

        self.loop.stop()
        self._logger.critical('Stopping event loop and shutting down')

    def stop(self):
        """Stop the gateway manager and synchronously wait for it to stop."""

        self.loop.add_callback(self._stop_loop)
        self.wait()

    def wait(self):
        """Wait for this gateway to shut down.

        We need this special function because waiting inside
        join will cause signals to not get handled.
        """

        while self.is_alive():
            try:
                self.join(timeout=0.1)
            except IOError:
                pass  # IOError comes when this call is interrupted in a signal handler

    def stop_from_signal(self):
        """Stop the gateway from a signal handler, not waiting for it to stop."""

        self.loop.add_callback_from_signal(self._stop_loop)