Exemple #1
0
    def __init__(self, url, sk=None, name='Node'):
        self.log = get_logger("{}.ReactorDaemon".format(name))
        self.log.info("ReactorDaemon started with url {}".format(url))
        self.url = url

        # Comment out below for more granularity in debugging
        # self.log.setLevel(logging.INFO)

        # TODO optimize cache
        self.ip_cache = CappedDict(max_size=64)

        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        # Register signal handler to teardown
        signal.signal(signal.SIGTERM, self._signal_teardown)

        self.discovery_mode = 'test' if os.getenv(
            'TEST_NAME') else 'neighborhood'
        self.dht = DHT(sk=sk,
                       mode=self.discovery_mode,
                       loop=self.loop,
                       alpha=ALPHA,
                       ksize=KSIZE,
                       daemon=self,
                       max_peers=MAX_PEERS,
                       block=False,
                       cmd_cli=False,
                       wipe_certs=True)

        self.context = zmq.asyncio.Context()
        self.socket = self.context.socket(
            zmq.PAIR)  # For communication with main process
        self.socket.connect(self.url)

        # Set Executor _parent_name to differentiate between nodes in log files
        Executor._parent_name = name

        self.executors = {
            name: executor(self.loop, self.socket, self.dht.network.ironhouse)
            for name, executor in Executor.registry.items()
        }

        try:
            self.loop.run_until_complete(self._recv_messages())
        except Exception as e:
            err_msg = '\n' + '!' * 64 + '\nDeamon Loop terminating with exception:\n' + str(
                traceback.format_exc())
            err_msg += '\n' + '!' * 64 + '\n'
            self.log.error(err_msg)
        finally:
            self._teardown()
def run_node():
    import asyncio, os, zmq.auth
    from cilantro.protocol.overlay.dht import DHT
    from cilantro.logger import get_logger
    log = get_logger(__name__)

    signing_keys = {
        'node_1':
        '06391888e37a48cef1ded85a375490df4f9b2c74f7723e88c954a055f3d2685a',
        'node_2':
        '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8',
        'node_3':
        'f9489f880ef1a8b2ccdecfcad073e630ede1dd190c3b436421f665f767704c55',
        'node_4':
        '8ddaf072b9108444e189773e2ddcb4cbd2a76bbf3db448e55d0bfc131409a197',
        'node_5':
        '5664ec7306cc22e56820ae988b983bdc8ebec8246cdd771cfee9671299e98e3c',
        'node_6':
        '20b577e71e0c3bddd3ae78c0df8f7bb42b29b0c0ce9ca42a44e6afea2912d17b'
    }
    sk = signing_keys.get(os.getenv('HOSTNAME'))

    dht = DHT(mode='test', sk=sk, wipe_certs=True, block=False)

    dht.loop.run_forever()
Exemple #3
0
    def test_join_network_as_sole_non_master_node(self):
        def run(self):
            self.loop.call_soon_threadsafe(self.loop.stop)

        with self.assertRaises(ErrorWithArgs):
            self.node = DHT(sk=self.witness['sk'],
                                mode='test',
                                port=3321,
                                keyname='node',
                                wipe_certs=True,
                                loop=self.loop,
                                max_wait=0.1,
                                block=False,
                                retry_discovery=1)

        t = Timer(0.01, run, [self])
        t.start()
        self.loop.run_forever()
Exemple #4
0
    def test_join_network_as_sole_master(self):
        def run(self):
            self.node.cleanup()
            self.loop.call_soon_threadsafe(self.loop.stop)

        self.node = DHT(sk=self.master['sk'],
                            mode='test',
                            port=3321,
                            keyname='master',
                            wipe_certs=True,
                            loop=self.loop,
                            max_wait=0.1,
                            block=False)

        self.assertEqual(self.node.network.ironhouse.vk, self.master['vk'])
        t = Timer(0.01, run, [self])
        t.start()
        self.loop.run_forever()
