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 assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call # cb = ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env) # composer.interface.router.route_callback.assert_called_once_with(cb) expected_cb = call( ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env)) unexpected_cb = call( ReactorCommand.create_callback( callback=StateInput.LOOKUP_FAILED, envelope=evil_env)) call_args = composer.manager.router.route_callback.call_args_list # # DEBUG STUFF # from cilantro.logger.base import get_logger # l = get_logger("assert sub") # l.debug('EXPECTED') # l.critical(expected_cb) # l.debug('UNEXPECTED') # l.critical(unexpected_cb) # l.important2("got dat call args: {}".format(call_args)) # # END DEBUG STUFF # composer.interface.router.route_callback.assert_has_calls([expected_cb], any_order=True) assert expected_cb in call_args, "Expected callback {} to be in call_args {}".format( expected_cb, call_args) assert unexpected_cb not in call_args, "Did not expect callback {} to be in call_args {}".format( unexpected_cb, call_args)
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.fatal('{} resolves for {}'.format(os.getenv('HOST_IP'), node)) if node and not cached: ip = node.ip if type(node) == Node else node.split(':')[0] public_key = self.dht.network.ironhouse.vk2pk(vk) authorization = await self.dht.network.ironhouse.authenticate( public_key, ip) self.log.fatal('{} -> {} is {}'.format(os.getenv('HOST_IP'), node, authorization)) if authorization != 'authorized': node = None else: n = Node(node_id=digest(vk), public_key=public_key, ip=ip, port=self.dht.network.network_port) self.dht.network.protocol.router.addContact(n) self.dht.network.connect_to_neighbor(n) self.log.fatal([ item[0] for item in self.dht.network.bootstrappableNeighbors() ]) 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 run_assertions(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call cb1 = ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env1) cb2 = ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env2) composer.manager.router.route_callback.assert_has_calls( [call(cb1), call(cb2)], any_order=True)
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 assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call callback1 = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env1) callback2 = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env2) calls = [call(callback1), call(callback2)] call_args = composer.manager.router.route_callback.call_args_list composer.manager.router.route_callback.assert_has_calls( calls, any_order=True)
def assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput cb = ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env) composer.interface.router.route_callback.assert_called_once_with( cb)
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("Daemon _recv_messages task canceled externally")
def connection_drop(self, node): if self.event_sock: self.event_sock.send_json({'event':'disconect', 'ip':node.ip, 'vk': self.ironhouse.pk2vk.get(node.public_key) }) callback = ReactorCommand.create_callback( callback=StateInput.CONN_DROPPED, ip=node.ip ) log.debug("Sending callback failure to mainthread {}".format(callback))
def assert_router(composer: Composer): from cilantro.protocol.states.decorators import StateInput from cilantro.messages.reactor.reactor_command import ReactorCommand cb = ReactorCommand.create_callback(callback=StateInput.REQUEST, envelope=request_env, header=dealer_id) composer.manager.router.route_callback.assert_called_with(cb)
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): cb = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env) reactor._run_callback.assert_called_once_with(cb) 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()
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)
def assert_bad_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call cb = call( ReactorCommand.create_callback(callback=StateInput.INPUT, envelope=env)) assert cb not in composer.manager.router.route_callback.call_args_list
def assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call callback1 = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env1) callback2 = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env2) calls = [call(callback1), call(callback2)] call_args = composer.interface.router.route_callback.call_args_list assert len(call_args) == 2, "route_callback should be called exactly twice, not {} times with {}"\ .format(len(call_args), call_args) composer.interface.router.route_callback.assert_has_calls( calls, any_order=True)
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 = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=env) reactor._run_callback.assert_called_once_with(callback) 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()
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 connection_drop(self): if self.daemon: callback = ReactorCommand.create_callback( callback=StateInput.CONN_DROPPED, vk=self.ironhouse.vk, ip=self.node.ip) log.debug( "Sending callback failure to mainthread {}".format(callback)) self.daemon.socket.send(callback.serialize())
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 _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_create_with_envelope(self): """ Tests creating a message with an envelope produces an object with the expected properties """ sk, vk = wallet.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 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_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 assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call expected_calls = [] for env in envs: callback = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env) expected_calls.append(call(callback)) call_args = composer.manager.router.route_callback.call_args_list composer.manager.router.route_callback.assert_has_calls( expected_calls, any_order=True)
def assert_sub(composer: Composer): from cilantro.messages.reactor.reactor_command import ReactorCommand from cilantro.protocol.states.decorators import StateInput from unittest.mock import call expected_calls = [] for env in envs: callback = ReactorCommand.create_callback( callback=StateInput.INPUT, envelope=env) expected_calls.append(call(callback)) call_args = composer.interface.router.route_callback.call_args_list assert len(call_args) == len(expected_calls), "route_callback should be called exactly {} times, not {} " \ "times with {}".format(len(expected_calls), len(call_args), call_args) composer.interface.router.route_callback.assert_has_calls( expected_calls, any_order=True)
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 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)
def test_req_reply_1_1_1(self): """ Tests a request/reply round trip between dealer and router. """ def config_dealer(reactor: ReactorInterface): reactor._run_callback = MagicMock() return reactor def config_router(reactor: ReactorInterface): def reply_effect(*args, **kwargs): # log = get_logger("ASSERT ROUTER SIDE EFFECT WTIH") # log.critical("\n\n sending reply command... \n\n") reactor.send_cmd(reply) dealer.send_cmd(reply) reactor._run_callback = MagicMock() reactor._run_callback.side_effect = reply_effect return reactor def assert_dealer(reactor: ReactorInterface): cb = ReactorCommand.create_callback(callback=ROUTE_CALLBACK, envelope=rep_env) reactor._run_callback.assert_called_once_with(cb) def assert_router(reactor: ReactorInterface): cb = ReactorCommand.create_callback(callback=ROUTE_REQ_CALLBACK, envelope=req_env, header=DEALER_ID) reactor._run_callback.assert_called_once_with(cb) # reactor._run_callback.side_effect = reply_effect DEALER_URL = URLS[0] DEALER_ID = "id-" + DEALER_URL # irl this would a node's vk ROUTER_URL = URLS[1] req_env = random_envelope() rep_env = random_envelope() 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=req_env) reply = ReactorCommand.create_cmd( class_name=DealerRouterExecutor.__name__, func_name=DealerRouterExecutor.reply.__name__, id=DEALER_ID, envelope=rep_env) dealer = MPReactorInterface(name='DEALER', config_fn=config_dealer, assert_fn=assert_dealer) router = MPReactorInterface(config_fn=config_router, assert_fn=assert_router, name='ROUTER') dealer.send_cmd(add_dealer) router.send_cmd(add_router) time.sleep(0.2) dealer.send_cmd(request) self.start()
def send_cmd(self, cmd: ReactorCommand): assert isinstance( cmd, ReactorCommand ), "Only ReactorCommand instances can sent through the reactor" self.socket.send(cmd.serialize())