class RoutingManager(object): def __init__(self, my_node, querier, bootstrap_nodes): self.my_node = my_node self.querier = querier #Copy the bootstrap list self.bootstrap_nodes = [n for n in bootstrap_nodes] self.main = RoutingTable(my_node, NODES_PER_BUCKET) self.replacement = RoutingTable(my_node, NODES_PER_BUCKET) self.ping_msg = message.OutgoingPingQuery(my_node.id) self.find_node_msg = message.OutgoingFindNodeQuery( my_node.id, my_node.id) self.mode = BOOTSTRAP_MODE self.num_concurrent_refresh_msgs = 0 #This must be called by an external party: self.do_bootstrap() #After initializing callbacks # Add myself to the routing table rnode = self.main.add(my_node) self._reset_refresh_task(rnode) def do_bootstrap(self): if self.main.num_rnodes > MIN_RNODES_BOOTSTRAP: # Enough nodes. Stop bootstrap. return for _ in xrange(NUM_NODES_PER_BOOTSTRAP_STEP): if not self.bootstrap_nodes: self.mode = NORMAL_MODE return index = random.randint(0, len(self.bootstrap_nodes) - 1) self.querier.send_query(self.find_node_msg, self.bootstrap_nodes[index], self._do_nothing, self._do_nothing, self._do_nothing) del self.bootstrap_nodes[index] #TODO2: Don't use querier's rpc_m self.querier.rpc_m.call_later(BOOTSTRAP_DELAY, self.do_bootstrap) def on_query_received(self, node_): try: rnode = self.main.get_rnode(node_) except RnodeNotFound: pass # node is not in the main table else: # node in routing table: inform rnode rnode.on_query_received() self._reset_refresh_task(rnode) return # Node is not in routing table # Check reachability (if the bucket is not full) if self.main.there_is_room(node_): # there is room in the bucket: ping node to check reachability self._refresh_now(node_) return # No room in the main routing table # Add to replacement table (if the bucket is not full) bucket = self.replacement.get_bucket(node_) worst_rnode = self._worst_rnode(bucket.rnodes) if worst_rnode \ and worst_rnode.timeouts_in_a_row() > MAX_NUM_TIMEOUTS: self.replacement.remove(worst_rnode) self.replacement.add(node_) def on_response_received(self, node_): #TODO2:, rtt=0): try: rnode = self.main.get_rnode(node_) except (RnodeNotFound): pass else: # node in routing table: refresh it rnode.on_response_received() self._reset_refresh_task(rnode) return # The node is not in main try: rnode = self.replacement.get_rnode(node_) except (RnodeNotFound): pass else: # node in replacement table # let's see whether there is room in the main rnode.on_response_received() if self.main.there_is_room(node_): rnode = self.main.add(rnode) self._reset_refresh_task(rnode) self.replacement.remove(rnode) return # The node is nowhere # Add to replacement table (if the bucket is not full) bucket = self.replacement.get_bucket(node_) if self.main.there_is_room(node_): if not bucket.rnodes: # Replacement is empty rnode = self.main.add(node_) self._reset_refresh_task(rnode) return # The main bucket is full or the repl bucket is not empty worst_rnode = self._worst_rnode(bucket.rnodes) # Get the worst node in replacement bucket and see whether # it's bad enough to be replaced by node_ if worst_rnode \ and worst_rnode.timeouts_in_a_row() > MAX_NUM_TIMEOUTS: # This node is better candidate than worst_rnode self.replacement.remove(worst_rnode) try: self.replacement.add(node_) except (BucketFullError): pass def on_error_received(self, node_): pass def on_timeout(self, node_): if node_ is self.my_node: raise Exception, 'I got a timeout from myself!!!' if not node_.id: return # This is a bootstrap node (just addr, no id) try: rnode = self.main.get_rnode(node_) except RnodeNotFound: pass else: # node in routing table: check whether it should be removed rnode.on_timeout() replacement_bucket = self.replacement.get_bucket(node_) self._refresh_replacement_bucket(replacement_bucket) self.main.remove(rnode) try: self.replacement.add(rnode) except (BucketFullError): worst_rnode = self._worst_rnode(replacement_bucket.rnodes) if worst_rnode: # Replace worst node in replacement table self.replacement.remove(worst_rnode) self._refresh_replacement_bucket(replacement_bucket) # We don't want to ping the node which just did timeout self.replacement.add(rnode) # Node is not in main table try: rnode = self.replacement.get_rnode(node_) except RnodeNotFound: pass # the node is not in any table. Nothing to do here. else: # Node in replacement table: just update rnode rnode.on_timeout() def on_nodes_found(self, nodes): #FIXME: this will send ping at exponential rate #not good!!!! logging.debug('nodes found: %r', nodes) for node_ in nodes: try: rnode = self.main.get_rnode(node_) except RnodeNotFound: # Not in the main: ping it if there is room in main if self.main.there_is_room(node_): logging.debug('pinging node found: %r', node_) self._refresh_now(node_, NO_PRIORITY) #TODO2: prefer NS def get_closest_rnodes(self, target_id, num_nodes=DEFAULT_NUM_NODES): return self.main.get_closest_rnodes(target_id, num_nodes) def get_all_rnodes(self): return (self.main.get_all_rnodes(), self.replacement.get_all_rnodes()) def _refresh_now(self, node_, priority=PRIORITY): if priority == NO_PRIORITY and \ self.num_concurrent_refresh_msgs > MAX_CONCURRENT_REFRESH_MSGS: return self.num_concurrent_refresh_msgs += 1 return self.querier.send_query(self.find_node_msg, node_, self._refresh_now_callback, self._refresh_now_callback, self._refresh_now_callback) def _reset_refresh_task(self, rnode): if rnode.refresh_task: # Cancel the current refresh task rnode.refresh_task.cancel() if rnode.in_quarantine: rnode.refresh_task = self._refresh_later(rnode, QUARANTINE_PERIOD) else: rnode.refresh_task = self._refresh_later(rnode) def _refresh_later(self, rnode, delay=REFRESH_PERIOD): return self.querier.send_query_later(delay, self.find_node_msg, rnode, self._do_nothing, self._do_nothing, self._do_nothing) def _do_nothing(self, *args, **kwargs): pass def _refresh_now_callback(self, *args, **kwargs): self.num_concurrent_refresh_msgs -= 1 def _refresh_replacement_bucket(self, bucket): for rnode in bucket.rnodes: if rnode.is_ns: # We give advantage to NS nodes self._refresh_now(rnode) else: self._refresh_later(rnode, REFRESH_DELAY_FOR_NON_NS) def _worst_rnode(self, rnodes): max_num_timeouts = -1 worst_rnode_so_far = None for rnode in rnodes: num_timeouots = rnode.timeouts_in_a_row() if num_timeouots >= max_num_timeouts: max_num_timeouts = num_timeouots worst_rnode_so_far = rnode return worst_rnode_so_far
class RoutingManager(object): def __init__(self, my_node, querier, bootstrap_nodes): self.my_node = my_node self.querier = querier #Copy the bootstrap list self.bootstrap_nodes = [n for n in bootstrap_nodes] self.main = RoutingTable(my_node, NODES_PER_BUCKET) self.replacement = RoutingTable(my_node, NODES_PER_BUCKET) self.ping_msg = message.OutgoingPingQuery(my_node.id) self.find_node_msg = message.OutgoingFindNodeQuery( my_node.id, my_node.id) self.mode = BOOTSTRAP_MODE self.num_concurrent_refresh_msgs = 0 #This must be called by an external party: self.do_bootstrap() #After initializing callbacks # Add myself to the routing table rnode = self.main.add(my_node) self._reset_refresh_task(rnode) def do_bootstrap(self): if self.main.num_rnodes > MIN_RNODES_BOOTSTRAP: # Enough nodes. Stop bootstrap. return for _ in xrange(NUM_NODES_PER_BOOTSTRAP_STEP): if not self.bootstrap_nodes: self.mode = NORMAL_MODE return index = random.randint(0, len(self.bootstrap_nodes) - 1) self.querier.send_query(self.find_node_msg, self.bootstrap_nodes[index], self._do_nothing, self._do_nothing, self._do_nothing) del self.bootstrap_nodes[index] #TODO2: Don't use querier's rpc_m self.querier.rpc_m.call_later(BOOTSTRAP_DELAY, self.do_bootstrap) def on_query_received(self, node_): try: rnode = self.main.get_rnode(node_) except RnodeNotFound: pass # node is not in the main table else: # node in routing table: inform rnode rnode.on_query_received() self._reset_refresh_task(rnode) return # Node is not in routing table # Check reachability (if the bucket is not full) if self.main.there_is_room(node_): # there is room in the bucket: ping node to check reachability self._refresh_now(node_) return # No room in the main routing table # Add to replacement table (if the bucket is not full) bucket = self.replacement.get_bucket(node_) worst_rnode = self._worst_rnode(bucket.rnodes) if worst_rnode \ and worst_rnode.timeouts_in_a_row() > MAX_NUM_TIMEOUTS: self.replacement.remove(worst_rnode) self.replacement.add(node_) def on_response_received(self, node_): #TODO2:, rtt=0): try: rnode = self.main.get_rnode(node_) except (RnodeNotFound): pass else: # node in routing table: refresh it rnode.on_response_received() self._reset_refresh_task(rnode) return # The node is not in main try: rnode = self.replacement.get_rnode(node_) except (RnodeNotFound): pass else: # node in replacement table # let's see whether there is room in the main rnode.on_response_received() if self.main.there_is_room(node_): rnode = self.main.add(rnode) self._reset_refresh_task(rnode) self.replacement.remove(rnode) return # The node is nowhere # Add to replacement table (if the bucket is not full) bucket = self.replacement.get_bucket(node_) if self.main.there_is_room(node_): if not bucket.rnodes: # Replacement is empty rnode = self.main.add(node_) self._reset_refresh_task(rnode) return # The main bucket is full or the repl bucket is not empty worst_rnode = self._worst_rnode(bucket.rnodes) # Get the worst node in replacement bucket and see whether # it's bad enough to be replaced by node_ if worst_rnode \ and worst_rnode.timeouts_in_a_row() > MAX_NUM_TIMEOUTS: # This node is better candidate than worst_rnode self.replacement.remove(worst_rnode) try: self.replacement.add(node_) except (BucketFullError): pass def on_error_received(self, node_): pass def on_timeout(self, node_): if node_ is self.my_node: raise Exception, 'I got a timeout from myself!!!' if not node_.id: return # This is a bootstrap node (just addr, no id) try: rnode = self.main.get_rnode(node_) except RnodeNotFound: pass else: # node in routing table: check whether it should be removed rnode.on_timeout() replacement_bucket = self.replacement.get_bucket(node_) self._refresh_replacement_bucket(replacement_bucket) self.main.remove(rnode) try: self.replacement.add(rnode) except (BucketFullError): worst_rnode = self._worst_rnode(replacement_bucket.rnodes) if worst_rnode: # Replace worst node in replacement table self.replacement.remove(worst_rnode) self._refresh_replacement_bucket(replacement_bucket) # We don't want to ping the node which just did timeout self.replacement.add(rnode) # Node is not in main table try: rnode = self.replacement.get_rnode(node_) except RnodeNotFound: pass # the node is not in any table. Nothing to do here. else: # Node in replacement table: just update rnode rnode.on_timeout() def on_nodes_found(self, nodes): #FIXME: this will send ping at exponential rate #not good!!!! log.debug('nodes found: %r', nodes) for node_ in nodes: try: rnode = self.main.get_rnode(node_) except RnodeNotFound: # Not in the main: ping it if there is room in main if self.main.there_is_room(node_): log.debug('pinging node found: %r', node_) self._refresh_now(node_, NO_PRIORITY) #TODO2: prefer NS def get_closest_rnodes(self, target_id, num_nodes=DEFAULT_NUM_NODES): return self.main.get_closest_rnodes(target_id, num_nodes) def get_all_rnodes(self): return (self.main.get_all_rnodes(), self.replacement.get_all_rnodes()) def _refresh_now(self, node_, priority=PRIORITY): if priority == NO_PRIORITY and \ self.num_concurrent_refresh_msgs > MAX_CONCURRENT_REFRESH_MSGS: return self.num_concurrent_refresh_msgs += 1 return self.querier.send_query(self.find_node_msg, node_, self._refresh_now_callback, self._refresh_now_callback, self._refresh_now_callback) def _reset_refresh_task(self, rnode): if rnode.refresh_task: # Cancel the current refresh task rnode.refresh_task.cancel() if rnode.in_quarantine: rnode.refresh_task = self._refresh_later(rnode, QUARANTINE_PERIOD) else: rnode.refresh_task = self._refresh_later(rnode) def _refresh_later(self, rnode, delay=REFRESH_PERIOD): return self.querier.send_query_later(delay, self.find_node_msg, rnode, self._do_nothing, self._do_nothing, self._do_nothing) def _do_nothing(self, *args, **kwargs): pass def _refresh_now_callback(self, *args, **kwargs): self.num_concurrent_refresh_msgs -= 1 def _refresh_replacement_bucket(self, bucket): for rnode in bucket.rnodes: if rnode.is_ns: # We give advantage to NS nodes self._refresh_now(rnode) else: self._refresh_later(rnode, REFRESH_DELAY_FOR_NON_NS) def _worst_rnode(self, rnodes): max_num_timeouts = -1 worst_rnode_so_far = None for rnode in rnodes: num_timeouots = rnode.timeouts_in_a_row() if num_timeouots >= max_num_timeouts: max_num_timeouts = num_timeouots worst_rnode_so_far = rnode return worst_rnode_so_far