Exemple #5
0
 def _start_service(cls, sk):
     ctx = zmq.asyncio.Context()
     cls.event_sock = ctx.socket(zmq.PUB)
     cls.event_sock.bind(cls.event_url)
     cls.discovery_mode = 'test' if os.getenv(
         'TEST_NAME') else 'neighborhood'
     cls.dht = DHT(sk=sk,
                   mode=cls.discovery_mode,
                   loop=cls.loop,
                   alpha=ALPHA,
                   ksize=KSIZE,
                   event_sock=cls.event_sock,
                   max_peers=MAX_PEERS,
                   block=False,
                   cmd_cli=False,
                   wipe_certs=True)
     cls._started = True
     cls.listener_fut = asyncio.ensure_future(cls._listen_for_cmds())
     cls.event_sock.send_json({'event': 'service_started'})
     cls.loop.run_forever()
Exemple #6
0
class ReactorDaemon:
    def __init__(self, url, sk=None, name='Node'):
        self.log = get_logger("{}.ReactorDaemon".format(name))
        self.log.info("ReactorDaemon started with url {}".format(url))
        self.url = url

        # Comment out below for more granularity in debugging
        # self.log.setLevel(logging.INFO)

        # TODO optimize cache
        self.ip_cache = CappedDict(max_size=64)

        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        # Register signal handler to teardown
        signal.signal(signal.SIGTERM, self._signal_teardown)

        self.discovery_mode = 'test' if os.getenv(
            'TEST_NAME') else 'neighborhood'
        self.dht = DHT(sk=sk,
                       mode=self.discovery_mode,
                       loop=self.loop,
                       alpha=ALPHA,
                       ksize=KSIZE,
                       daemon=self,
                       max_peers=MAX_PEERS,
                       block=False,
                       cmd_cli=False,
                       wipe_certs=True)

        self.context = zmq.asyncio.Context()
        self.socket = self.context.socket(
            zmq.PAIR)  # For communication with main process
        self.socket.connect(self.url)

        # Set Executor _parent_name to differentiate between nodes in log files
        Executor._parent_name = name

        self.executors = {
            name: executor(self.loop, self.socket, self.dht.network.ironhouse)
            for name, executor in Executor.registry.items()
        }

        try:
            self.loop.run_until_complete(self._recv_messages())
        except Exception as e:
            err_msg = '\n' + '!' * 64 + '\nDeamon Loop terminating with exception:\n' + str(
                traceback.format_exc())
            err_msg += '\n' + '!' * 64 + '\n'
            self.log.error(err_msg)
        finally:
            self._teardown()

    async def _recv_messages(self):
        try:
            # Notify parent proc that this proc is ready
            self.log.notice("Daemon notifying main proc of ready")
            self.socket.send(CHILD_RDY_SIG)

            self.log.info(
                "-- Daemon proc listening to main proc on PAIR Socket at {} --"
                .format(self.url))
            while True:
                self.log.spam(
                    "ReactorDaemon awaiting for command from main thread...")
                cmd_bin = await self.socket.recv()
                self.log.spam("Got cmd from queue: {}".format(cmd_bin))

                if cmd_bin == KILL_SIG:
                    self.log.important(
                        "Daemon Process got kill signal from main proc")
                    self._teardown()
                    return

                # Should from_bytes be in a try/catch? I suppose if we get a bad command from the main proc we might as well
                # blow up because this is very likely because of a development error, so no try/catch for now
                cmd = ReactorCommand.from_bytes(cmd_bin)
                assert cmd.class_name and cmd.func_name, "Received invalid command with no class/func name!"

                self._execute_cmd(cmd)

        except asyncio.CancelledError:
            self.log.warning("Daemon _recv_messages task canceled externally")

    def _signal_teardown(self, signal, frame):
        self.log.important("Daemon process got kill signal!")
        self._teardown()

    def _teardown(self):
        """
        Close sockets. Teardown executors. Close Event Loop.
        """
        self.log.info("[DEAMON PROC] Tearing down Reactor Daemon process")

        self.log.warning("Closing pair socket")
        self.socket.close()

        self.log.warning("Tearing down executors")
        for e in self.executors.values():
            e.teardown()

        self.dht.cleanup()

        self.log.warning("Closing event loop")
        self.loop.call_soon_threadsafe(self.loop.stop)

    def _execute_cmd(self, cmd: ReactorCommand):
        """
        Propagates a command to the appropriate executor
        :param cmd: an instance of ReactorCommand
        """
        assert isinstance(
            cmd, ReactorCommand
        ), "Cannot execute cmd {} that is not a ReactorCommand object".format(
            cmd)

        cmd_args = self._parse_cmd(cmd)
        if cmd_args:
            executor_name, executor_func, kwargs = cmd_args
        else:
            self.log.debugv(
                'Command requires VK lookup. Short circuiting from _execute_cmd.'
            )
            return

        # Sanity checks (for catching bugs mostly)
        assert executor_name in self.executors, "Executor name {} not found in executors {}"\
            .format(executor_name, self.executors)
        assert hasattr(self.executors[executor_name], executor_func), "Function {} not found on executor class {}"\
            .format(executor_func, self.executors[executor_name])

        # Execute command
        try:
            getattr(self.executors[executor_name], executor_func)(**kwargs)
        except Exception as e:
            self.log.fatal("Error executing command {}\n....error={}".format(
                cmd, e))

    def _parse_cmd(self, cmd: ReactorCommand):
        """
        Parses a cmd for execution, by extracting/preparing the necessary kwargs for execution.
        :param cmd: an instance of ReactorCommand
        :return: A tuple of 3 elements (executor_name, executor_func, kwargs). Returns None if the command specifies
        a URL with a VK instead of a IP address.
        """
        executor_name = cmd.class_name
        executor_func = cmd.func_name
        kwargs = cmd.kwargs

        # Remove class_name and func_name from kwargs. We just need these to lookup the function to call
        del kwargs['class_name']
        del kwargs['func_name']

        # Add envelope to kwargs if its in the reactor command
        if cmd.envelope_binary:
            kwargs['envelope'] = cmd.envelope_binary

        # Replace VK with IP address if necessary
        if 'url' in kwargs:
            self.log.spam("Processing command with url {}".format(
                kwargs['url']))
            url = kwargs['url']

            # Check if URL has a VK inside
            vk = IPUtils.get_vk(url)
            if vk:
                if vk == self.dht.network.ironhouse.vk:
                    ip = self.dht.ip
                else:
                    ip = self.dht.network.lookup_ip_in_cache(vk)
                if not ip:
                    self.log.debug(
                        "Could not find ip for vk {} in cache. Performing lookup in DHT."
                        .format(vk))

                    asyncio.ensure_future(self._lookup_ip(cmd, url, vk))
                    return

                new_url = IPUtils.interpolate_url(url, ip)
                kwargs['url'] = new_url

        return executor_name, executor_func, kwargs

    async def _lookup_ip(self, cmd, url, vk, *args, **kwargs):
        ip, node = None, None
        try:
            node, cached = await self.dht.network.lookup_ip(vk)
            # NOTE while secure, this is a more loose connection policy
            self.log.debugv('IP {} resolves {} into {}'.format(
                os.getenv('HOST_IP', '127.0.0.1'), vk, node))
            self.log.debugv(
                '... but is {} authorized? Until next episode!'.format(node))
            if node:
                if not self.dht.network.ironhouse.authorized_nodes.get(
                        node.id):
                    authorization = await self.dht.network.authenticate(node)
                    if not authorization:
                        node = None
            else:
                node = None

        except Exception as e:
            delim_line = '!' * 64
            err_msg = '\n\n' + delim_line + '\n' + delim_line
            err_msg += '\n ERROR CAUGHT IN LOOKUP FOR VK {}\ncalled \w args={}\nand kwargs={}\n'\
                       .format(vk, args, kwargs)
            err_msg += '\nError Message: '
            err_msg += '\n\n{}'.format(traceback.format_exc())
            err_msg += '\n' + delim_line + '\n' + delim_line
            self.log.fatal(err_msg)

        if node is None:
            kwargs = cmd.kwargs
            callback = ReactorCommand.create_callback(
                callback=StateInput.LOOKUP_FAILED, **kwargs)
            self.log.debug(
                "Sending callback failure to mainthread {}".format(callback))
            self.socket.send(callback.serialize())
            # TODO -- send callback to SM saying hey i couldnt lookup this vk

            return

        # Send interpolated command back through pipeline
        ip = node.ip if type(node) == Node else node
        new_url = IPUtils.interpolate_url(url, ip)
        kwargs = cmd.kwargs
        kwargs['url'] = new_url
        new_cmd = ReactorCommand.create_cmd(envelope=cmd.envelope, **kwargs)

        self._execute_cmd(new_cmd)