Implement rerouting feature in PathCalcRuntimeModule

Change-Id: Ie37ebd1fa6910e999d457481d7082adb0d1d9a3a
diff --git a/src/main/java/net/onrc/onos/intent/Intent.java b/src/main/java/net/onrc/onos/intent/Intent.java
index 8e20bb7..002d6f6 100644
--- a/src/main/java/net/onrc/onos/intent/Intent.java
+++ b/src/main/java/net/onrc/onos/intent/Intent.java
@@ -50,4 +50,9 @@
 	public int hashCode() {
 		return id.hashCode();
 	}
+	
+	@Override
+	public String toString() {
+		return id.toString() + ", " + state.toString();
+	}
 }
diff --git a/src/main/java/net/onrc/onos/intent/IntentMap.java b/src/main/java/net/onrc/onos/intent/IntentMap.java
index 57ce5e0..9969433 100644
--- a/src/main/java/net/onrc/onos/intent/IntentMap.java
+++ b/src/main/java/net/onrc/onos/intent/IntentMap.java
@@ -49,18 +49,32 @@
 	}
 
 	private HashSet<ChangedListener> listeners = new HashSet<>();
-	protected HashMap<String, Intent> intents = new HashMap<>();
+	private HashMap<String, Intent> intents = new HashMap<>();
+	
+	protected void putIntent(Intent intent) {
+		if (intents.containsKey(intent.getId()))
+			removeIntent(intent.getId());
+		intents.put(intent.getId(), intent);
+	}
+	
+	protected void removeIntent(String intentId) {
+		intents.remove(intentId);		
+	}
+	
+	public Intent getIntent(String intentId) {
+		return intents.get(intentId);
+	}
 
 	public void executeOperations(IntentOperationList operations) {
 		LinkedList<ChangedEvent> events = new LinkedList<>();
 		for (IntentOperation operation: operations) {
 			switch (operation.operator) {
 			case ADD:
-				intents.put(operation.intent.getId(), operation.intent);
+				putIntent(operation.intent);
 				events.add(new ChangedEvent(ChangedEventType.ADDED, operation.intent));
 				break;
 			case REMOVE:
-				Intent intent = intents.get(operation.intent.getId());
+				Intent intent = getIntent(operation.intent.getId());
 				if (intent == null) {
 					// TODO throw exception
 				}
@@ -78,14 +92,16 @@
 	}
 
 	public void purge() {
-		Iterator<Entry<String, Intent>> i = intents.entrySet().iterator();
-		while (i.hasNext()) {
-			Entry<String, Intent> entry = i.next();
+		LinkedList<String> removeIds = new LinkedList<>();
+		for (Entry<String, Intent> entry: intents.entrySet()) {
 			Intent intent = entry.getValue();
 			if (intent.getState() == IntentState.DEL_ACK
 					|| intent.getState() == IntentState.INST_NACK) {
-				i.remove();
-			}
+				removeIds.add(intent.getId());
+			}			
+		}
+		for (String intentId: removeIds) {
+			removeIntent(intentId);
 		}
 	}
 
@@ -93,10 +109,6 @@
 		return intents.values();
 	}
 
-	public Intent getIntent(String key) {
-		return intents.get(key);
-	}
-
 	public void addChangeListener(ChangedListener listener) {
 		listeners.add(listener);
 	}
diff --git a/src/main/java/net/onrc/onos/intent/IntentOperation.java b/src/main/java/net/onrc/onos/intent/IntentOperation.java
index 8ac45c9..c769811 100644
--- a/src/main/java/net/onrc/onos/intent/IntentOperation.java
+++ b/src/main/java/net/onrc/onos/intent/IntentOperation.java
@@ -26,4 +26,9 @@
 
 	public Operator operator;
 	public Intent intent;
+	
+	@Override
+	public String toString() {
+		return operator.toString() + ", (" + intent.toString() + ")";
+	}
 }
diff --git a/src/main/java/net/onrc/onos/intent/PathIntent.java b/src/main/java/net/onrc/onos/intent/PathIntent.java
index 4ef4cf0..a5454fa 100644
--- a/src/main/java/net/onrc/onos/intent/PathIntent.java
+++ b/src/main/java/net/onrc/onos/intent/PathIntent.java
@@ -1,16 +1,19 @@
 package net.onrc.onos.intent;
 
