def __init__(self, **kwargs) -> None: """ Initialize dialogues. :return: None """ BaseOefSearchDialogues.__init__(self, str(OEFConnection.connection_id))
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, )
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, )
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)
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)
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)
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 __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, )
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)
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)
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)