def run_assertions(reactor: ReactorInterface): cb1 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env1) cb2 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env2) reactor._run_callback.assert_has_calls( [call(cb1), call(cb2)], any_order=False)
def test_subpub_1_n_1(self): """ Tests sub/pub with 1 publisher and 3 subs, with one message """ def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): callback = 'route' data = env.serialize() reactor._run_callback.assert_called_once_with(callback, data) env = random_envelope() sub1 = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB1') sub2 = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB2') sub3 = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB3') pub = MPReactorInterface(name='++ PUB') add_sub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URL, filter=FILTER) add_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env, filter=FILTER) sub1.send_cmd(add_sub_cmd) sub2.send_cmd(add_sub_cmd) sub3.send_cmd(add_sub_cmd) pub.send_cmd(add_pub_cmd) time.sleep(0.2) pub.send_cmd(send_pub_cmd) self.start()
def test_1_1_1(self): """ Tests pub/sub 1-1 (one sub one pub) with one message """ def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): callback = 'route' data = env.serialize() reactor._run_callback.assert_called_once_with(callback, data) env = random_envelope() sub = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB') pub = MPReactorInterface(name='++ PUB') # test self.execute_python('node_1', something_silly, async=True) # end tests add_sub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URL, filter=FILTER) add_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env, filter=FILTER) sub.send_cmd(add_sub_cmd) pub.send_cmd(add_pub_cmd) time.sleep(0.2) # To allow time for subs to connect to pub before pub sends data pub.send_cmd(send_pub_cmd) self.start()
async def _lookup_ip(self, cmd, url, vk, *args, **kwargs): ip, node = None, None try: node = await self.dht.network.lookup_ip(vk) except Exception as e: delim_line = '!' * 64 err_msg = '\n\n' + delim_line + '\n' + delim_line err_msg += '\n ERROR CAUGHT IN LOOKUP FUNCTION {}\ncalled \w args={}\nand kwargs={}\n'\ .format(args, kwargs) err_msg += '\nError Message: ' err_msg += '\n\n{}'.format(traceback.format_exc()) err_msg += '\n' + delim_line + '\n' + delim_line self.log.error(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)
def test_pubsub_1_1_n_filters(self): """ Test pub/sub 1-1 with multiple filters, only some of which should be received """ def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): callback = 'route' data = env.serialize() reactor._run_callback.assert_called_once_with(callback, data) env = random_envelope() env2 = random_envelope() sub = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB') pub = MPReactorInterface(name='++ PUB 1') pub = MPReactorInterface(name='++ PUB 2') add_sub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URL, filter=FILTER) add_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env, filter=FILTER) sub.send_cmd(add_sub_cmd) pub.send_cmd(add_pub_cmd) time.sleep(0.2) # To allow time for subs to connect to pub before pub sends data pub.send_cmd(send_pub_cmd) time.sleep(0.2) # Allow pubs to go through self.start()
def test_1_1_n_delay(self): """ Tests pub/sub 1-1 with 3 messages, and a 0.2 second delay between messages. The messages should be received in order they are sent """ def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): cb1 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env1) cb2 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env2) reactor._run_callback.assert_has_calls( [call(cb1), call(cb2)], any_order=False) env1 = random_envelope() env2 = random_envelope() sub = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB') pub = MPReactorInterface(name='++ PUB') add_sub_cmd = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URL, filter=FILTER) add_pub_cmd = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd1 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env1, filter=FILTER) send_pub_cmd2 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env2, filter=FILTER) sub.send_cmd(add_sub_cmd) pub.send_cmd(add_pub_cmd) time.sleep(0.2) pub.send_cmd(send_pub_cmd1) time.sleep(0.1) # Give time for first message to go through first pub.send_cmd(send_pub_cmd2) time.sleep(0.2) # To allow both pubs to go through self.start()
def test_1_1_n(self): """ Tests pub/sub 1-1 with 2 messages (any order with no delay in sends) """ def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): cb1 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env1) cb2 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env2) reactor._run_callback.assert_has_calls( [call(cb1), call(cb2)], any_order=True) env1 = random_envelope() env2 = random_envelope() sub = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB') pub = MPReactorInterface(name='++ PUB') add_sub_cmd = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URL, filter=FILTER) add_pub_cmd = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd1 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env1, filter=FILTER) send_pub_cmd2 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env2, filter=FILTER) sub.send_cmd(add_sub_cmd) pub.send_cmd(add_pub_cmd) time.sleep(0.2) pub.send_cmd(send_pub_cmd1) pub.send_cmd(send_pub_cmd2) time.sleep(0.2) # Give time for both pubs to go through self.start()
def send_request_env(self, envelope: Envelope, timeout=0, ip: str='', vk: str=''): url = "tcp://{}:{}".format(ip or vk, Constants.Ports.RouterDealer) reply_uuid = EnvelopeAuth.reply_uuid(envelope.meta.uuid) cmd = ReactorCommand.create_cmd(DealerRouterExecutor.__name__, DealerRouterExecutor.request.__name__, url=url, envelope=envelope, timeout=timeout, reply_uuid=reply_uuid) self.interface.send_cmd(cmd)
async def _recv_messages(self): try: # Notify parent proc that this proc is ready self.log.debug("reactorcore 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.debug( "ReactorDaemon awaiting for command from main thread...") cmd_bin = await self.socket.recv() self.log.debug("Got cmd from queue: {}".format(cmd_bin)) if cmd_bin == KILL_SIG: self.log.debug( "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("some ish got cacnelerd")
def remove_sub_filter(self, filter: str): """ Removes a filters from the sub socket. Unlike the remove_sub API, this does not disconnect a URL. It only unsubscribes to 'filter :param filter: A string to use as the filter frame. This filter will be unsubscribed. """ cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.remove_sub_filter.__name__, filter=filter) self.interface.send_cmd(cmd)
def add_router(self, ip: str='', vk: str=''): """ Add a router socket at url. Routers are like 'async repliers', and can connect to many Dealer sockets (N-1) :param url: The URL the router socket should BIND to :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.RouterDealer) cmd = ReactorCommand.create_cmd(DealerRouterExecutor.__name__, DealerRouterExecutor.add_router.__name__, url=url) self.interface.send_cmd(cmd)
def remove_pub(self, ip: str='', vk: str=''): """ Removes a publisher (duh) :param url: The URL of the router that the created dealer socket should CONNECT to. :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.remove_pub.__name__, url=url) self.interface.send_cmd(cmd)
def send_pub_env(self, filter: str, envelope: Envelope): """ Publish envelope with filter frame 'filter'. :param filter: A string to use as the filter frame :param envelope: An instance of Envelope """ cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, filter=filter, envelope=envelope) self.interface.send_cmd(cmd)
def call_on_mp(self, callback: str, header: str=None, envelope_binary: bytes=None, **kwargs): if header: kwargs['header'] = header cmd = ReactorCommand.create_callback(callback=callback, envelope_binary=envelope_binary, **kwargs) # self.log.critical("\ncalling callback cmd to reactor interface: {}".format(cmd)) # DEBUG line remove this self.inproc_socket.send(cmd.serialize())
def add_pub(self, ip: str=''): """ Create a publisher socket that BINDS to 'url' :param url: The URL to publish under. :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=url) self.interface.send_cmd(cmd)
def _lookup_failed(self, cmd: ReactorCommand): kwargs = cmd.kwargs del (kwargs['callback']) new_cmd = ReactorCommand.create_cmd(envelope=cmd.envelope, **kwargs) import time time.sleep(0.5) self.sm.composer.interface.send_cmd(new_cmd)
def test_req_1_1_1(self): """ Tests dealer router 1/1 (1 dealer, 1 router) with 1 message request (dealer sends router 1 message) """ def run_assertions(reactor: ReactorInterface): cb = ReactorCommand.create_callback(callback=ROUTE_REQ_CALLBACK, envelope=env, header=DEALER_ID) reactor._run_callback.assert_called_once_with(cb) DEALER_URL = URLS[0] DEALER_ID = "id-" + DEALER_URL # irl this would a node's vk ROUTER_URL = URLS[1] env = random_envelope() dealer = MPReactorInterface(name='DEALER') router = MPReactorInterface(config_fn=config_reactor, assert_fn=run_assertions, name='ROUTER') add_dealer = ReactorCommand.create_cmd( class_name=DealerRouterExecutor.__name__, func_name=DealerRouterExecutor.add_dealer.__name__, url=ROUTER_URL, id=DEALER_ID) add_router = ReactorCommand.create_cmd( class_name=DealerRouterExecutor.__name__, func_name=DealerRouterExecutor.add_router.__name__, url=ROUTER_URL) request = ReactorCommand.create_cmd( class_name=DealerRouterExecutor.__name__, func_name=DealerRouterExecutor.request.__name__, url=ROUTER_URL, envelope=env) dealer.send_cmd(add_dealer) router.send_cmd(add_router) time.sleep(0.2) dealer.send_cmd(request) self.start()
def remove_sub_url(self, ip: str='', vk: str=''): """ Disconnects the sub URL from url. Unlike the remove_sub API, this does not remove a filter from the socket. It only disconnects from the url. received :param url: The URL of the router that the created dealer socket should CONNECT to. :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.remove_sub_url.__name__, url=url) self.interface.send_cmd(cmd)
def add_sub(self, filter: str, ip: str='', vk: str=''): """ Connects the subscriber socket to listen to 'URL' with filter 'filter'. :param url: The URL to CONNECT the sub socket to (ex 'tcp://17.1.3.4:4200') :param filter: The filter to subscribe to. Only data published with this filter will be received. Currently, only one filter per CONNECT is supported. """ # url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, filter=filter, url=url, vk=vk) self.interface.send_cmd(cmd)
def test_create_callback(self): """ Tests create_callback """ callback = 'route' kwargs = {'cats': '18', 'dogs': 'over 9000'} cmd = ReactorCommand.create_callback(callback, **kwargs) self.assertEqual(cmd.callback, callback) for k in kwargs: self.assertEqual(cmd.kwargs[k], kwargs[k])
def test_create_with_envelope(self): """ Tests creating a message with an envelope produces an object with the expected properties """ sk, vk = ED25519Wallet.new() tx = StandardTransactionBuilder.random_tx() sender = 'me' env = Envelope.create_from_message(message=tx, signing_key=sk) cmd = ReactorCommand.create_cmd('some_cls', 'some_func', envelope=env) self.assertTrue(ReactorCommand.envelope, env)
def add_dealer(self, ip: str='', vk: str=''): """ Add a dealer socket at url. Dealers are like 'async requesters', and can connect to a single Router socket (1-1) (side note: A router socket, however, can connect to N dealers) 'id' socketopt for the dealer socket will be this node's verifying key :param url: The URL of the router that the created dealer socket should CONNECT to. :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.RouterDealer) cmd = ReactorCommand.create_cmd(DealerRouterExecutor.__name__, DealerRouterExecutor.add_dealer.__name__, id=self.verifying_key, url=url, vk=vk) self.interface.send_cmd(cmd)
def test_create_with_kwargs(self): """ Tests creating a message without an envelope produces an objects with the expected kwargs """ kwargs = { 'callback': 'do_something', 'some_num': '10', 'some_id': '0x5544ddeeff' } cmd = ReactorCommand.create(**kwargs) self.assertEqual(cmd.kwargs, kwargs)
def send_reply(self, message: MessageBase, request_envelope: Envelope): """ Send a reply message (via a Router socket) for the original reqeust in request_envelope (which came from a Dealer socket). Replies envelope are created as a deterministic function of their original request envelope, so that both parties (the sender and receiver) are in agreement on what the reply envelope should look like :param message: A MessageBase instance that denotes the reply data :param request_envelope: An Envelope instance that denotes the envelope of the original request that we are replying to """ requester_id = request_envelope.seal.verifying_key reply_env = self._package_reply(reply=message, req_env=request_envelope) cmd = ReactorCommand.create_cmd(DealerRouterExecutor.__name__, DealerRouterExecutor.reply.__name__, id=requester_id, envelope=reply_env) self.interface.send_cmd(cmd)
def test_create_cmd(self): """ Tests create_cmd """ class_name = 'TestClass' func_name = 'do_something_lit' kwargs = {'cats': '18', 'dogs': 'over 9000'} cmd = ReactorCommand.create_cmd(class_name, func_name, **kwargs) self.assertEquals(cmd.func_name, func_name) self.assertEqual(cmd.class_name, class_name) for k in kwargs: self.assertEqual(cmd.kwargs[k], kwargs[k]) self.assertEqual(cmd.envelope, None)
async def _recv_messages(self): """ Should be for internal use only. Starts listening to messages from the ReactorDaemon. This method gets run_until_complete by invoking .start_reactor on the ReactorInterface object. """ try: self.log.debug( "~~ Reactor listening to messages from ReactorDaemon ~~") while True: self.log.debug("Waiting for callback...") msg = await self.socket.recv() callback = ReactorCommand.from_bytes(msg) self.log.debug("Got callback cmd <{}>".format(callback)) self.router.route_callback(callback) except asyncio.CancelledError: self.log.debug("_recv_messages future canceled!")
def remove_sub(self, filter: str, ip: str='', vk: str=''): """ Stop subscribing to a URL and filter. Note that all other subscriber connections will drop this filter as well (so if there is another URL you are subscribing to with the same filter, that sub will no longer work). The pattern at this point is to have a single filter for each 'node type', ie witness/delegate/masternode. If you wish to stop subscribing to a URL, but not necessarily a filter, then call this method and pass in an empty string to FILTER. For example, a delegate might want to stop subscribing to a particular witness, but not all witnesses. :param filter: The filter to subscribe to. Only multipart messages with this filter as the first frame will be received :param url: The URL of the router that the created dealer socket should CONNECT to. :param vk: The Node's VK to connect to. This will be looked up in the overlay network """ url = "tcp://{}:{}".format(ip or vk, Constants.Ports.PubSub) cmd = ReactorCommand.create_cmd(SubPubExecutor.__name__, SubPubExecutor.remove_sub.__name__, filter=filter, url=url, vk=vk) self.interface.send_cmd(cmd)
async def _recv_messages(self): # Notify parent proc that this proc is ready self.log.debug("reactorcore 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.debug( "ReactorDaemon awaiting for command from main thread...") cmd_bin = await self.socket.recv() self.log.debug("Got cmd from queue: {}".format(cmd_bin)) if cmd_bin == KILL_SIG: 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) await self._execute_cmd(cmd)
def run_assertions(reactor: ReactorInterface): callback = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env) reactor._run_callback.assert_called_once_with(callback)
def test_pubsub_1_n_n_filters(self): """ Test pub/sub 1-1 with multiple filters, only some of which should be received """ # TODO -- implement self.assertTrue(27**2 + 36**2 == 45**2) return def configure_interface(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def run_assertions(reactor: ReactorInterface): cb = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env1) reactor._run_callback.assert_called_once_with(cb) env1, env2, env3, env4, env5 = (random_envelope() for _ in range(5)) sub = MPReactorInterface(config_fn=configure_interface, assert_fn=run_assertions, name='** SUB') pub1 = MPReactorInterface(name='++ PUB 1') pub2 = MPReactorInterface(name='++ PUB 2') pub3 = MPReactorInterface(name='++ PUB 3') add_sub_cmd1 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URLS[1], filter=FILTERS[1]) add_sub_cmd2 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URLS[1], filter=FILTERS[2]) add_sub_cmd3 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_sub.__name__, url=URLS[1], filter=FILTERS[3]) add_pub_cmd1 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) add_pub_cmd2 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.add_pub.__name__, url=URL) send_pub_cmd1 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env1, filter=FILTERS[1]) send_pub_cmd2 = ReactorCommand.create_cmd( SubPubExecutor.__name__, SubPubExecutor.send_pub.__name__, envelope=env2, filter=FILTERS[1]) sub.send_cmd(add_sub_cmd1) sub.send_cmd(add_sub_cmd1) pub1.send_cmd(add_pub_cmd1) pub2.send_cmd(add_pub_cmd1) time.sleep( 0.2 ) # To allow time for subs to connect to pub before pub sends data pub1.send_cmd(send_pub_cmd1) time.sleep(0.2) # Allow pubs to go through self.start()