Example #1
0
    def __init__(self, **kwargs) -> None:
        """
        Initialize dialogues.

        :return: None
        """
        BaseOefSearchDialogues.__init__(self, str(OEFConnection.connection_id))
Example #2
0
    def __init__(self, **kwargs: Any) -> None:
        """
        Initialize dialogues.

        :param agent_address: the address of the agent for whom dialogues are maintained
        :return: None
        """
        Model.__init__(self, **kwargs)

        def role_from_first_message(  # pylint: disable=unused-argument
                message: Message,
                receiver_address: Address) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            return BaseOefSearchDialogue.Role.AGENT

        BaseOefSearchDialogues.__init__(
            self,
            self_address=self.context.agent_address,
            role_from_first_message=role_from_first_message,
        )
Example #3
0
    def __init__(self) -> None:
        """
        Initialize dialogues.

        :return: None
        """

        def role_from_first_message(  # pylint: disable=unused-argument
            message: Message, receiver_address: Address
        ) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            # The local connection maintains the dialogue on behalf of the node
            return OefSearchDialogue.Role.OEF_NODE

        BaseOefSearchDialogues.__init__(
            self,
            self_address=str(OEFLocalConnection.connection_id),
            role_from_first_message=role_from_first_message,
            dialogue_class=OefSearchDialogue,
        )
Example #4
0
    def __init__(self, agent_address: str) -> None:
        """
        Initialize dialogues.

        :param agent_address: the address of the agent for whom dialogues are maintained
        :return: None
        """
        BaseOefSearchDialogues.__init__(self, agent_address)
Example #5
0
    def __init__(self, **kwargs) -> None:
        """
        Initialize dialogues.

        :param agent_address: the address of the agent for whom dialogues are maintained
        :return: None
        """
        Model.__init__(self, **kwargs)
        BaseOefSearchDialogues.__init__(self, self.context.agent_address)
Example #6
0
    def setup_class(cls):
        """Set up the test."""
        cls.node = LocalNode()
        cls.node.start()

        cls.address_1 = "address_1"
        cls.connection = _make_local_connection(cls.address_1, cls.node,)
        cls.multiplexer = Multiplexer([cls.connection])

        cls.multiplexer.connect()
        cls.dialogues = OefSearchDialogues(cls.address_1)
Example #7
0
    async def connect(
        self, address: Address, writer: asyncio.Queue
    ) -> Optional[asyncio.Queue]:
        """
        Connect an address to the node.

        :param address: the address of the agent.
        :param writer: the queue where the client is listening.
        :return: an asynchronous queue, that constitutes the communication channel.
        """
        if address in self._out_queues.keys():
            return None

        assert self._in_queue is not None
        q = self._in_queue  # type: asyncio.Queue
        self._out_queues[address] = writer

        self.address = address
        self._dialogues = OefSearchDialogues(
            agent_address=str(OEFLocalConnection.connection_id)
        )
        return q
Example #8
0
    def __init__(self, self_address: Address) -> None:
        """
        Initialize dialogues.

        :return: None
        """
        def role_from_first_message(  # pylint: disable=unused-argument
                message: Message,
                receiver_address: Address) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            return OefSearchDialogue.Role.OEF_NODE

        OefSearchDialogues.__init__(
            self,
            self_address=self_address,
            role_from_first_message=role_from_first_message,
            dialogue_class=OEFNodeDialogue,
        )
Example #9
0
    def setup_class(cls):
        """Set up the test."""
        cls.node = LocalNode()
        cls.node.start()

        cls.address_1 = "address"
        cls.multiplexer = Multiplexer(
            [_make_local_connection(cls.address_1, cls.node,)]
        )

        cls.multiplexer.connect()

        # register a service.
        cls.dialogues = OefSearchDialogues(cls.address_1)
        cls.data_model = DataModel(
            "foobar",
            attributes=[Attribute("foo", int, True), Attribute("bar", str, True)],
        )
        service_description = Description(
            {"foo": 1, "bar": "baz"}, data_model=cls.data_model
        )
        register_service_request = OefSearchMessage(
            performative=OefSearchMessage.Performative.REGISTER_SERVICE,
            dialogue_reference=cls.dialogues.new_self_initiated_dialogue_reference(),
            service_description=service_description,
        )
        register_service_request.counterparty = str(OEFLocalConnection.connection_id)
        cls.sending_dialogue = cast(
            Optional[OefSearchDialogue], cls.dialogues.update(register_service_request)
        )
        assert cls.sending_dialogue is not None
        envelope = Envelope(
            to=register_service_request.counterparty,
            sender=register_service_request.sender,
            protocol_id=register_service_request.protocol_id,
            message=register_service_request,
        )
        cls.multiplexer.put(envelope)
