def __init__(self, my_node, querier, bootstrap_lookup_f, bootstrap_nodes): self.my_node = my_node self.querier = querier self.bootstrap_lookup = bootstrap_lookup_f #Copy the bootstrap list self.bootstrap_nodes = [n for n in bootstrap_nodes] self.table = RoutingTable(my_node, NODES_PER_BUCKET) self.ping_msg = message.OutgoingPingQuery(my_node.id) self.find_closest_msg = message.OutgoingFindNodeQuery( my_node.id, my_node.id) #This must be called by an external party: self.do_bootstrap() #After initializing callbacks # maintenance variables self.last_maintenance_index = -1 self.maintenance_mode = BOOTSTRAP_MODE self.lookup_mode = False self._query_received_queue = QueryReceivedQueue(self.table) self._found_nodes_queue = FoundNodesQueue(self.table)
class RoutingManager(object): def __init__(self, my_node, querier, bootstrap_lookup_f, bootstrap_nodes): self.my_node = my_node self.querier = querier self.bootstrap_lookup = bootstrap_lookup_f #Copy the bootstrap list self.bootstrap_nodes = [n for n in bootstrap_nodes] self.table = RoutingTable(my_node, NODES_PER_BUCKET) self.ping_msg = message.OutgoingPingQuery(my_node.id) self.find_closest_msg = message.OutgoingFindNodeQuery( my_node.id, my_node.id) #This must be called by an external party: self.do_bootstrap() #After initializing callbacks # maintenance variables self.last_maintenance_index = -1 self.maintenance_mode = BOOTSTRAP_MODE self.lookup_mode = False self._query_received_queue = QueryReceivedQueue(self.table) self._found_nodes_queue = FoundNodesQueue(self.table) def notify_lookup_start(self): self.lookup_mode = True def notify_lookup_stop(self): self.lookup_mode = False def do_maintenance(self): if self.maintenance_mode == BOOTSTRAP_MODE: self._do_bootstrap() elif self.maintenance_mode == FIND_CLOSEST_MODE: self.bootstrap_lookup() self.maintenance_mode = NORMAL_MODE else: self._ping_staled_buckets() return MAINTENANCE_DELAY[self.maintenance_mode] def _do_bootstrap(self): try: node = self.bootstrap_nodes.pop(0) except(IndexError): # Stop bootstrap. Now find closest nodes self.maintenance_mode = FIND_CLOSEST_MODE else: self._send_maintenance_ping(node) def _ping_staled_buckets(self): current_time = time.time() for log_distance, buckets in enumerate(self.table.buckets[:-1]): # Exclude last bucket (myself) m_bucket = buckets[0] if current_time < m_bucket.last_maintenance_ts + REFRESH_PERIOD: continue rnode = m_bucket.get_freshest_rnode() if rnode and current_time > rnode.last_seen + REFRESH_PERIOD: m_bucket.last_maintenance_ts = current_time print '[%f] staled bucket %d' % (time.time(), log_distance) target = self.my_node.id.generate_close_id(log_distance) self.bootstrap_lookup(target) def _send_maintenance_ping(self, node_): if node_.id: log_distance = self.table.find_next_bucket_with_room_index(node_) else: # Bootstrap nodes don't have id log_distance = 0 if 0:#log_distance: target = self.my_node.id.generate_close_id(log_distance) msg = message.OutgoingFindNodeQuery(self.my_node.id, target) m_bucket, r_bucket = self.table.buckets[log_distance] print '[%f] FIND_NODE(%d:%d:%d)' % (time.time(), log_distance, len(m_bucket), len(r_bucket)), else: print '[%f] PING' % (time.time()), msg = self.ping_msg if node_.id: m_bucket, r_bucket = self.table.get_buckets(node_) print 'to (%r) %d:%d:%d --' % ( node_.addr, node_.log_distance(self.my_node), len(m_bucket), len(r_bucket)) else: print 'to UNKNOWN id' self.querier.send_query(msg, node_) def on_query_received(self, node_): rnode = self._on_msg_received(node_) if rnode: # node in routing table self._update_rnode_on_query_received(rnode) def on_response_received(self, node_, rtt=0): #TODO2:, rtt=0): logger.debug('response from %r' % (node_)) rnode = self._on_msg_received(node_) if rnode: # node in routing table self._update_rnode_on_response_received(rnode) def _on_msg_received(self, node_): m_bucket, r_bucket = self.table.get_buckets(node_) rnode = m_bucket.get_rnode(node_) if rnode: # The node is in the bucket (it needs to be updated) return rnode # The node is not in main if m_bucket.there_is_room(): # There is room rnode = node_.get_rnode() m_bucket.add(rnode) return rnode # The main bucket is full bad_rnode = self._pop_bad_rnode(m_bucket) if bad_rnode: print 'replacing bad rnode...' # There is a bad node, add the new node rnode = node_.get_rnode() m_bucket.add(rnode) return rnode q_rnodes = self._get_questionable_rnodes(m_bucket) if q_rnodes: print 'pinging questionable rnodes...' # There are questionable nodes, ping them rnode = node_.get_rnode() r_bucket.rnodes = [rnode] for rnode in q_rnodes: self._send_maintenance_ping(rnode) return rnode def on_error_received(self, node_): pass def on_timeout(self, node_): if not node_.id: return # This is a bootstrap node (just addr, no id) m_bucket, r_bucket = self.table.get_buckets(node_) rnode = m_bucket.get_rnode(node_) if rnode: # Node in routing table print 'timeout', rnode.addr, self.my_node.log_distance(rnode) self._update_rnode_on_timeout(rnode) if len(r_bucket): # There is a replacement node r_rnode = r_bucket.rnodes[0] if r_rnode.last_seen > time.time() - 60: # And it is fresh if rnode.timeouts_in_a_row() == 1: # Give it another chance, ping it self._send_maintenance_ping(rnode) else: # Several timeouts. Replace! m_bucket.remove(rnode) m_bucket.add(r_rnode) del r_bucket.rnodes[0] def on_nodes_found(self, nodes): return def get_closest_rnodes(self, target_id, num_nodes=DEFAULT_NUM_NODES): return self.table.get_closest_rnodes(target_id, num_nodes) def get_main_rnodes(self): return self.table.get_main_rnodes() def print_stats(self): self.table.print_stats() def _refresh_replacement_bucket(self, bucket): for rnode in bucket.rnodes: self._send_maintenance_ping(rnode) print '-----------' def _pop_bad_rnode(self, bucket): for rnode in bucket.rnodes: if rnode.timeouts_in_a_row() > 1: print 'bad rnode' bucket.remove(rnode) return rnode def _get_questionable_rnodes(self, bucket): q_nodes = [] for rnode in bucket.rnodes: if time.time() > rnode.last_seen + REFRESH_PERIOD: print 'old>', q_nodes.append(rnode) continue if rnode.num_responses == 0: print 'no_responses', q_nodes.append(rnode) if q_nodes: print '' return q_nodes 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 def _update_rnode_on_query_received(self, rnode): """Register a query from node. You should call this method when receiving a query from this node. """ current_time = time.time() rnode.last_action_ts = time.time() rnode.msgs_since_timeout += 1 rnode.num_queries += 1 rnode.last_events.append((current_time, node.QUERY)) rnode.last_events[:rnode.max_last_events] rnode.last_seen = current_time def _update_rnode_on_response_received(self, rnode, rtt=0): """Register a reply from rnode. You should call this method when receiving a response from this rnode. """ current_time = time.time() #rnode._reset_refresh_task() if rnode.in_quarantine: rnode.in_quarantine = \ rnode.last_action_ts < current_time - node.QUARANTINE_PERIOD rnode.last_action_ts = current_time rnode.num_responses += 1 rnode.last_events.append((time.time(), node.RESPONSE)) rnode.last_events[:rnode.max_last_events] rnode.last_seen = current_time def _update_rnode_on_timeout(self, rnode): """Register a timeout for this rnode. You should call this method when getting a timeout for this node. """ rnode.last_action_ts = time.time() rnode.msgs_since_timeout = 0 rnode.num_timeouts += 1 rnode.last_events.append((time.time(), node.TIMEOUT)) rnode.last_events[:rnode.max_last_events]
class RoutingManager(object): def __init__(self, my_node, querier, bootstrap_lookup_f, bootstrap_nodes): self.my_node = my_node self.querier = querier self.bootstrap_lookup = bootstrap_lookup_f #Copy the bootstrap list self.bootstrap_nodes = [n for n in bootstrap_nodes] self.table = RoutingTable(my_node, NODES_PER_BUCKET) self.ping_msg = message.OutgoingPingQuery(my_node.id) self.find_closest_msg = message.OutgoingFindNodeQuery( my_node.id, my_node.id) #This must be called by an external party: self.do_bootstrap() #After initializing callbacks # maintenance variables self.last_maintenance_index = -1 self.maintenance_mode = BOOTSTRAP_MODE self.lookup_mode = False self._query_received_queue = QueryReceivedQueue(self.table) self._found_nodes_queue = FoundNodesQueue(self.table) def notify_lookup_start(self): self.lookup_mode = True def notify_lookup_stop(self): self.lookup_mode = False def do_maintenance(self): if self.maintenance_mode == BOOTSTRAP_MODE: self._do_bootstrap() elif self.maintenance_mode == FIND_CLOSEST_MODE: self.bootstrap_lookup() self.maintenance_mode = NORMAL_MODE else: self._ping_staled_buckets() return MAINTENANCE_DELAY[self.maintenance_mode] def _do_bootstrap(self): try: node = self.bootstrap_nodes.pop(0) except (IndexError): # Stop bootstrap. Now find closest nodes self.maintenance_mode = FIND_CLOSEST_MODE else: self._send_maintenance_ping(node) def _ping_staled_buckets(self): current_time = time.time() for log_distance, buckets in enumerate(self.table.buckets[:-1]): # Exclude last bucket (myself) m_bucket = buckets[0] if current_time < m_bucket.last_maintenance_ts + REFRESH_PERIOD: continue rnode = m_bucket.get_freshest_rnode() if rnode and current_time > rnode.last_seen + REFRESH_PERIOD: m_bucket.last_maintenance_ts = current_time print '[%f] staled bucket %d' % (time.time(), log_distance) target = self.my_node.id.generate_close_id(log_distance) self.bootstrap_lookup(target) def _send_maintenance_ping(self, node_): if node_.id: log_distance = self.table.find_next_bucket_with_room_index(node_) else: # Bootstrap nodes don't have id log_distance = 0 if 0: #log_distance: target = self.my_node.id.generate_close_id(log_distance) msg = message.OutgoingFindNodeQuery(self.my_node.id, target) m_bucket, r_bucket = self.table.buckets[log_distance] print '[%f] FIND_NODE(%d:%d:%d)' % (time.time(), log_distance, len(m_bucket), len(r_bucket)), else: print '[%f] PING' % (time.time()), msg = self.ping_msg if node_.id: m_bucket, r_bucket = self.table.get_buckets(node_) print 'to (%r) %d:%d:%d --' % (node_.addr, node_.log_distance(self.my_node), len(m_bucket), len(r_bucket)) else: print 'to UNKNOWN id' self.querier.send_query(msg, node_) def on_query_received(self, node_): rnode = self._on_msg_received(node_) if rnode: # node in routing table self._update_rnode_on_query_received(rnode) def on_response_received(self, node_, rtt=0): #TODO2:, rtt=0): logger.debug('response from %r' % (node_)) rnode = self._on_msg_received(node_) if rnode: # node in routing table self._update_rnode_on_response_received(rnode) def _on_msg_received(self, node_): m_bucket, r_bucket = self.table.get_buckets(node_) rnode = m_bucket.get_rnode(node_) if rnode: # The node is in the bucket (it needs to be updated) return rnode # The node is not in main if m_bucket.there_is_room(): # There is room rnode = node_.get_rnode() m_bucket.add(rnode) return rnode # The main bucket is full bad_rnode = self._pop_bad_rnode(m_bucket) if bad_rnode: print 'replacing bad rnode...' # There is a bad node, add the new node rnode = node_.get_rnode() m_bucket.add(rnode) return rnode q_rnodes = self._get_questionable_rnodes(m_bucket) if q_rnodes: print 'pinging questionable rnodes...' # There are questionable nodes, ping them rnode = node_.get_rnode() r_bucket.rnodes = [rnode] for rnode in q_rnodes: self._send_maintenance_ping(rnode) return rnode def on_error_received(self, node_): pass def on_timeout(self, node_): if not node_.id: return # This is a bootstrap node (just addr, no id) m_bucket, r_bucket = self.table.get_buckets(node_) rnode = m_bucket.get_rnode(node_) if rnode: # Node in routing table print 'timeout', rnode.addr, self.my_node.log_distance(rnode) self._update_rnode_on_timeout(rnode) if len(r_bucket): # There is a replacement node r_rnode = r_bucket.rnodes[0] if r_rnode.last_seen > time.time() - 60: # And it is fresh if rnode.timeouts_in_a_row() == 1: # Give it another chance, ping it self._send_maintenance_ping(rnode) else: # Several timeouts. Replace! m_bucket.remove(rnode) m_bucket.add(r_rnode) del r_bucket.rnodes[0] def on_nodes_found(self, nodes): return def get_closest_rnodes(self, target_id, num_nodes=DEFAULT_NUM_NODES): return self.table.get_closest_rnodes(target_id, num_nodes) def get_main_rnodes(self): return self.table.get_main_rnodes() def print_stats(self): self.table.print_stats() def _refresh_replacement_bucket(self, bucket): for rnode in bucket.rnodes: self._send_maintenance_ping(rnode) print '-----------' def _pop_bad_rnode(self, bucket): for rnode in bucket.rnodes: if rnode.timeouts_in_a_row() > 1: print 'bad rnode' bucket.remove(rnode) return rnode def _get_questionable_rnodes(self, bucket): q_nodes = [] for rnode in bucket.rnodes: if time.time() > rnode.last_seen + REFRESH_PERIOD: print 'old>', q_nodes.append(rnode) continue if rnode.num_responses == 0: print 'no_responses', q_nodes.append(rnode) if q_nodes: print '' return q_nodes 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 def _update_rnode_on_query_received(self, rnode): """Register a query from node. You should call this method when receiving a query from this node. """ current_time = time.time() rnode.last_action_ts = time.time() rnode.msgs_since_timeout += 1 rnode.num_queries += 1 rnode.last_events.append((current_time, node.QUERY)) rnode.last_events[:rnode.max_last_events] rnode.last_seen = current_time def _update_rnode_on_response_received(self, rnode, rtt=0): """Register a reply from rnode. You should call this method when receiving a response from this rnode. """ current_time = time.time() #rnode._reset_refresh_task() if rnode.in_quarantine: rnode.in_quarantine = \ rnode.last_action_ts < current_time - node.QUARANTINE_PERIOD rnode.last_action_ts = current_time rnode.num_responses += 1 rnode.last_events.append((time.time(), node.RESPONSE)) rnode.last_events[:rnode.max_last_events] rnode.last_seen = current_time def _update_rnode_on_timeout(self, rnode): """Register a timeout for this rnode. You should call this method when getting a timeout for this node. """ rnode.last_action_ts = time.time() rnode.msgs_since_timeout = 0 rnode.num_timeouts += 1 rnode.last_events.append((time.time(), node.TIMEOUT)) rnode.last_events[:rnode.max_last_events]