class PeersManager(object): """ Manage all the neighbours of a node """ # the number of neigbours should be inside a range of value # if exp =10 and percentage = 0.2, then we should have between 8 and 12 neighbours PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS = 0.2 def __init__(self, node, params): """ Constructor node : node using this manager peersParams : initialization parameters of the peer manager, a list [entitiesFileName, maxPeers, logger] """ self.node = node self.entitiesFileName = params.entities_file self.setExpectedPeers(params.expected_neighbours) self.logger = logging.getLogger("root") self.reset() def reset(self): # hash table of peers indexed by ID self.peers = {} # clountercloclwise ordered list of peers self.ccwPeers = CcwList() # distance ordered list of peers self.distPeers = DistList() def setExpectedPeers(self, nbPeers): maxCoeff = 1 + PeersManager.PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS minCoeff = 1 - PeersManager.PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS self.expectedPeers = nbPeers self.maxPeers = int(self.expectedPeers * maxCoeff) self.minPeers = int(self.expectedPeers * minCoeff) def getExpectedPeers(self): return self.expectedPeers def getRandomPeer(self): """ Return a peer randomly chosen from a file Read the entities file and return a random peer """ lines = [] try: f = file(self.entitiesFileName, 'r') # read file lines = f.readlines() f.close() except: self.logger.critical("Cannot read file " + self.entitiesFileName) raise # retrieve peer peer = '' # ignore blank lines in file while peer == '': peer = random.choice(lines).strip() host, stringPort = string.splitfields(peer) port = int(stringPort) addr = Address(host, port) p = Peer(address=addr) return p def hasPeer(self, id_): """ Check if the manager knwos this peer id : ID of the peer Returns True if the manager knows this peer""" return self.peers.has_key(id_) def addPeer(self, p): """ add a new peer p : a peer object Raises : DuplicateIdError, if a peer with the same id already exists EmptyIdError, if the ID of the peer is empty """ referencePosition = self.node.getPosition() p.setLocalPosition(referencePosition) id_ = p.getId() if not id_: raise EmptyIdError() if self.peers.has_key(id_): raise DuplicateIdError(id_) self.peers[id_] = p self.ccwPeers.insert(p) self.distPeers.insert(p) def removePeer(self, id_): """ remove a peer id : ID of the peer to remove Raises : UnknownIdError if the manager has no peer with this ID """ p = self.getPeer(id_) del self.peers[id_] self.ccwPeers.delete(p) self.distPeers.delete(p) def recalculate(self): """ Recalculate topology information. This functions must be called when the node position has changed. """ peers = self.enumeratePeers() self.reset() for p in peers: self.addPeer(p) def updatePeer(self, p): """ update information on a peer. """ self.removePeer(p.getId()) self.addPeer(p) def getPeer(self, peerid): """ Return the peer associated with this ID Raises : UnknownIdError if the manager has no peer with this ID """ try: p = self.peers[peerid] return p except KeyError: raise UnknownIdError(peerid) def heartbeat(self, id_): """ Update status of a peer id : id of the peer that sent us a HEARTBEAT message """ peer = self.peers[id_] peer.setActiveTime(time.time()) def getPeerFromAddress(self, address): """ Get the peer with address 'address' address: address of the peer we are looking for - Address object Return : a peer, or None if no such peer was found """ # Iterate through list of peers ids = self.peers.keys() for id_ in ids: p = self.peers[id_] if p.address.toString() == address.toString(): return p # no peer with this address was found return None def getClosestPeer(self, target, emitter_id = None): """ Return the peer that is the closest to a target position target : a Position object """ closestPeer = None for p in self.peers.values(): if p.getId() == emitter_id: continue if closestPeer is None or p.isCloser(closestPeer, target): closestPeer = p return closestPeer def getPeerAround(self, targetPosition, emitter_id, isClockWise=True): """ Return the peer that is the closest to a target position and that is in the right half plane. targetPosition : target position which we are looking around isClockWise : boolean indicating if we we searching in the right or the left half-plane - optional Return : a peer or None if there is no peer in the half plane """ found = False around = None distClosest = 0 nodePosition = self.node.getPosition() for p in self.peers.values(): if p.getId() == emitter_id: continue if Geometry.inHalfPlane(nodePosition, targetPosition, p.getPosition()) == isClockWise: # first entity in right half-plane if not found: found = True around = p distClosest = Geometry.distance(targetPosition, p.getPosition()) else: dist = Geometry.distance(targetPosition, p.getPosition()) # this peer is closer if dist < distClosest: around = p distClosest = dist return around def getMedianAwarenessRadius(self): """ Return the median value of the awareness radius of all our peers. This is needed during the connection phasis when we to guess a reasonnable value for our AR """ arList = [] for p in self.enumeratePeers(): arList.append(p.getAwarenessRadius()) arList.sort() return arList[len(arList)//2] def getNumberOfPeers(self): return len(self.peers) def hasTooFewPeers(self): """ Check if we have too few neighbours Return True if we have too few peers""" return self.getNumberOfPeers() < self.minPeers def hasTooManyPeers(self): """ Check if we have too many neighbours Return True if we have too many peers""" return self.getNumberOfPeers() > self.maxPeers def isPeerAccepted(self, peer): """ Check, if a peer is the worst peer. """ if not self.hasTooManyPeers(): return True if self.hasPeer(peer.getId()): worst = self.getWorstPeer() else: self.addPeer(peer) worst = self.getWorstPeer() self.removePeer(peer.getId()) return worst is not peer def getWorstPeer(self): """ Choose a peer for removal. Removing this must NOT break the global connectivity rule. Return the worst or None if we cannot remove a peer """ filter_list = self.necessaryPeers() i = len(self.distPeers) - 1 while i >= 0: peer = self.distPeers.ll[i] if peer not in filter_list: return peer i -= 1 return None def necessaryPeers(self): """ Returns the list of peers that are necessary for our global connectivity. """ n = len(self.ccwPeers) if n < 4: return self.enumeratePeers() result = [] for i in xrange(n): pred_pos = self.ccwPeers.ll[i - 2].getPosition() pos = self.node.getPosition() succ_pos = self.ccwPeers.ll[i].getPosition() if not Geometry.inHalfPlane(pred_pos, pos, succ_pos): result.append(self.ccwPeers.ll[i - 1]) return result def enumeratePeers(self): """ return a list with all peers """ return self.peers.values() def getBadGlobalConnectivityPeers(self): """ Check if global connectivity is ensured Return a pair of entities not respecting property or an empty set. First entity should be used to search clockwise and the second one ccw""" result = [] nodePos = self.node.getPosition() length = self.getNumberOfPeers() if length == 0: return [] if length == 1: (peer,) = self.peers.values() return [peer, peer] for index in range(length): ent = self.ccwPeers.ll[index] nextEnt = self.ccwPeers.ll[ (index+1) % length ] entPos = ent.getPosition() nextEntPos = nextEnt.getPosition() if not Geometry.inHalfPlane(entPos, nodePos, nextEntPos): return [ent, nextEnt] return [] def hasGlobalConnectivity(self): """ Return True if Global connectivity rule is respected""" return self.getNumberOfPeers() > 0 and self.getBadGlobalConnectivityPeers() == [] def computeAwarenessRadius(self): """ Based on curent the repartition of our peers (number, position), compute what should be our awareness radius Return an awareness radius (integer) """ if self.hasTooManyPeers(): offset = self.getNumberOfPeers() - self.maxPeers index = len(self.distPeers) - offset - 1 # Get the average between the max inside distance and the min outside distance pos_outside = self.distPeers.ll[index].getPosition() pos_inside = self.distPeers.ll[index - 1].getPosition() dist_outside = Geometry.distance(self.node.getPosition(), pos_outside) dist_inside = Geometry.distance(self.node.getPosition(), pos_inside) return (dist_outside + dist_inside) // 2 if self.hasTooFewPeers(): fartherPeerPos = self.distPeers.ll[len(self.distPeers) - 1].getPosition() maxDist = Geometry.distance(self.node.getPosition(), fartherPeerPos) # Areal density return maxDist * math.sqrt(self.expectedPeers / self.getNumberOfPeers()) else: fartherPeerPos = self.distPeers.ll[len(self.distPeers) - 1].getPosition() return Geometry.distance(self.node.getPosition(), fartherPeerPos)
class PeersManager(object): """ Manage all the neighbours of a node """ # the number of neigbours should be inside a range of value # if exp =10 and percentage = 0.2, then we should have between 8 and 12 neighbours PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS = 0.2 def __init__(self, node, params): """ Constructor node : node using this manager peersParams : initialization parameters of the peer manager, a list [entitiesFileName, maxPeers, logger] """ self.node = node self.entitiesFileName = params.entities_file self.setExpectedPeers(params.expected_neighbours) self.logger = logging.getLogger("root") # hash table of peers indexed by ID self.peers = {} # clountercloclwise ordered list of peers self.ccwPeers = CcwList() # distance ordered list of peers self.distPeers = DistList() def setExpectedPeers(self, nbPeers): maxCoeff = 1 + PeersManager.PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS minCoeff = 1 - PeersManager.PERCENTAGE_VARIATION_EXPECTED_NEIGHBOURS self.expectedPeers = nbPeers self.maxPeers = int(self.expectedPeers * maxCoeff) self.minPeers = int(self.expectedPeers * minCoeff) def getRandomPeer(self): """ Return a peer randomly chosen from a file Read the entities file and return a random peer """ try: f = file(self.entitiesFileName, 'r') # read file lines = f.readlines() # retrieve peer peer = '' # ignore blank lines in file while peer == '': peer = random.choice(lines).strip() host, stringPort = string.splitfields(peer) port = int(stringPort) addr = Address(host, port) f.close() except: self.logger.critical("Cannot read file " + self.entitiesFileName) raise p = Peer(address=addr) return p def hasPeer(self, id_): """ Check if the manager knwos this peer id : ID of the peer Returns True if the manager knows this peer""" return self.peers.has_key(id_) def addPeer(self, p): """ add a new peer p : a peer object Raises : DuplicateIdError, if a peer with the same id already exists EmptyIdError, if the ID of the peer is empty """ referencePosition = self.node.getPosition() p.setLocalPosition(referencePosition) id_ = p.getId() if not id_: raise EmptyIdError() if self.peers.has_key(id_): raise DuplicateIdError(id_) self.peers[id_] = p self.ccwPeers.insert(p) self.distPeers.insert(p) def removePeer(self, id_): """ remove a peer id : ID of the peer to remove Raises : UnknownIdError if the manager has no peer with this ID """ p = self.getPeer(id_) del self.peers[id_] self.ccwPeers.delete(p) self.distPeers.delete(p) def updatePeer(self, p): """ update information on a peer. """ self.removePeer(p.getId()) self.addPeer(p) def getPeer(self, peerid): """ Return the peer associated with this ID Raises : UnknownIdError if the manager has no peer with this ID """ try: p = self.peers[peerid] return p except KeyError: raise UnknownIdError(peerid) def heartbeat(self, id_): """ Update status of a peer id : id of the peer that sent us a HEARTBEAT message """ peer = self.peers[id_] peer.setActiveTime(time.time()) def getPeerFromAddress(self, address): """ Get the peer with address 'address' address: address of the peer we are looking for - Address object Return : a peer, or None if no such peer was found """ # Iterate through list of peers ids = self.peers.keys() for id_ in ids: p = self.peers[id_] if p.address.toString() == address.toString(): return p # no peer with this address was found return None def getClosestPeer(self, target): """ Return the peer that is the closest to a target position target : a Position object """ closestPeer = self.peers.values()[0] for p in self.peers.values(): if p.isCloser(closestPeer, target): closestPeer = p return closestPeer def getPeerAround(self, targetPosition, isClockWise=True): """ Return the peer that is the closest to a target position and that is in the right half plane. targetPosition : target position which we are looking around isClockWise : boolean indicating if we we searching in the right or the left half-plane - optional Return : a peer or None if there is no peer in the half plane """ found = False around = None distClosest = 0 nodePosition = self.node.getPosition() for p in self.peers.values(): if Geometry.inHalfPlane(nodePosition, targetPosition, p.getPosition()) == isClockWise: # first entity in right half-plane if not found: found = True around = p distClosest = Geometry.distance(targetPosition, p.getPosition()) else: dist = Geometry.distance(targetPosition, p.getPosition()) # this peer is closer if dist < distClosest: around = p distClosest = dist return around def getMedianAwarenessRadius(self): """ Return the median value of the awareness radius of all our peers. This is needed during the connection phasis when we to guess a reasonnable value for our AR """ arList = [] for p in self.enumeratePeers(): arList.append(p.getAwarenessRadius()) arList.sort() return arList[len(arList)//2] def getNumberOfPeers(self): return len(self.peers) def hasTooFewPeers(self): """ Check if we have too few neighbours Return True if we have too few peers""" return self.getNumberOfPeers() < self.minPeers def hasTooManyPeers(self): """ Check if we have too many neighbours Return True if we have too many peers""" return self.getNumberOfPeers() > self.maxPeers def isWorstPeer(self, peer): """ Check, if a peer is the worst peer. """ if self.hasPeer(peer.getId()): worst = self.getWorstPeer() else: self.addPeer(peer) worst = self.getWorstPeer() self.removePeer(peer.getId()) if worst is not None: return worst.getId() == peer.getId() else: return False def getWorstPeer(self): """ Choose a peer for removal. Removing this must NOT break the global connectivity rule. Return the worst or None if we cannot remove a peer """ worstList = self.getWorstPeers() worst = None # there is a posibility to remove any entity in FilterList. # By default, we decide to remove the farthest entity. # In article presented at RESH'02, we proposed several other possibilities # for more efficient choice. if len(worstList) > 0: worst = worstList[0] return worst def getWorstPeers(self): """ Return a list of peers with which we should disconnect. Removing these peers must NOT break the global connectivity rule. Return a list of peers or [] if we cannot remove a peer """ if not self.hasTooManyPeers(): return [] # filter list of neighbors # keep only entities not in Awareness Area which do not provoke mis-respect # of Global Connectivity Rule FilterList = [] endFilter = True indexFilter = len(self.distPeers) - 1 nodePos = self.node.getPosition() while endFilter and indexFilter > 0: ent = self.distPeers.ll[indexFilter] distEnt = Geometry.distance(ent.getPosition(), nodePos) # first, verify that ent is not in Awareness Area if distEnt > self.node.getAwarenessRadius() : # and that we are not in its AR if distEnt > ent.getAwarenessRadius(): indInCcw = self.ccwPeers.ll.index(ent) successor = self.ccwPeers.ll[(indInCcw + 1) % len(self.ccwPeers)] predecessor = self.ccwPeers.ll[indInCcw - 1] # then verify that ent is not mandatory for Rule respect if Geometry.inHalfPlane(predecessor.getPosition(), nodePos, successor.getPosition()): FilterList.append(ent) else: # stop iteration because all following entities are in Awareness # Radius endFilter = False indexFilter -= 1 return FilterList def enumeratePeers(self): """ return a list with all peers """ return self.peers.values() def getBadGlobalConnectivityPeers(self): """ Check if global connectivity is ensured Return a pair of entities not respecting property or an empty set. First entity should be used to search clockwise and the second one ccw""" result = [] nodePos = self.node.getPosition() length = self.getNumberOfPeers() # three or more entities, if length >= 2: for index in range(length) : ent = self.ccwPeers.ll[index] nextEnt = self.ccwPeers.ll[ (index+1) % length ] entPos = ent.getPosition() nextEntPos = nextEnt.getPosition() if not Geometry.inHalfPlane(entPos, nodePos, nextEntPos) : result = [ent, nextEnt] return result def hasGlobalConnectivity(self): """ Return True if Global connectivity rule is respected""" return self.getBadGlobalConnectivityPeers() == [] def computeAwarenessRadius(self): """ Based on curent the repartition of our peers (number, position), compute what should be our awareness radius Return an awareness radius (integer) """ if self.hasTooManyPeers(): offset = self.getNumberOfPeers() - self.maxPeers # get the awareness radius of the last peer that is inside our AR # in distance order index = len(self.distPeers) - offset -1 return self.distPeers.ll[index].getAwarenessRadius() elif self.hasTooFewPeers(): fartherPeerPos = self.distPeers.ll[len(self.distPeers) - 1].getPosition() maxDist = Geometry.distance(self.node.getPosition(), fartherPeerPos) density = maxDist / self.getNumberOfPeers() return self.expectedPeers / density else: fartherPeerPos = self.distPeers.ll[len(self.distPeers) - 1].getPosition() return Geometry.distance(self.node.getPosition(), fartherPeerPos)