Example #10
0
    def setup_class(cls):
        """Set up the test."""
        cls.node = LocalNode()
        cls.node.start()

        cls.address_1 = "multiplexer1"
        cls.address_2 = "multiplexer2"
        cls.multiplexer1 = Multiplexer(
            [_make_local_connection(cls.address_1, cls.node,)]
        )
        cls.multiplexer2 = Multiplexer(
            [_make_local_connection(cls.address_2, cls.node,)]
        )
        cls.multiplexer1.connect()
        cls.multiplexer2.connect()
        cls.dialogues1 = OefSearchDialogues(cls.address_1)
        cls.dialogues2 = OefSearchDialogues(cls.address_2)

        # register 'multiplexer1' as a service 'foobar'.
        cls.data_model_foobar = DataModel(
            "foobar",
            attributes=[Attribute("foo", int, True), Attribute("bar", str, True)],
        )
        service_description = Description(
            {"foo": 1, "bar": "baz"}, data_model=cls.data_model_foobar
        )
        register_service_request = OefSearchMessage(
            performative=OefSearchMessage.Performative.REGISTER_SERVICE,
            dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(),
            service_description=service_description,
        )
        register_service_request.counterparty = str(OEFLocalConnection.connection_id)
        sending_dialogue = cast(
            Optional[OefSearchDialogue], cls.dialogues1.update(register_service_request)
        )
        assert sending_dialogue is not None
        envelope = Envelope(
            to=register_service_request.counterparty,
            sender=register_service_request.sender,
            protocol_id=register_service_request.protocol_id,
            message=register_service_request,
        )
        cls.multiplexer1.put(envelope)
        wait_for_condition(lambda: len(cls.node.services) == 1, timeout=10)

        # register 'multiplexer2' as a service 'barfoo'.
        cls.data_model_barfoo = DataModel(
            "barfoo",
            attributes=[Attribute("foo", int, True), Attribute("bar", str, True)],
        )
        service_description = Description(
            {"foo": 1, "bar": "baz"}, data_model=cls.data_model_barfoo
        )
        register_service_request = OefSearchMessage(
            performative=OefSearchMessage.Performative.REGISTER_SERVICE,
            dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(),
            service_description=service_description,
        )
        register_service_request.counterparty = str(OEFLocalConnection.connection_id)
        sending_dialogue = cast(
            Optional[OefSearchDialogue], cls.dialogues2.update(register_service_request)
        )
        assert sending_dialogue is not None
        envelope = Envelope(
            to=register_service_request.counterparty,
            sender=register_service_request.sender,
            protocol_id=register_service_request.protocol_id,
            message=register_service_request,
        )

        cls.multiplexer2.put(envelope)
        wait_for_condition(lambda: len(cls.node.services) == 2, timeout=10)

        # unregister multiplexer1
        data_model = DataModel(
            "foobar",
            attributes=[Attribute("foo", int, True), Attribute("bar", str, True)],
        )
        service_description = Description(
            {"foo": 1, "bar": "baz"}, data_model=data_model
        )
        msg = OefSearchMessage(
            performative=OefSearchMessage.Performative.UNREGISTER_SERVICE,
            dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(),
            service_description=service_description,
        )
        msg.counterparty = str(OEFLocalConnection.connection_id)
        sending_dialogue = cast(Optional[OefSearchDialogue], cls.dialogues1.update(msg))
        assert sending_dialogue is not None
        envelope = Envelope(
            to=msg.counterparty,
            sender=msg.sender,
            protocol_id=msg.protocol_id,
            message=msg,
        )
        cls.multiplexer1.put(envelope)
        # ensure one service stays registered
        wait_for_condition(lambda: len(cls.node.services) == 1, timeout=10)
