// $Id: Node.java,v 1.30 2007/11/08 07:35:13 zahorjan Exp $ import java.util.*; import org.jgrapht.Graphs; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; /** * Abstract base class of DV and LS node types. * Implements common functionality -- most everything but timestep based routing table update. */ public abstract class Node { private static int nextUID = 0; /** * Each node has an integer UID. (They're consecutive.) */ protected int uid; /** * Each node has a routing table. */ private RoutingTable routingTable; // maps destination node id to next hop node id /** * Each node has two queues of incoming update packets: *
    *
  1. The packets sent to it during the last time step. *
  2. The packets being sent to it during this time step. *
* Packets in the first queue "have arrived," and can be read during this time step. * Packets in the second queue (i.e., sent this time step) aren't available until the * next time step. */ private LinkedList currentInQueue; private LinkedList futureInQueue; /** * Each node has a current list of neighbors */ protected LinkedHashSet neighbor; /** * Each neighbor has a time since last heard from */ protected HashMap lastHeardDelay; /** * Time slot in which we last ran */ protected int lastUpdatedSlot; /** * Indicator of whether or not this node has failed */ protected boolean failed; protected static MyRandom rand = null; private static final float FAILURE_PROB = 1.0F / ConstSysProperties.NODE_MTF; private static final float RECOVERY_PROB = 1.0F / ConstSysProperties.NODE_MTR; public int getUID() { return uid; } public RoutingTable getRoutingTable() { return routingTable; } public boolean hasFailed() { return failed; } public Node() { uid = nextUID++; routingTable = new RoutingTable(); routingTable.setEntry(this, this, 0); currentInQueue = new LinkedList(); futureInQueue = new LinkedList(); lastHeardDelay = new HashMap(); lastUpdatedSlot = 0; failed = false; if ( rand == null ) rand = new MyRandom(); } /** * Fills in the routing table using an oracle (i.e., reliable/correct) * view of network connectivity. */ public void oracleComputeRoutingTable() { oracleFillTable( routingTable ); } /** * Fills in the routing table using an oracle (i.e., reliable/correct) * view of network connectivity. */ public void oracleFillTable(RoutingTable rt) { Network network = Network.getNetwork(); ClosestFirstIterator iter = new ClosestFirstIterator(network, this); while (iter.hasNext()) { Node next = iter.next(); if ( next == this ) continue; DefaultEdge e = iter.getSpanningTreeEdge(next); // the graph is undirected, so we're not sure at which end of an 'edge' // to look for the one 'next' is connected to Node predecessor = network.getEdgeSource(e); if ( predecessor == next ) predecessor = network.getEdgeTarget(e); if ( predecessor == this ) { rt.setEntry(next, next, 1); } else { rt.setEntry(next, rt.getHopTo(predecessor), rt.getDistTo(predecessor) + 1 ); } } } /** * Called as an initialization step, once the network topology is finalized. * Node should do whatever it has to do to understand who it thinks its neighbors * are. */ public void setupNeighborInformation() { neighbor = new LinkedHashSet( Graphs.neighborListOf(Network.getNetwork(), this) ); for ( Node n : neighbor ) { lastHeardDelay.put(n, new Integer(0) ); } } /** * Send an update packet */ protected void sendUpdatePacket(Node dest, UpdatePacket p) throws IllegalArgumentException { if ( !neighbor.contains(dest) ) throw new IllegalArgumentException("Node " + this + " attempting to send to a non-neighbor destination (" + dest + ")"); dest.futureInQueue.add(p); ScoreKeeper.getScoreKeeper().sentUpdate(); } /** * Swaps the current and future in queues. (Called by Network code each time it * is asked to take a step.) */ public void swapInQueues() { LinkedList temp = currentInQueue; currentInQueue = futureInQueue; futureInQueue = temp; } /** * Causes node to read currentInQueue of update packets and act on them. */ public void takeStep() { // check for failure / recovery. Note that you can't fail and recover // in the same step (which is good, as the future queue isn't cleared // until it becomes the current queue). if ( hasFailed() ) { currentInQueue.clear(); if ( rand.nextFloat() < RECOVERY_PROB) setHasFailed(false); } else { if ( rand.nextFloat() < FAILURE_PROB) setHasFailed(true); } // now actually take a step, depending on the current state of node if ( rand.nextFloat() < ConstSysProperties.UPDATE_PROB && !hasFailed()) { // inform subclass that a step is starting startUpdate(); // first increment time since we heard from each neighbor int tDelta = sim.getTime() - lastUpdatedSlot; lastUpdatedSlot = sim.getTime(); for ( Node n : neighbor ) { // increment only if we currently think the node is up if ( routingTable.getDistTo(n) < ConstAlg.INFINITY ) lastHeardDelay.put(n, new Integer(lastHeardDelay.get(n) + tDelta)); } // update state based on received update packets. UpdatePacket p; while (!currentInQueue.isEmpty()) { p = currentInQueue.remove(); if ( ConstConfig.DEBUG && routingTable.getDistTo(p.fromNode) >= ConstAlg.INFINITY ) System.out.println( this + " sees " + p.fromNode + " is back at t=" + sim.getTime() ); lastHeardDelay.put( p.fromNode, new Integer(0) ); // allow subclass to process update processUpdate(p); } // look for neighbors that appear to be unreachable. // can't remove elements from collection we're iterating over, // so remember ones to remove and then remove them later. for ( Node n : neighbor ) { if ( lastHeardDelay.get(n) >= ConstAlg.FAILED_TIMEOUT ) { // notify subclass of failure neighborFailureEvent(n); lastHeardDelay.put(n, new Integer(0)); if ( ConstConfig.DEBUG ) System.out.println(this + " sees " + n + " down at t=" + sim.getTime()); } } } // indicate to the node's implementation that we're done with this time step. // it should send any update packets it feels are useful. stopUpdate(); } /** * Allows manipulation of failure/recovery of this node. */ public boolean setHasFailed( boolean f ) { boolean result = failed; failed = f; // if this node has failed, it can't route to anywhere if ( failed ) { if ( ConstConfig.DEBUG ) System.out.println(this + " failing at t=" + sim.getTime()); // update Network graph Network.getNetwork().removeVertex(this); // throw away all routing table info (and turn off logging) routingTable.cleanTable(); // notify subclass of failure failureEvent(); } else if (result == true ) { // it was down; now it's up. Put this node back // in the network graph. if ( ConstConfig.DEBUG ) System.out.println(this + " recovering at t=" + sim.getTime()); Network.getNetwork().addVertex(this); for ( Node o : neighbor ) { lastHeardDelay.put( o, new Integer(0) ); if ( !o.hasFailed() ) Network.getNetwork().addEdge(this, o); } // notify the subclass that the node has recovered recoveryEvent(); // make sure the self link entry is correct routingTable.setEntry(this, this, 0); } return result; } /** * When this node is selected to run during a time step, the "driver" * code in Node.java: *
    *
  1. first calls this routine (startUpdate()) *
  2. then with high, but not certain, probability calls processUpdate() * for each update packet in the node's queue *
  3. * (1) and (3) allow some flexibility in the implementation of * different (sub)classes of node, without having to meddle with the * code in Node.java. */ protected abstract void startUpdate(); /** * Called for each update packet in this node's queue * when it is selected to run during a time step. */ protected abstract void processUpdate(UpdatePacket p); /** * See startUpdate(). */ protected abstract void stopUpdate(); /** * Invoked when this node fails. */ protected abstract void failureEvent(); /** * Invoked when this node recovers from a failure. */ protected abstract void recoveryEvent(); /** * Invoked when a timeout indicates that a neighbor node * has failed. */ protected abstract void neighborFailureEvent(Node neighbor); public String toString() { return new Integer(uid).toString(); } }