+import java.util.LinkedList;
+import java.util.List;
+
 import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 import net.onrc.onos.ofcontroller.networkgraph.Path;
 import net.onrc.onos.ofcontroller.networkgraph.Port;
-import net.onrc.onos.ofcontroller.networkgraph.Switch;
 
 /**
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public class PathIntent extends Intent {
-	protected long pathData[];
+	protected List<LinkEvent> path;
 	protected double bandwidth;
 	protected Intent parentIntent;
 
@@ -31,13 +34,13 @@
 	 */
 	public PathIntent(String id, Path path, double bandwidth, Intent parentIntent) {
 		super(id);
-		pathData = new long[path.size() * 4];
-		for (int i=0; i<path.size(); i++) {
-			Link link = path.get(i);
-			this.pathData[i*4] = link.getSourceSwitch().getDpid();
-			this.pathData[i*4+1] = link.getSourcePort().getNumber();
-			this.pathData[i*4+2] = link.getDestinationSwitch().getDpid();
-			this.pathData[i*4+3] = link.getDestinationPort().getNumber();
+		this.path = new LinkedList<LinkEvent>();
+		for (Link link: path) {
+			this.path.add(new LinkEvent(
+					link.getSourceSwitch().getDpid(),
+					link.getSourcePort().getNumber(),
+					link.getDestinationSwitch().getDpid(),
+					link.getDestinationPort().getNumber()));
 		}
 		this.bandwidth = bandwidth;
 		this.parentIntent = parentIntent;
@@ -47,8 +50,8 @@
 		return bandwidth;
 	}
 
-	public long[] getPathData() {
-		return pathData;
+	public List<LinkEvent> getPathByLinkEvent() {
+		return path;
 	}
 
 	/**
@@ -57,19 +60,13 @@
 	 * @return path object. If there is no path in the specified graph, returns null.
 	 */
 	public Path getPath(NetworkGraph graph) {
-		Path path = new Path();
-		Switch srcSwitch;
-		Port srcPort;
-		Link link;
-		for (int i=0; i<pathData.length; i+=4) {
-			if ((srcSwitch = graph.getSwitch(pathData[i])) == null) return null;
-			if ((srcPort = srcSwitch.getPort(pathData[i+1])) == null) return null;
-			if ((link = srcPort.getOutgoingLink()) == null) return null;
-			if (link.getDestinationSwitch().getDpid() != pathData[i+2]) return null;
-			if (link.getDestinationPort().getNumber() != pathData[i+3]) return null;
-			path.add(link);
+		Path pathObj = new Path();
+		for (LinkEvent linkEvent: path) {
+			Link link = linkEvent.getLink(graph);
+			if (link == null) return null;
+			pathObj.add(link);
 		}
-		return path;
+		return pathObj;
 	}
 
 	public Intent getParentIntent() {
diff --git a/src/main/java/net/onrc/onos/intent/PathIntentMap.java b/src/main/java/net/onrc/onos/intent/PathIntentMap.java
index 02444ff..b955922 100644
--- a/src/main/java/net/onrc/onos/intent/PathIntentMap.java
+++ b/src/main/java/net/onrc/onos/intent/PathIntentMap.java
@@ -6,43 +6,44 @@
 import java.util.HashSet;
 
 import net.onrc.onos.ofcontroller.networkgraph.Link;
-import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
 
 /**
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public class PathIntentMap extends IntentMap {
-	protected HashMap<Link, HashSet<PathIntent>> linkToIntents = new HashMap<Link, HashSet<PathIntent>>();
-	protected NetworkGraph graph;
+	protected HashMap<LinkEvent, HashSet<PathIntent>> linkToIntents = new HashMap<>();
 
-	public PathIntentMap(NetworkGraph graph) {
-		this.graph = graph;
-	}
-
-	public void addIntent(PathIntent intent) {
-		if (intents.containsKey(intent.getId()))
-			removeIntent((PathIntent)intents.get(intent.getId()));
-		intents.put(intent.getId(), intent);
-		for (Link link: intent.getPath(graph)) {
-			HashSet<PathIntent> value = linkToIntents.get(link);
-			if (value == null) {
+	@Override
+	protected void putIntent(Intent intent) {
+		super.putIntent(intent);
+		for (LinkEvent linkEvent: ((PathIntent) intent).getPathByLinkEvent()) {
+			HashSet<PathIntent> value = linkToIntents.get(linkEvent);
+			if (value == null)
 				value = new HashSet<PathIntent>();
-				linkToIntents.put(link, value);
-			}
-			value.add(intent);
+			value.add((PathIntent) intent);
+			linkToIntents.put(linkEvent, value);
 		}
 	}
 
-	public void removeIntent(PathIntent intent) {
-		intents.remove(intent);
-		for (Link link: intent.getPath(graph)) {
-			HashSet<PathIntent> value = linkToIntents.get(link);
+	@Override
+	protected void removeIntent(String intentId) {
+		PathIntent intent = (PathIntent) getIntent(intentId);
+		for (LinkEvent linkEvent: intent.getPathByLinkEvent()) {
+			HashSet<PathIntent> value = linkToIntents.get(linkEvent);
 			value.remove(intent);
 		}
+		super.removeIntent(intentId);
 	}
 
-	public Collection<PathIntent> getIntentByLink(Link link) {
-		return Collections.unmodifiableCollection(linkToIntents.get(link));
+	public Collection<PathIntent> getIntentsByLink(LinkEvent linkEvent) {
+		Collection<PathIntent> intents = linkToIntents.get(linkEvent);
+		if (intents == null) {
+			return null;
+		}
+		else {
+			return Collections.unmodifiableCollection(intents);
+		}
 	}
 
 	/**
@@ -51,9 +52,11 @@
 	 * @return
 	 */
 	public Double getAvailableBandwidth(Link link) {
+		if (link == null) return null;
 		Double bandwidth = link.getCapacity();
-		if (!bandwidth.isInfinite() && linkToIntents.containsKey(link)) {
-			for (PathIntent intent: getIntentByLink(link)) {
+		LinkEvent linkEvent = new LinkEvent(link);
+		if (!bandwidth.isInfinite() && linkToIntents.containsKey(linkEvent)) {
+			for (PathIntent intent: getIntentsByLink(linkEvent)) {
 				Double intentBandwidth = intent.getBandwidth();
 				if (intentBandwidth == null || intentBandwidth.isInfinite() || intentBandwidth.isNaN())
 					continue;
diff --git a/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java b/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
index 4b324df..4198b18 100644
--- a/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
+++ b/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
@@ -58,7 +58,8 @@
 
 	@Override
 	public String toString() {
-		return String.format("srcDpid:%s, srcPort:%d, srcMac:%s, dstDpid:%s, dstPort:%d, dstMac:%s",
+		return String.format("id:%s, state:%s, srcDpid:%s, srcPort:%d, srcMac:%s, dstDpid:%s, dstPort:%d, dstMac:%s",
+				getId(), state,
 				new Dpid(srcSwitchDpid), srcPortNumber, MACAddress.valueOf(srcMacAddress),
 				new Dpid(dstSwitchDpid), dstPortNumber, MACAddress.valueOf(dstMacAddress));
 	}
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
index 32fe746..4408f3f 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
@@ -5,11 +5,13 @@
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.onrc.onos.intent.ConstrainedBFSTree;
 import net.onrc.onos.intent.ConstrainedShortestPathIntent;
+import net.onrc.onos.intent.Intent;
 import net.onrc.onos.intent.IntentOperation;
 import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.intent.PathIntent;
 import net.onrc.onos.intent.PathIntentMap;
 import net.onrc.onos.intent.ShortestPathIntent;
+import net.onrc.onos.intent.IntentOperation.Operator;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 import net.onrc.onos.ofcontroller.networkgraph.Path;
 import net.onrc.onos.ofcontroller.networkgraph.Switch;
@@ -74,7 +76,7 @@
 				pathIntentOpList.add(new IntentOperation(IntentOperation.Operator.ADD, pathIntent));
 				break;
 			case REMOVE:
-				pathIntentOpList.add(intentOp);
+				pathIntentOpList.add(Operator.REMOVE, new Intent("pi" + intentOp.intent.getId()));
 				break;
 			}
 		}
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
index 26afe51..d72a13a 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
@@ -11,22 +11,43 @@
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.onrc.onos.datagrid.IDatagridService;
 import net.onrc.onos.datagrid.IEventChannel;
+import net.onrc.onos.intent.Intent;
 import net.onrc.onos.intent.IntentMap;
+import net.onrc.onos.intent.IntentOperation.Operator;
 import net.onrc.onos.intent.IntentOperationList;
+import net.onrc.onos.intent.PathIntent;
 import net.onrc.onos.intent.PathIntentMap;
+import net.onrc.onos.ofcontroller.networkgraph.DeviceEvent;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphListener;
 import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
-import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.PortEvent;
+import net.onrc.onos.ofcontroller.networkgraph.SwitchEvent;
 
-public class PathCalcRuntimeModule implements IFloodlightModule, IPathCalcRuntimeService {
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class PathCalcRuntimeModule implements IFloodlightModule, IPathCalcRuntimeService, INetworkGraphListener {
 	private PathCalcRuntime runtime;
 	private IDatagridService datagridService;
 	private INetworkGraphService networkGraphService;
 	private IntentMap highLevelIntents;
 	private PathIntentMap pathIntents;
 
-	private IEventChannel<byte[], IntentOperationList> eventChannel;
+	private IEventChannel<String, IntentOperationList> eventChannel;
 	private static final String EVENT_CHANNEL_NAME = "onos.pathintent";
 
+	private void reroutePaths(LinkEvent linkEvent) {
+		Collection<PathIntent> oldPaths = pathIntents.getIntentsByLink(linkEvent);
+		if (oldPaths == null) return;
+		IntentOperationList reroutingOperation = new IntentOperationList();
+		for (PathIntent pathIntent: oldPaths) {
+			// TODO use Operator.UPDATE instead of REMOVE and ADD in order to optimize
+			reroutingOperation.add(Operator.REMOVE, new Intent(pathIntent.getParentIntent().getId()));
+			reroutingOperation.add(Operator.ADD, pathIntent.getParentIntent());
+		}
+		executeIntentOperations(reroutingOperation);
+	}
 
 	@Override
 	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
@@ -60,23 +81,22 @@
 	public void startUp(FloodlightModuleContext context) {
 		highLevelIntents = new IntentMap();
 		runtime = new PathCalcRuntime(networkGraphService.getNetworkGraph());
-		pathIntents = new PathIntentMap(networkGraphService.getNetworkGraph());
+		pathIntents = new PathIntentMap();
 		eventChannel = datagridService.createChannel(
 				EVENT_CHANNEL_NAME,
-				byte[].class,
+				String.class,
 				IntentOperationList.class);
-	}
-
-	protected void publishPathIntentOperationList(IntentOperationList list) {
-		eventChannel.addEntry(new byte[1], list); // TODO make key bytes		
+		networkGraphService.registerNetworkGraphListener(this);
 	}
 
 	@Override
 	public IntentOperationList executeIntentOperations(IntentOperationList list) {
 		highLevelIntents.executeOperations(list);
 		IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, pathIntents);
+		String key = "..."; // TODO generate key
+		System.out.println(pathIntentOperations);
 		pathIntents.executeOperations(pathIntentOperations);
-		publishPathIntentOperationList(pathIntentOperations);
+		eventChannel.addEntry(key, pathIntentOperations);
 		return pathIntentOperations;
 	}
 
@@ -95,4 +115,44 @@
 		highLevelIntents.purge();
 		pathIntents.purge();
 	}
+
+	@Override
+	public void putSwitchEvent(SwitchEvent switchEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void removeSwitchEvent(SwitchEvent switchEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void putPortEvent(PortEvent portEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void removePortEvent(PortEvent portEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void putLinkEvent(LinkEvent linkEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void removeLinkEvent(LinkEvent linkEvent) {
+		reroutePaths(linkEvent);
+	}
+
+	@Override
+	public void putDeviceEvent(DeviceEvent deviceEvent) {
+		// do nothing
+	}
+
+	@Override
+	public void removeDeviceEvent(DeviceEvent deviceEvent) {
+		// do nothing
+	}
 }
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
index 4582021..19a6b53 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
@@ -37,7 +37,7 @@
 		this.graph = graph;
 		this.flowEntries = new HashSet<>();
 		this.plan = new ArrayList<>();
-		this.intents = new PathIntentMap(this.graph);
+		this.intents = new PathIntentMap();
 	}
 	
 	public void addIntents(IntentOperationList intentOpList) {
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/Link.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/Link.java
index ef74f73..4fd53af 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/Link.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/Link.java
@@ -12,7 +12,7 @@
 	public Port getDestinationPort();
 	public Switch getSourceSwitch();
 	public Switch getDestinationSwitch();
-		
+
 	public long getLastSeenTime();
 
 	public int getCost();
@@ -21,8 +21,12 @@
 	// Not sure if we want to expose these northbound
 	// Toshi: I think these are unnecessary because we can get them
 	// Toshi: like "this.getSourcePort().getSwitch()" etc.
+	@Deprecated
 	public Long getSourceSwitchDpid();
+	@Deprecated
 	public Long getSourcePortNumber();
+	@Deprecated
 	public Long getDestinationSwitchDpid();
+	@Deprecated
 	public Long getDestinationPortNumber();
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkEvent.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkEvent.java
index c8cf71e..83af786 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkEvent.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkEvent.java
@@ -24,18 +24,23 @@
 
     public LinkEvent(Long src_dpid, Long src_port_no, Long dst_dpid,
 	    Long dst_port_no) {
-
 	src = new SwitchPort(src_dpid, src_port_no);
 	dst = new SwitchPort(dst_dpid, dst_port_no);
+    }
 
+    public LinkEvent(Link link) {
+	src = new SwitchPort(link.getSourceSwitch().getDpid(),
+		link.getSourcePort().getNumber());
+	dst = new SwitchPort(link.getDestinationSwitch().getDpid(),
+		link.getDestinationPort().getNumber());
     }
 
     public SwitchPort getSrc() {
-        return src;
+	return src;
     }
 
     public SwitchPort getDst() {
-        return dst;
+	return dst;
     }
 
     @Override
@@ -54,6 +59,47 @@
 
     public byte[] getID() {
 	return getLinkID(src.getDpid(), src.getNumber(),
-			 dst.getDpid(), dst.getNumber());
+		dst.getDpid(), dst.getNumber());
     }
-}
+
+    public Link getLink(NetworkGraph graph) {
+	Port srcPort = graph.getPort(getSrc().getDpid(), getSrc().getNumber());
+	if (srcPort == null) return null;
+	Link link = srcPort.getOutgoingLink();
+	if (link == null) return null;
+	if (link.getDestinationSwitch().getDpid() != getDst().getDpid()) return null;
+	if (link.getDestinationPort().getNumber() != getDst().getNumber()) return null;
+	return link;
+    }
+
+    @Override
+    public int hashCode() {
+	final int prime = 31;
+	int result = 1;
+	result = prime * result + ((dst == null) ? 0 : dst.hashCode());
+	result = prime * result + ((src == null) ? 0 : src.hashCode());
+	return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+	if (this == obj)
+	    return true;
+	if (obj == null)
+	    return false;
+	if (getClass() != obj.getClass())
+	    return false;
+	LinkEvent other = (LinkEvent) obj;
+	if (dst == null) {
+	    if (other.dst != null)
+		return false;
+	} else if (!dst.equals(other.dst))
+	    return false;
+	if (src == null) {
+	    if (other.src != null)
+		return false;
+	} else if (!src.equals(other.src))
+	    return false;
+	return true;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkImpl.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkImpl.java
index c8954a9..801e780 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkImpl.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/LinkImpl.java
@@ -76,39 +76,43 @@
 	}
 
 	@Override
+	public Double getCapacity() {
+		return capacity;
+	}
+
+	public void setCapacity(Double capacity) {
+		this.capacity = capacity;
+	}
+
+	@Deprecated
+	@Override
 	public Long getSourceSwitchDpid() {
 		return srcPort.getSwitch().getDpid();
 	}
 
+	@Deprecated
 	@Override
 	public Long getSourcePortNumber() {
 		return srcPort.getNumber();
 	}
 
+	@Deprecated
 	@Override
 	public Long getDestinationSwitchDpid() {
 		return dstPort.getSwitch().getDpid();
 	}
 
+	@Deprecated
 	@Override
 	public Long getDestinationPortNumber() {
 		return dstPort.getNumber();
 	}
 
 	@Override
-	public Double getCapacity() {
-		return capacity;
-	}
-
-	@Override
 	public String toString() {
 		return String.format("%s --(cap:%f Mbps)--> %s",
 				getSourcePort().toString(),
 				getCapacity(),
 				getDestinationPort().toString());
 	}
-
-	public void setCapacity(Double capacity) {
-		this.capacity = capacity;
-	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/PortEvent.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/PortEvent.java
index b42bf1e..8172f29 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/PortEvent.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/PortEvent.java
@@ -10,8 +10,8 @@
  */
 public class PortEvent {
     public static class SwitchPort {
-        public final Long dpid;
-        public final Long number;
+	public final Long dpid;
+	public final Long number;
 
 	/**
 	 * Default constructor.
@@ -39,6 +39,37 @@
             return "(" + Long.toHexString(dpid) + "@" + number + ")";
         }
 
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((dpid == null) ? 0 : dpid.hashCode());
+            result = prime * result
+        	    + ((number == null) ? 0 : number.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+        	return true;
+            if (obj == null)
+        	return false;
+            if (getClass() != obj.getClass())
+        	return false;
+            SwitchPort other = (SwitchPort) obj;
+            if (dpid == null) {
+        	if (other.dpid != null)
+        	    return false;
+            } else if (!dpid.equals(other.dpid))
+        	return false;
+            if (number == null) {
+        	if (other.number != null)
+        	    return false;
+            } else if (!number.equals(other.number))
+        	return false;
+            return true;
+        }
     }
 
     private final SwitchPort id;
diff --git a/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java b/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
index ce857de..263cbf4 100644
--- a/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
+++ b/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
@@ -1,12 +1,23 @@
 package net.onrc.onos.intent;
 
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraphImpl;
 import net.onrc.onos.ofcontroller.networkgraph.Link;
 import net.onrc.onos.ofcontroller.networkgraph.LinkImpl;
+import net.onrc.onos.ofcontroller.networkgraph.Port;
 import net.onrc.onos.ofcontroller.networkgraph.Switch;
 import net.onrc.onos.ofcontroller.networkgraph.SwitchImpl;
 
 public class MockNetworkGraph extends NetworkGraphImpl {
+	class DetachableLinkImpl extends LinkImpl {
+		public DetachableLinkImpl(NetworkGraph graph, Port srcPort, Port dstPort) {
+			super(graph, srcPort, dstPort);
+		}
+
+		public void detachFromGraph() {
+			unsetFromPorts();
+		}	
+	}
 	public Switch addSwitch(Long switchId) {
 		SwitchImpl sw = new SwitchImpl(this, switchId);
 		this.putSwitch(sw);
@@ -15,7 +26,7 @@
 	}
 
 	public Link addLink(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
-		return new LinkImpl(
+		return new DetachableLinkImpl(
 				this,
 				getSwitch(srcDpid).getPort(srcPortNo),
 				getSwitch(dstDpid).getPort(dstPortNo));
@@ -63,4 +74,11 @@
 			((LinkImpl)link).setCapacity(1000.0);
 		}
 	}
+
+	public void removeLink(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
+		DetachableLinkImpl link = (DetachableLinkImpl)getSwitch(srcDpid).getPort(srcPortNo).getOutgoingLink();
+		if (link.getDestinationSwitch().getDpid().equals(dstDpid) && link.getDestinationPort().getNumber().equals(dstPortNo)) {
+			link.detachFromGraph();
+		}
+	}
 }
diff --git a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
index 8040141..d02cdee 100644
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -12,8 +12,9 @@
 import net.onrc.onos.intent.PathIntent;
 import net.onrc.onos.intent.PathIntentMap;
 import net.onrc.onos.intent.ShortestPathIntent;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphListener;
 import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
-import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 
 import org.easymock.EasyMock;
@@ -48,11 +49,12 @@
 		.andReturn(datagridService).once();
 		EasyMock.expect(modContext.getServiceImpl(EasyMock.eq(INetworkGraphService.class)))
 		.andReturn(networkGraphService).once();
-		
-		networkGraphService.getNetworkGraph();
-		EasyMock.expectLastCall().andReturn(g).anyTimes();
-		
-		EasyMock.expect(datagridService.createChannel("onos.pathintent", byte[].class, IntentOperationList.class))
+
+		EasyMock.expect(networkGraphService.getNetworkGraph()).andReturn(g).anyTimes();
+		networkGraphService.registerNetworkGraphListener(EasyMock.anyObject(INetworkGraphListener.class));
+		EasyMock.expectLastCall();
+
+		EasyMock.expect(datagridService.createChannel("onos.pathintent", String.class, IntentOperationList.class))
 		.andReturn(eventChannel).once();
 
 		EasyMock.replay(datagridService);
@@ -72,18 +74,14 @@
 			PathIntent pathIntent = (PathIntent)intent;
 			System.out.println("Parent intent: " + pathIntent.getParentIntent().toString());
 			System.out.println("Path:");
-			for (Link link: pathIntent.getPath(g)) {
-				System.out.printf("%s --(%f/%f)--> %s\n",
-						link.getSourcePort(),
-						link.getCapacity() - intents.getAvailableBandwidth(link),
-						link.getCapacity(),
-						link.getDestinationPort());
+			for (LinkEvent linkEvent: pathIntent.getPathByLinkEvent()) {
+				System.out.println(linkEvent);
 			}
 		}
 	}
 
 	@Test
-	public void useCase1() throws FloodlightModuleException {
+	public void createShortestPaths() throws FloodlightModuleException {
 		// create shortest path intents
 		IntentOperationList opList = new IntentOperationList();
 		opList.add(Operator.ADD, new ShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 4L));
@@ -106,7 +104,7 @@
 	}
 
 	@Test
-	public void useCase2() throws FloodlightModuleException {
+	public void createConstrainedShortestPaths() throws FloodlightModuleException {
 		// create constrained shortest path intents
 		IntentOperationList opList = new IntentOperationList();
 		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 17L, 400.0));
@@ -131,8 +129,8 @@
 	}
 
 	@Test
-	public void useCase3() throws FloodlightModuleException {
-		// create constrained & not best effort shortest path intents
+	public void createMixedShortestPaths() throws FloodlightModuleException {
+		// create constrained & best effort shortest path intents
 		IntentOperationList opList = new IntentOperationList();
 		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 6L, 600.0));
 		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 7L, 600.0));
@@ -154,4 +152,41 @@
 		showResult((PathIntentMap) runtime1.getPathIntents());
 		System.out.println(runtime2.getPlan());
 	}
+	
+	@Test
+	public void rerouteShortestPaths() throws FloodlightModuleException {
+		// create shortest path intents
+		IntentOperationList opList = new IntentOperationList();
+		opList.add(Operator.ADD, new ShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 4L));
+		opList.add(Operator.ADD, new ShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 5L));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 6L));
+
+		// compile high-level intent operations into low-level intent operations (calculate paths)
+		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
+		runtime1.init(modContext);
+		runtime1.startUp(modContext);
+		IntentOperationList pathIntentOpList = runtime1.executeIntentOperations(opList);
+
+		// compile low-level intents into flow entry installation plan
+		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
+		runtime2.addIntents(pathIntentOpList);
+
+		// show results step1
+		showResult((PathIntentMap) runtime1.getPathIntents());
+		System.out.println(runtime2.getPlan());
+
+		// link down
+		((MockNetworkGraph)g).removeLink(1L, 2L, 9L, 1L); // This link is used by the intent "1"
+		LinkEvent linkEvent = new LinkEvent(1L, 2L, 9L, 1L);
+		runtime1.removeLinkEvent(linkEvent);
+		((MockNetworkGraph)g).removeLink(9L, 1L, 1L, 2L);
+		linkEvent = new LinkEvent(9L, 1L, 1L, 2L);
+		runtime1.removeLinkEvent(linkEvent);
+
+		System.out.println("Link goes down.");
+
+		// show results step2
+		showResult((PathIntentMap) runtime1.getPathIntents());
+		System.out.println(runtime2.getPlan());		
+	}
 }