Example #11
0
class LocalNode:
    """A light-weight local implementation of a OEF Node."""

    def __init__(
        self, loop: AbstractEventLoop = None, logger: logging.Logger = _default_logger
    ):
        """
        Initialize a local (i.e. non-networked) implementation of an OEF Node.

        :param loop: the event loop. If None, a new event loop is instantiated.
        """
        self.services = defaultdict(lambda: [])  # type: Dict[str, List[Description]]
        self._lock = asyncio.Lock()
        self._loop = loop if loop is not None else asyncio.new_event_loop()
        self._thread = Thread(target=self._run_loop, daemon=True)

        self._in_queue = asyncio.Queue(loop=self._loop)  # type: asyncio.Queue
        self._out_queues = {}  # type: Dict[str, asyncio.Queue]

        self._receiving_loop_task = None  # type: Optional[asyncio.Task]
        self.address: Optional[Address] = None
        self._dialogues: Optional[OefSearchDialogues] = None
        self.logger = logger

    def __enter__(self):
        """Start the local node."""
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stop the local node."""
        self.stop()

    def _run_loop(self):
        """
        Run the asyncio loop.

        This method is supposed to be run only in the Multiplexer thread.
        """
        self.logger.debug("Starting threaded asyncio loop...")
        asyncio.set_event_loop(self._loop)
        self._loop.run_forever()
        self.logger.debug("Asyncio loop has been stopped.")

    async def connect(
        self, address: Address, writer: asyncio.Queue
    ) -> Optional[asyncio.Queue]:
        """
        Connect an address to the node.

        :param address: the address of the agent.
        :param writer: the queue where the client is listening.
        :return: an asynchronous queue, that constitutes the communication channel.
        """
        if address in self._out_queues.keys():
            return None

        assert self._in_queue is not None
        q = self._in_queue  # type: asyncio.Queue
        self._out_queues[address] = writer

        self.address = address
        self._dialogues = OefSearchDialogues(
            agent_address=str(OEFLocalConnection.connection_id)
        )
        return q

    def start(self):
        """Start the node."""
        if not self._loop.is_running() and not self._thread.is_alive():
            self._thread.start()
        self._receiving_loop_task = asyncio.run_coroutine_threadsafe(
            self.receiving_loop(), loop=self._loop
        )
        self.logger.debug("Local node has been started.")

    def stop(self):
        """Stop the node."""
        asyncio.run_coroutine_threadsafe(self._in_queue.put(None), self._loop).result()
        self._receiving_loop_task.result()

        if self._loop.is_running():
            self._loop.call_soon_threadsafe(self._loop.stop)
        if self._thread.is_alive():
            self._thread.join()

    async def receiving_loop(self):
        """Process incoming messages."""
        while True:
            envelope = await self._in_queue.get()
            if envelope is None:
                self.logger.debug("Receiving loop terminated.")
                return
            self.logger.debug("Handling envelope: {}".format(envelope))
            await self._handle_envelope(envelope)

    async def _handle_envelope(self, envelope: Envelope) -> None:
        """Handle an envelope.

        :param envelope: the envelope
        :return: None
        """
        if envelope.protocol_id == ProtocolId.from_str("fetchai/oef_search:0.4.0"):
            await self._handle_oef_message(envelope)
        else:
            await self._handle_agent_message(envelope)

    async def _handle_oef_message(self, envelope: Envelope) -> None:
        """Handle oef messages.

        :param envelope: the envelope
        :return: None
        """
        assert isinstance(
            envelope.message, OefSearchMessage
        ), "Message not of type OefSearchMessage"
        oef_message, dialogue = self._get_message_and_dialogue(envelope)

        if dialogue is None:
            self.logger.warning(
                "Could not create dialogue for message={}".format(oef_message)
            )
            return

        if oef_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE:
            await self._register_service(
                envelope.sender, oef_message.service_description
            )
        elif (
            oef_message.performative == OefSearchMessage.Performative.UNREGISTER_SERVICE
        ):
            await self._unregister_service(oef_message, dialogue)
        elif oef_message.performative == OefSearchMessage.Performative.SEARCH_SERVICES:
            await self._search_services(oef_message, dialogue)
        else:
            # request not recognized
            pass

    async def _handle_agent_message(self, envelope: Envelope) -> None:
        """
        Forward an envelope to the right agent.

        :param envelope: the envelope
        :return: None
        """
        destination = envelope.to

        if destination not in self._out_queues.keys():
            msg = DefaultMessage(
                performative=DefaultMessage.Performative.ERROR,
                dialogue_reference=("", ""),
                target=TARGET,
                message_id=MESSAGE_ID,
                error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE,
                error_msg="Destination not available",
                error_data={},  # TODO: reference incoming message.
            )
            error_envelope = Envelope(
                to=envelope.sender,
                sender=str(OEFLocalConnection.connection_id),
                protocol_id=DefaultMessage.protocol_id,
                message=msg,
            )
            await self._send(error_envelope)
            return
        else:
            await self._send(envelope)

    async def _register_service(
        self, address: Address, service_description: Description
    ):
        """
        Register a service agent in the service directory of the node.

        :param address: the address of the service agent to be registered.
        :param service_description: the description of the service agent to be registered.
        :return: None
        """
        async with self._lock:
            self.services[address].append(service_description)

    async def _unregister_service(
        self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue,
    ) -> None:
        """
        Unregister a service agent.

        :param oef_search_msg: the incoming message.
        :param dialogue: the dialogue.
        :return: None
        """
        service_description = oef_search_msg.service_description
        address = oef_search_msg.sender
        async with self._lock:
            if address not in self.services:
                msg = OefSearchMessage(
                    performative=OefSearchMessage.Performative.OEF_ERROR,
                    target=oef_search_msg.message_id,
                    message_id=oef_search_msg.message_id + 1,
                    oef_error_operation=OefSearchMessage.OefErrorOperation.UNREGISTER_SERVICE,
                    dialogue_reference=dialogue.dialogue_label.dialogue_reference,
                )
                msg.counterparty = oef_search_msg.sender
                assert dialogue.update(msg)
                envelope = Envelope(
                    to=msg.counterparty,
                    sender=msg.sender,
                    protocol_id=msg.protocol_id,
                    message=msg,
                )
                await self._send(envelope)
            else:
                self.services[address].remove(service_description)
                if len(self.services[address]) == 0:
                    self.services.pop(address)

    async def _search_services(
        self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue,
    ) -> None:
        """
        Search the agents in the local Service Directory, and send back the result.

        This is actually a dummy search, it will return all the registered agents with the specified data model.
        If the data model is not specified, it will return all the agents.

        :param oef_search_msg: the message.
        :param dialogue: the dialogue.
        :return: None
        """
        async with self._lock:
            query = oef_search_msg.query
            result = []  # type: List[str]
            if query.model is None:
                result = list(set(self.services.keys()))
            else:
                for agent_address, descriptions in self.services.items():
                    for description in descriptions:
                        if description.data_model == query.model:
                            result.append(agent_address)

            msg = OefSearchMessage(
                performative=OefSearchMessage.Performative.SEARCH_RESULT,
                target=oef_search_msg.message_id,
                dialogue_reference=dialogue.dialogue_label.dialogue_reference,
                message_id=oef_search_msg.message_id + 1,
                agents=tuple(sorted(set(result))),
            )
            msg.counterparty = oef_search_msg.sender
            assert dialogue.update(msg)

            envelope = Envelope(
                to=msg.counterparty,
                sender=msg.sender,
                protocol_id=msg.protocol_id,
                message=msg,
            )
            await self._send(envelope)

    def _get_message_and_dialogue(
        self, envelope: Envelope
    ) -> Tuple[OefSearchMessage, Optional[OefSearchDialogue]]:
        """
        Get a message copy and dialogue related to this message.

        :param envelope: incoming envelope

        :return: Tuple[MEssage, Optional[Dialogue]]
        """
        assert self._dialogues is not None, "Call connect before!"
        message_orig = cast(OefSearchMessage, envelope.message)
        message = copy.copy(
            message_orig
        )  # TODO: fix; need to copy atm to avoid overwriting "is_incoming"
        message.is_incoming = True  # TODO: fix; should be done by framework
        message.counterparty = (
            message_orig.sender
        )  # TODO: fix; should be done by framework
        dialogue = cast(OefSearchDialogue, self._dialogues.update(message))
        return message, dialogue

    async def _send(self, envelope: Envelope):
        """Send a message."""
        destination = envelope.to
        destination_queue = self._out_queues[destination]
        destination_queue._loop.call_soon_threadsafe(destination_queue.put_nowait, envelope)  # type: ignore  # pylint: disable=protected-access
        self.logger.debug("Send envelope {}".format(envelope))

    async def disconnect(self, address: Address) -> None:
        """
        Disconnect.

        :param address: the address of the agent
        :return: None
        """
        async with self._lock:
            self._out_queues.pop(address, None)
            self.services.pop(address, None)