Merge "Specify RAMCloud thread config in startup script" into dev/ramcloud-new-datamodel
diff --git a/pom.xml b/pom.xml
index ef5aee1..6803d4c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -258,6 +258,24 @@
 	</reportSets>
       </plugin>
       <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>2.5.3</version>
+        <configuration>
+          <reportPlugins>
+            <plugin>
+              <groupId>org.codehaus.mojo</groupId>
+              <artifactId>findbugs-maven-plugin</artifactId>
+            </plugin>
+          </reportPlugins>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>3.0.1</version>
+      </plugin>
+      <plugin>
 	<groupId>org.apache.maven.plugins</groupId>
 	<artifactId>maven-jxr-plugin</artifactId>
 	<version>2.3</version>
diff --git a/src/main/java/net/onrc/onos/datagrid/web/DatagridWebRoutable.java b/src/main/java/net/onrc/onos/datagrid/web/DatagridWebRoutable.java
index 0e8300a..82a1157 100755
--- a/src/main/java/net/onrc/onos/datagrid/web/DatagridWebRoutable.java
+++ b/src/main/java/net/onrc/onos/datagrid/web/DatagridWebRoutable.java
@@ -21,6 +21,7 @@
         router.attach("/get/intents/json", IntentResource.class);
         router.attach("/get/intent/{intent_id}/json", IntentResource.class);
         router.attach("/get/ng-events/json", GetNGEventsResource.class);
+        router.attach("/get/ng-flows/summary/json", GetNGFlowsSummaryResource.class);
         router.attach("/get/intents/{category}/json", IntentResource.class);
         router.attach("/get/intent/{category}/{intent_id}/json", IntentResource.class);
         router.attach("/delete/intents/json", IntentResource.class);
diff --git a/src/main/java/net/onrc/onos/datagrid/web/GetNGFlowsSummaryResource.java b/src/main/java/net/onrc/onos/datagrid/web/GetNGFlowsSummaryResource.java
new file mode 100644
index 0000000..cf02a12
--- /dev/null
+++ b/src/main/java/net/onrc/onos/datagrid/web/GetNGFlowsSummaryResource.java
@@ -0,0 +1,117 @@
+package net.onrc.onos.datagrid.web;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.PathIntent;
+import net.onrc.onos.intent.ShortestPathIntent;
+import net.onrc.onos.intent.Intent.IntentState;
+import net.onrc.onos.intent.IntentMap;
+import net.onrc.onos.intent.runtime.IPathCalcRuntimeService;
+
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.Path;
+import net.onrc.onos.ofcontroller.util.CallerId;
+import net.onrc.onos.ofcontroller.util.Dpid;
+import net.onrc.onos.ofcontroller.util.FlowEntry;
+import net.onrc.onos.ofcontroller.util.FlowId;
+import net.onrc.onos.ofcontroller.util.FlowPath;
+import net.onrc.onos.ofcontroller.util.FlowPathType;
+import net.onrc.onos.ofcontroller.util.FlowPathUserState;
+import net.onrc.onos.ofcontroller.util.Port;
+import net.onrc.onos.ofcontroller.util.SwitchPort;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST API call to get a summary of Flow Paths.
+ *
+ * NOTE: This REST API call is needed for the ONOS GUI.
+ *
+ *    GET /wm/onos/datagrid/get/ng-flows/summary/json
+ */
+public class GetNGFlowsSummaryResource extends ServerResource {
+    public static final Logger log = LoggerFactory.getLogger(GetNGFlowsSummaryResource.class);
+	
+    @Get("json")
+    public ArrayList<FlowPath> retrieve() {
+	ArrayList<FlowPath> result = new ArrayList<>();
+	SortedMap<Long, FlowPath> sortedFlowPaths = new TreeMap<>();
+
+        IPathCalcRuntimeService pathRuntime =
+	    (IPathCalcRuntimeService)getContext().
+	    getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+	log.debug("Get NG Flows Summary");
+
+
+	IntentMap parentIntentMap = pathRuntime.getHighLevelIntents();
+	IntentMap intentMap = pathRuntime.getPathIntents();
+	for (Intent parentIntent : parentIntentMap.getAllIntents()) {
+	    // Get only installed Shortest Paths
+	    if (parentIntent.getState() != IntentState.INST_ACK)
+		continue;
+	    if (! (parentIntent instanceof ShortestPathIntent))
+		continue;
+	    ShortestPathIntent spIntent = (ShortestPathIntent)parentIntent;
+
+	    // Get the Path Intent
+	    Intent intent = intentMap.getIntent(spIntent.getPathIntentId());
+	    if (! (intent instanceof PathIntent))
+		continue;
+	    PathIntent pathIntent = (PathIntent)intent;
+
+	    // Decode the Shortest Path ID
+	    String applnIntentId = parentIntent.getId();
+	    String intentId = applnIntentId.split(":")[1];
+ 	    // A hack for Flow IDs that are not routable 
+	    intentId = intentId.replace("F", "");
+
+	    // Create the Flow Path
+	    FlowId flowId = new FlowId(intentId);
+	    FlowPath flowPath = new FlowPath();
+	    flowPath.setFlowId(flowId);
+	    sortedFlowPaths.put(flowPath.flowId().value(), flowPath);
+
+	    flowPath.setInstallerId(new CallerId("E"));
+	    flowPath.setFlowEntryActions(null);
+	    flowPath.setFlowPathType(FlowPathType.FP_TYPE_SHORTEST_PATH);
+	    flowPath.setFlowPathUserState(FlowPathUserState.FP_USER_ADD);
+
+	    // Setup the Source and Destination DPID and Port
+	    SwitchPort srcPort = flowPath.dataPath().srcPort();
+	    SwitchPort dstPort = flowPath.dataPath().dstPort();
+	    srcPort.setDpid(new Dpid(spIntent.getSrcSwitchDpid()));
+	    srcPort.setPort(new Port((short)spIntent.getSrcPortNumber()));
+	    dstPort.setDpid(new Dpid(spIntent.getDstSwitchDpid()));
+	    dstPort.setPort(new Port((short)spIntent.getDstPortNumber()));
+
+	    // Extract the Flow Entries
+	    Path path = pathIntent.getPath();
+	    FlowEntry flowEntry;
+	    ArrayList<FlowEntry> flowEntries = new ArrayList<>();
+	    for (LinkEvent linkEvent : path) {
+		Dpid dpid = new Dpid(linkEvent.getSrc().getDpid());
+		flowEntry = new FlowEntry();
+		flowEntry.setDpid(dpid);
+		flowEntries.add(flowEntry);
+	    }
+	    // Add the final Flow Entry
+	    flowEntry = new FlowEntry();
+	    flowEntry.setDpid(new Dpid(spIntent.getDstSwitchDpid()));
+	    flowEntries.add(flowEntry);
+	    flowPath.dataPath().setFlowEntries(flowEntries);
+	}
+
+	// Prepare the return result
+	for (FlowPath flowPath : sortedFlowPaths.values())
+	    result.add(flowPath);
+
+	return result;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/intent/ConstrainedBFSTree.java b/src/main/java/net/onrc/onos/intent/ConstrainedBFSTree.java
index 7f38883..a1edb20 100644
--- a/src/main/java/net/onrc/onos/intent/ConstrainedBFSTree.java
+++ b/src/main/java/net/onrc/onos/intent/ConstrainedBFSTree.java
@@ -60,7 +60,7 @@
 		if (path == null && switchSearched.contains(leafSwitch)) {
 			path = new Path();
 			Long sw = leafSwitch.getDpid();
-			while (sw != rootSwitchDpid) {
+			while (!sw.equals(rootSwitchDpid)) {
 				LinkEvent upstreamLink = upstreamLinks.get(sw);
 				path.add(0, upstreamLink);
 				sw = upstreamLink.getSrc().getDpid();
diff --git a/src/main/java/net/onrc/onos/intent/persist/PersistIntent.java b/src/main/java/net/onrc/onos/intent/persist/PersistIntent.java
index 380a0f2..602762b 100755
--- a/src/main/java/net/onrc/onos/intent/persist/PersistIntent.java
+++ b/src/main/java/net/onrc/onos/intent/persist/PersistIntent.java
@@ -17,6 +17,7 @@
 import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.util.serializers.KryoFactory;
 import net.onrc.onos.registry.controller.IControllerRegistryService;
 import net.onrc.onos.registry.controller.IdBlock;
 import org.slf4j.Logger;
@@ -34,7 +35,7 @@
     private final static String intentJournal = "G:IntentJournal";
     private final static int valueStoreLimit = 1024 * 1024;
     private RCTable table;
-    private Kryo kryo = new Kryo();
+    private Kryo kryo;
     private ByteArrayOutputStream stream;
     private Output output = null;
     private AtomicLong nextId = null;
@@ -48,6 +49,7 @@
         table = RCTable.getTable(intentJournal);
         stream = new ByteArrayOutputStream(1024);
         output = new Output(stream);
+        kryo = (new KryoFactory()).newKryo();
     }
     
     public long getKey() {
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 6ff92db..3de671e 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
@@ -45,6 +45,7 @@
 
 		// TODO optimize locking of NetworkGraph
 		graph.acquireReadLock();
+		log.debug("NetworkGraph: {}", graph.getLinks());
 
 		for (IntentOperation intentOp: intentOpList) {
 			switch (intentOp.operator) {
@@ -62,9 +63,10 @@
 				Switch srcSwitch = graph.getSwitch(spIntent.getSrcSwitchDpid());
 				Switch dstSwitch = graph.getSwitch(spIntent.getDstSwitchDpid());
 				if (srcSwitch == null || dstSwitch == null) {
-					log.error("Switch not found: {}, {}",
+					log.error("Switch not found. src:{}, dst:{}, NetworkGraph:{}",
 							spIntent.getSrcSwitchDpid(),
-							spIntent.getDstSwitchDpid());
+							spIntent.getDstSwitchDpid(),
+							graph.getLinks());
 					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
 							ErrorType.SWITCH_NOT_FOUND,
 							"Switch not found.",
@@ -87,7 +89,7 @@
 				}
 				Path path = tree.getPath(dstSwitch);
 				if (path == null) {
-					log.error("Path not found: {}", spIntent.toString());
+					log.error("Path not found. Intent: {}, NetworkGraph:{}", spIntent.toString(), graph.getLinks());
 					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
 							ErrorType.PATH_NOT_FOUND,
 							"Path not found.",
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 37015cb..1101dc9 100755
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
@@ -84,6 +84,7 @@
 
 	private IEventChannel<Long, IntentOperationList> opEventChannel;
 	private final ReentrantLock lock = new ReentrantLock();
+	private HashSet<LinkEvent> unmatchedLinkEvents = new HashSet<>();
 	private static final String INTENT_OP_EVENT_CHANNEL_NAME = "onos.pathintent";
 	private static final String INTENT_STATE_EVENT_CHANNEL_NAME = "onos.pathintent_state";
 	private static final Logger log = LoggerFactory.getLogger(PathCalcRuntimeModule.class);
@@ -99,8 +100,10 @@
 		IntentOperationList reroutingOperation = new IntentOperationList();
 		for (Intent intent : oldPaths) {
 			PathIntent pathIntent = (PathIntent) intent;
-			if (pathIntent.getState().equals(IntentState.INST_ACK) &&
-					!reroutingOperation.contains(pathIntent)) {
+			if (pathIntent.getId().startsWith("1:F")) // XXX: demo special, the intent start with "F" is skipped
+				continue;
+			if (pathIntent.getState().equals(IntentState.INST_ACK) && // XXX: path intents in flight
+					!reroutingOperation.contains(pathIntent.getParentIntent())) {
 				reroutingOperation.add(Operator.ADD, pathIntent.getParentIntent());
 			}
 		}
@@ -173,6 +176,8 @@
 			for (IntentOperation op : list) {
 				if (op.intent.getState().equals(IntentState.INST_ACK))
 					states.put(op.intent.getId(), IntentState.REROUTE_REQ);
+				if (op.intent.getState().equals(IntentState.CREATED))
+					states.put(op.intent.getId(), IntentState.INST_REQ); // XXX
 			}
 			highLevelIntents.changeStates(states);
 			p.log("end_updateInMemoryIntents");
@@ -207,7 +212,7 @@
 			pathIntents.executeOperations(pathIntentOperations);
 			p.log("end_updateInMemoryPathIntents");
 
-			// Demo special: add a complete path to remove operation
+			// XXX Demo special: add a complete path to remove operation
 			p.log("begin_addPathToRemoveOperation");
 			for (IntentOperation op: pathIntentOperations) {
 				if(op.operator.equals(Operator.REMOVE)) {
@@ -221,9 +226,11 @@
 
 			// send notification
 			p.log("begin_sendNotification");
-			opEventChannel.addEntry(key, pathIntentOperations);
+			// XXX: Send notifications using the same key every time
+			// and receive them by entryAdded() and entryUpdated()
+			opEventChannel.addEntry(0L, pathIntentOperations);
 			p.log("end_sendNotification");
-			opEventChannel.removeEntry(key);
+			//opEventChannel.removeEntry(key);
 			return pathIntentOperations;
 		}
 		finally {
@@ -265,14 +272,39 @@
 		PerfLogger p = new PerfLogger("networkGraphEvents");
 		HashSet<Intent> affectedPaths = new HashSet<>();
 
-		if (addedLinkEvents.size() > 0) { // ||
+		boolean rerouteAll = false;
+		for(LinkEvent le : addedLinkEvents) {
+		    LinkEvent rev = new LinkEvent(le.getDst().getDpid(), le.getDst().getNumber(), le.getSrc().getDpid(), le.getSrc().getNumber());
+		    if(unmatchedLinkEvents.contains(rev)) {
+			rerouteAll = true;
+			unmatchedLinkEvents.remove(rev);
+			log.debug("Found matched LinkEvent: {} {}", rev, le);
+		    }
+		    else {
+			unmatchedLinkEvents.add(le);
+			log.debug("Adding unmatched LinkEvent: {}", le);
+		    }
+		}
+		for(LinkEvent le : removedLinkEvents) {
+		    if (unmatchedLinkEvents.contains(le)) {
+			unmatchedLinkEvents.remove(le);
+			log.debug("Removing LinkEvent: {}", le);
+		    }
+		}
+		if(unmatchedLinkEvents.size() > 0) {
+		    log.debug("Unmatched link events: {} events", unmatchedLinkEvents.size());
+		}
+
+		if ( rerouteAll ) {//addedLinkEvents.size() > 0) { // ||
 //				addedPortEvents.size() > 0 ||
 //				addedSwitchEvents.size() > 0) {
 			p.log("begin_getAllIntents");
 			affectedPaths.addAll(getPathIntents().getAllIntents());
 			p.log("end_getAllIntents");
 		}
-		else {
+		else if (removedSwitchEvents.size() > 0 ||
+			 removedLinkEvents.size() > 0 ||
+			 removedPortEvents.size() > 0) {
 			p.log("begin_getIntentsByLink");
 			for (LinkEvent linkEvent: removedLinkEvents)
 				affectedPaths.addAll(pathIntents.getIntentsByLink(linkEvent));
@@ -288,7 +320,9 @@
 				affectedPaths.addAll(pathIntents.getIntentsByDpid(switchEvent.getDpid()));
 			p.log("end_getIntentsByDpid");
 		}
+		p.log("begin_reroutePaths");
 		reroutePaths(affectedPaths);
+		p.log("end_reroutePaths");
 		p.flushLog();
 	}
 
@@ -314,7 +348,8 @@
 		try {
 			// reflect state changes of path-level intent into application-level intents
 			p.log("begin_changeStateByNotification");
-			IntentStateList parentStates = new IntentStateList();
+			IntentStateList highLevelIntentStates = new IntentStateList();
+			IntentStateList pathIntentStates = new IntentStateList();
 			for (Entry<String, IntentState> entry: value.entrySet()) {
 				PathIntent pathIntent = (PathIntent) pathIntents.getIntent(entry.getKey());
 				if (pathIntent == null) continue;
@@ -327,20 +362,21 @@
 
 				IntentState state = entry.getValue();
 				switch (state) {
-				case INST_REQ:
+				//case INST_REQ:
 				case INST_ACK:
 				case INST_NACK:
-				case DEL_REQ:
+				//case DEL_REQ:
 				case DEL_ACK:
 				case DEL_PENDING:
-					parentStates.put(parentIntent.getId(), state);
+					highLevelIntentStates.put(parentIntent.getId(), state);
+					pathIntentStates.put(entry.getKey(), entry.getValue());
 					break;
 				default:
 					break;
 				}
 			}
-			highLevelIntents.changeStates(parentStates);
-			pathIntents.changeStates(value);
+			highLevelIntents.changeStates(highLevelIntentStates);
+			pathIntents.changeStates(pathIntentStates);
 			p.log("end_changeStateByNotification");
 		}
 		finally {
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 b12711a..5691c53 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
@@ -5,13 +5,11 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.intent.FlowEntry;
 import net.onrc.onos.intent.Intent;
@@ -23,6 +21,9 @@
 import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
 //import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  *
  * @author Brian O'Connor <bocon@onlab.us>
@@ -40,7 +41,7 @@
 
     public List<Set<FlowEntry>> computePlan(IntentOperationList intentOps) {
 	long start = System.nanoTime();
-	Set<Collection<FlowEntry>> flowEntries = computeFlowEntries(intentOps);
+	List<Collection<FlowEntry>> flowEntries = computeFlowEntries(intentOps);
 	long step1 = System.nanoTime();
 	List<Set<FlowEntry>> plan = buildPhases(flowEntries);
 	long step2 = System.nanoTime();
@@ -49,8 +50,8 @@
 	return plan;
     }
 
-    private Set<Collection<FlowEntry>> computeFlowEntries(IntentOperationList intentOps) {
-	Set<Collection<FlowEntry>> flowEntries = new HashSet<>();
+    private List<Collection<FlowEntry>> computeFlowEntries(IntentOperationList intentOps) {
+	List<Collection<FlowEntry>> flowEntries = new LinkedList<>();
 	for(IntentOperation i : intentOps) {
 	    if(!(i.intent instanceof PathIntent)) {
 		log.warn("Not a path intent: {}", i);
@@ -106,7 +107,7 @@
 	return flowEntries;
     }
 
-    private List<Set<FlowEntry>> buildPhases(Set<Collection<FlowEntry>> flowEntries) {
+    private List<Set<FlowEntry>> buildPhases(List<Collection<FlowEntry>> flowEntries) {
 	Map<FlowEntry, Integer> map = new HashMap<>();
 	List<Set<FlowEntry>> plan = new ArrayList<>();
 	for(Collection<FlowEntry> c : flowEntries) {
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java
index b8bed85..293a33d 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java
@@ -43,7 +43,7 @@
     private static final String PATH_INTENT_CHANNEL_NAME = "onos.pathintent";
     private static final String INTENT_STATE_EVENT_CHANNEL_NAME = "onos.pathintent_state";
 
-    
+
     @Override
     public void init(FloodlightModuleContext context)
 	    throws FloodlightModuleException {
@@ -59,27 +59,27 @@
 
     class EventListener extends Thread
     	implements IEventChannelListener<Long, IntentOperationList> {
-	
+
 	private BlockingQueue<IntentOperationList> intentQueue = new LinkedBlockingQueue<>();
 	private Long key = Long.valueOf(0);
-	
+
 	@Override
 	public void run() {
 	    while(true) {
 		try {
 		    IntentOperationList intents = intentQueue.take();
-		    //TODO: consider draining the remaining intent lists 
+		    //TODO: consider draining the remaining intent lists
 		    //      and processing in one big batch
 //		    List<IntentOperationList> remaining = new LinkedList<>();
 //		    intentQueue.drainTo(remaining);
-		    
+
 		    processIntents(intents);
 		} catch (InterruptedException e) {
 		    log.warn("Error taking from intent queue: {}", e.getMessage());
 		}
 	    }
 	}
-	
+
 	private void processIntents(IntentOperationList intents) {
 	    log("start_processIntents");
 	    log.debug("Processing OperationList {}", intents);
@@ -90,13 +90,13 @@
 	    log("begin_installPlan");
 	    boolean success = planInstall.installPlan(plan);
 	    log("end_installPlan");
-	    
+
 	    log("begin_sendInstallNotif");
 	    sendNotifications(intents, true, success);
 	    log("end_sendInstallNotif");
 	    log("finish");
 	}
-	
+
 	private void sendNotifications(IntentOperationList intents, boolean installed, boolean success) {
 	    IntentStateList states = new IntentStateList();
 	    for(IntentOperation i : intents) {
@@ -123,11 +123,23 @@
 		states.put(i.intent.getId(), newState);
 	    }
 	    intentStateChannel.addEntry(key, states);
-	    key += 1;
+	    // XXX: Send notifications using the same key every time
+	    // and receive them by entryAdded() and entryUpdated()
+	    // key += 1;
 	}
-	
+
 	@Override
 	public void entryAdded(IntentOperationList value) {
+	    entryUpdated(value);
+	}
+
+	@Override
+	public void entryRemoved(IntentOperationList value) {
+	    // This channel is a queue, so this method is not needed
+	}
+
+	@Override
+	public void entryUpdated(IntentOperationList value) {
 	    log("start_intentNotifRecv");
 	    log("begin_sendReceivedNotif");
 	    sendNotifications(value, false, false);
@@ -141,36 +153,26 @@
 		log.warn("Error putting to intent queue: {}", e.getMessage());
 	    }
 	}
-
-	@Override
-	public void entryRemoved(IntentOperationList value) {
-	    // This channel is a queue, so this method is not needed
-	}
-
-	@Override
-	public void entryUpdated(IntentOperationList value) {
-	    // This channel is a queue, so this method is not needed
-	}
     }
-    
+
     public static void log(String step) {
 	log.error("Time:{}, Step:{}", System.nanoTime(), step);
     }
-    
+
     @Override
     public void startUp(FloodlightModuleContext context) {
 	// start subscriber
-	datagridService.addListener(PATH_INTENT_CHANNEL_NAME, 
-				    	      eventListener, 
-				              Long.class, 
+	datagridService.addListener(PATH_INTENT_CHANNEL_NAME,
+				    	      eventListener,
+				              Long.class,
 				              IntentOperationList.class);
 	eventListener.start();
 	// start publisher
-	intentStateChannel = datagridService.createChannel(INTENT_STATE_EVENT_CHANNEL_NAME, 
-						Long.class, 
+	intentStateChannel = datagridService.createChannel(INTENT_STATE_EVENT_CHANNEL_NAME,
+						Long.class,
 						IntentStateList.class);
     }
-    
+
     @Override
     public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
 	Collection<Class<? extends IFloodlightService>> l =
@@ -181,7 +183,7 @@
 	l.add(IFlowPusherService.class);
 	return l;
     }
-    
+
     @Override
     public Collection<Class<? extends IFloodlightService>> getModuleServices() {
 	// no services, for now
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
index abd82ed..e8d498f 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
@@ -1,6 +1,7 @@
 package net.onrc.onos.intent.runtime;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -38,15 +39,73 @@
 	this.provider = provider;
 	this.pusher = pusher;
     }
+    
+    private static class FlowModCount {
+	IOFSwitch sw;
+	long modFlows = 0;
+	long delFlows = 0;
+	long errors = 0;
+	
+	FlowModCount(IOFSwitch sw) {
+	    this.sw = sw;
+	}
+	
+	void addFlowEntry(FlowEntry entry) {
+	    switch(entry.getOperator()){
+	    case ADD:
+		modFlows++;
+		break;
+	    case ERROR:
+		errors++;
+		break;
+	    case REMOVE:
+		delFlows++;
+		break;
+	    default:
+		break;
+	    }
+	}
+	
+	public String toString() {
+	    return "sw:" + sw.getStringId() + ": modify " + modFlows + " delete " + delFlows + " error " + errors;
+	}
+	
+	static Map<IOFSwitch, FlowModCount> map = new HashMap<>();
+	static void countFlowEntry(IOFSwitch sw, FlowEntry entry) {
+	    FlowModCount count = map.get(sw);
+	    if(count == null) {
+		count = new FlowModCount(sw);
+		map.put(sw, count);
+	    }
+	    count.addFlowEntry(entry);
+	}
+	static void startCount() {
+	    map.clear();
+	}
+	static void printCount() {
+	    String result = "FLOWMOD COUNT:\n";
+	    for(FlowModCount count : map.values()) {
+		result += count.toString() + '\n';
+	    }
+	    if(map.values().isEmpty()) {
+		result += "No flow mods installed\n";
+	    }
+	    log.error(result);
+	}
+    }
 
     public boolean installPlan(List<Set<FlowEntry>> plan) {
 	long start = System.nanoTime();
 	Map<Long,IOFSwitch> switches = provider.getSwitches();
+	
 	log.debug("IOFSwitches: {}", switches);
+	
+	FlowModCount.startCount();
 	for(Set<FlowEntry> phase : plan) {
 	    Set<Pair<IOFSwitch, net.onrc.onos.ofcontroller.util.FlowEntry>> entries = new HashSet<>();
 	    Set<IOFSwitch> modifiedSwitches = new HashSet<>();
-
+	    
+	    long step1 = System.nanoTime();
 	    // convert flow entries and create pairs
 	    for(FlowEntry entry : phase) {
 		IOFSwitch sw = switches.get(entry.getSwitch());
@@ -55,13 +114,16 @@
 		    log.debug("Skipping flow entry: {}", entry);
 		    continue;
 		}
-		entries.add(new Pair<>(sw, entry.getFlowEntry()));
+		entries.add(new Pair<>(sw, entry.getFlowEntry()));		
 		modifiedSwitches.add(sw);
+		FlowModCount.countFlowEntry(sw, entry);
 	    }
+	    long step2 = System.nanoTime();
 	    
 	    // push flow entries to switches
 	    log.debug("Pushing flow entries: {}", entries);
 	    pusher.pushFlowEntries(entries);
+	    long step3 = System.nanoTime();
 	    
 	    // TODO: insert a barrier after each phase on each modifiedSwitch
 	    // TODO: wait for confirmation messages before proceeding
@@ -78,9 +140,14 @@
 		    log.error("Barrier message not received for sw: {}", sw);
 		}
 	    }
+	    long step4 = System.nanoTime();
+	    log.error("MEASUREMENT: convert: {} ns, push: {} ns, barrierWait: {} ns",
+		    step2 - step1, step3 - step2, step4 - step3);
+
 	}
 	long end = System.nanoTime();
 	log.error("MEASUREMENT: Install plan: {} ns", (end-start));
+	FlowModCount.printCount();
 	
 	// TODO: we assume that the plan installation succeeds for now
 	return true;
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/TopologyManager.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/TopologyManager.java
index 38f164a..3f3af2d 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/TopologyManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/TopologyManager.java
@@ -480,10 +480,12 @@
 	    addDevice(deviceEvent);
     }
 
-    /* ******************************
-     * NetworkGraphDiscoveryInterface methods
-     * ******************************/
-
+    /**
+     * Switch discovered event.
+     *
+     * @param switchEvent the switch event.
+     * @param portEvents the corresponding port events for the switch.
+     */
     @Override
     public void putSwitchDiscoveryEvent(SwitchEvent switchEvent,
 					Collection<PortEvent> portEvents) {
@@ -533,6 +535,11 @@
 	}
     }
 
+    /**
+     * Switch removed event.
+     *
+     * @param switchEvent the switch event.
+     */
     @Override
     public void removeSwitchDiscoveryEvent(SwitchEvent switchEvent) {
 	// Get the old Port Events
@@ -578,6 +585,11 @@
 	}
     }
 
+    /**
+     * Port discovered event.
+     *
+     * @param portEvent the port event.
+     */
     @Override
     public void putPortDiscoveryEvent(PortEvent portEvent) {
 	if (datastore.addPort(portEvent)) {
@@ -598,6 +610,11 @@
 	}
     }
 
+    /**
+     * Port removed event.
+     *
+     * @param portEvent the port event.
+     */
     @Override
     public void removePortDiscoveryEvent(PortEvent portEvent) {
 	if (datastore.deactivatePort(portEvent)) {
@@ -645,6 +662,11 @@
 	}
     }
 
+    /**
+     * Link discovered event.
+     *
+     * @param linkEvent the link event.
+     */
     @Override
     public void putLinkDiscoveryEvent(LinkEvent linkEvent) {
 	if (datastore.addLink(linkEvent)) {
@@ -665,6 +687,11 @@
 	}
     }
 
+    /**
+     * Link removed event.
+     *
+     * @param linkEvent the link event.
+     */
     @Override
     public void removeLinkDiscoveryEvent(LinkEvent linkEvent) {
 	if (datastore.removeLink(linkEvent)) {
@@ -681,6 +708,11 @@
 	}
     }
 
+    /**
+     * Device discovered event.
+     *
+     * @param deviceEvent the device event.
+     */
     @Override
     public void putDeviceDiscoveryEvent(DeviceEvent deviceEvent) {
 	if (datastore.addDevice(deviceEvent)) {
@@ -704,6 +736,11 @@
 	}
     }
 
+    /**
+     * Device removed event.
+     *
+     * @param deviceEvent the device event.
+     */
     @Override
     public void removeDeviceDiscoveryEvent(DeviceEvent deviceEvent) {
 	if (datastore.removeDevice(deviceEvent)) {
@@ -723,9 +760,11 @@
 	}
     }
 
-    /* ************************************************
-     * Internal methods to maintain the network graph
-     * ************************************************/
+    /**
+     * Add a switch to the Network Graph.
+     *
+     * @param switchEvent the Switch Event with the switch to add.
+     */
     private void addSwitch(SwitchEvent switchEvent) {
 	Switch sw = networkGraph.getSwitch(switchEvent.getDpid());
 	if (sw == null) {
@@ -738,6 +777,11 @@
 	apiAddedSwitchEvents.add(switchEvent);
     }
 
+    /**
+     * Remove a switch from the Network Graph.
+     *
+     * @param switchEvent the Switch Event with the switch to remove.
+     */
     private void removeSwitch(SwitchEvent switchEvent) {
 	Switch sw = networkGraph.getSwitch(switchEvent.getDpid());
 	if (sw == null) {
@@ -763,6 +807,11 @@
 	apiRemovedSwitchEvents.add(switchEvent);
     }
 
+    /**
+     * Add a port to the Network Graph.
+     *
+     * @param portEvent the Port Event with the port to add.
+     */
     private void addPort(PortEvent portEvent) {
 	Switch sw = networkGraph.getSwitch(portEvent.getDpid());
 	if (sw == null) {
@@ -783,6 +832,11 @@
 	apiAddedPortEvents.add(portEvent);
     }
 
+    /**
+     * Remove a port from the Network Graph.
+     *
+     * @param portEvent the Port Event with the port to remove.
+     */
     private void removePort(PortEvent portEvent) {
 	Switch sw = networkGraph.getSwitch(portEvent.getDpid());
 	if (sw == null) {
@@ -839,6 +893,11 @@
 	apiRemovedPortEvents.add(portEvent);
     }
 
+    /**
+     * Add a link to the Network Graph.
+     *
+     * @param linkEvent the Link Event with the link to add.
+     */
     private void addLink(LinkEvent linkEvent) {
 	Port srcPort = networkGraph.getPort(linkEvent.getSrc().dpid,
 					    linkEvent.getSrc().number);
@@ -888,6 +947,11 @@
 	apiAddedLinkEvents.add(linkEvent);
     }
 
+    /**
+     * Remove a link from the Network Graph.
+     *
+     * @param linkEvent the Link Event with the link to remove.
+     */
     private void removeLink(LinkEvent linkEvent) {
 	Port srcPort = networkGraph.getPort(linkEvent.getSrc().dpid,
 					    linkEvent.getSrc().number);
@@ -923,7 +987,15 @@
 	apiRemovedLinkEvents.add(linkEvent);
     }
 
-    // TODO: Device-related work is incomplete
+    /**
+     * Add a device to the Network Graph.
+     *
+     * TODO: Device-related work is incomplete.
+     * TODO: Eventually, we might need to consider reordering
+     * or addLink() and addDevice() events on the same port.
+     *
+     * @param deviceEvent the Device Event with the device to add.
+     */
     private void addDevice(DeviceEvent deviceEvent) {
 	Device device = networkGraph.getDeviceByMac(deviceEvent.getMac());
 	if (device == null) {
@@ -969,6 +1041,13 @@
 	}
     }
 
+    /**
+     * Remove a device from the Network Graph.
+     *
+     * TODO: Device-related work is incomplete.
+     *
+     * @param deviceEvent the Device Event with the device to remove.
+     */
     private void removeDevice(DeviceEvent deviceEvent) {
 	Device device = networkGraph.getDeviceByMac(deviceEvent.getMac());
 	if (device == null) {
@@ -997,218 +1076,63 @@
     }
 
     /**
+     * Get the SwitchImpl-casted switch implementation.
      *
-     * @param switchEvent
-     * @return true if ready to accept event.
+     * @param sw the Switch to cast.
+     * @return the SwitchImpl-casted switch implementation.
      */
-    private boolean prepareForAddSwitchEvent(SwitchEvent switchEvent) {
-	// No show stopping precondition
-	return true;
-    }
-
-    private boolean prepareForRemoveSwitchEvent(SwitchEvent switchEvent) {
-	// No show stopping precondition
-	return true;
-    }
-
-    private boolean prepareForAddPortEvent(PortEvent portEvent) {
-	// Parent Switch must exist
-	if (networkGraph.getSwitch(portEvent.getDpid()) == null) {
-	    log.warn("Dropping add port event because switch doesn't exist: {}",
-		     portEvent);
-	    return false;
-	}
-	// Prep: None
-	return true;
-    }
-
-    private boolean prepareForRemovePortEvent(PortEvent portEvent) {
-	Port port = networkGraph.getPort(portEvent.getDpid(),
-					 portEvent.getNumber());
-	if (port == null) {
-	    log.debug("Port already removed? {}", portEvent);
-	    // let it pass
-	    return true;
-	}
-
-	// Prep: Remove Link and Device Attachment
-	ArrayList<DeviceEvent> deviceEvents = new ArrayList<>();
-	for (Device device : port.getDevices()) {
-	    log.debug("Removing Device {} on Port {}", device, portEvent);
-	    DeviceEvent devEvent = new DeviceEvent(device.getMacAddress());
-	    devEvent.addAttachmentPoint(new SwitchPort(port.getSwitch().getDpid(),
-						     port.getNumber()));
-	    deviceEvents.add(devEvent);
-	}
-	for (DeviceEvent devEvent : deviceEvents) {
-	    // calling Discovery API to wipe from DB, etc.
-	    removeDeviceDiscoveryEvent(devEvent);
-	}
-
-	Set<Link> links = new HashSet<>();
-	links.add(port.getOutgoingLink());
-	links.add(port.getIncomingLink());
-	for (Link link : links) {
-	    if (link == null) {
-		continue;
-	    }
-	    log.debug("Removing Link {} on Port {}", link, portEvent);
-	    LinkEvent linkEvent =
-		new LinkEvent(link.getSrcSwitch().getDpid(),
-			      link.getSrcPort().getNumber(),
-			      link.getDstSwitch().getDpid(),
-			      link.getDstPort().getNumber());
-	    // calling Discovery API to wipe from DB, etc.
-
-	    // Call internal remove Link, which will check
-	    // ownership of DST dpid and modify DB only if it is the owner
-	    removeLinkDiscoveryEvent(linkEvent, true);
-	}
-	return true;
-    }
-
-    private boolean prepareForAddLinkEvent(LinkEvent linkEvent) {
-	// Src/Dst Port must exist
-	Port srcPort = networkGraph.getPort(linkEvent.getSrc().dpid,
-					    linkEvent.getSrc().number);
-	Port dstPort = networkGraph.getPort(linkEvent.getDst().dpid,
-					    linkEvent.getDst().number);
-	if (srcPort == null || dstPort == null) {
-	    log.warn("Dropping add link event because port doesn't exist: {}",
-		     linkEvent);
-	    return false;
-	}
-
-	// Prep: remove Device attachment on both Ports
-	ArrayList<DeviceEvent> deviceEvents = new ArrayList<>();
-	for (Device device : srcPort.getDevices()) {
-	    DeviceEvent devEvent = new DeviceEvent(device.getMacAddress());
-	    devEvent.addAttachmentPoint(new SwitchPort(srcPort.getSwitch().getDpid(), srcPort.getNumber()));
-	    deviceEvents.add(devEvent);
-	}
-	for (Device device : dstPort.getDevices()) {
-	    DeviceEvent devEvent = new DeviceEvent(device.getMacAddress());
-	    devEvent.addAttachmentPoint(new SwitchPort(dstPort.getSwitch().getDpid(),
-						     dstPort.getNumber()));
-	    deviceEvents.add(devEvent);
-	}
-	for (DeviceEvent devEvent : deviceEvents) {
-	    // calling Discovery API to wipe from DB, etc.
-	    removeDeviceDiscoveryEvent(devEvent);
-	}
-
-	return true;
-    }
-
-    private boolean prepareForRemoveLinkEvent(LinkEvent linkEvent) {
-	// Src/Dst Port must exist
-	Port srcPort = networkGraph.getPort(linkEvent.getSrc().dpid,
-					    linkEvent.getSrc().number);
-	Port dstPort = networkGraph.getPort(linkEvent.getDst().dpid,
-					    linkEvent.getDst().number);
-	if (srcPort == null || dstPort == null) {
-	    log.warn("Dropping remove link event because port doesn't exist {}", linkEvent);
-	    return false;
-	}
-
-	Link link = srcPort.getOutgoingLink();
-
-	// Link is already gone, or different Link exist in memory
-	// XXX Check if we should reject or just accept these cases.
-	// it should be harmless to remove the Link on event from DB anyways
-	if (link == null ||
-	    !link.getDstPort().getNumber().equals(linkEvent.getDst().number)
-	    || !link.getDstSwitch().getDpid().equals(linkEvent.getDst().dpid)) {
-	    log.warn("Dropping remove link event because link doesn't exist: {}", linkEvent);
-	    return false;
-	}
-	// Prep: None
-	return true;
-    }
-
-    /**
-     *
-     * @param deviceEvent Event will be modified to remove inapplicable attachemntPoints/ipAddress
-     * @return false if this event should be dropped.
-     */
-    private boolean prepareForAddDeviceEvent(DeviceEvent deviceEvent) {
-	boolean preconditionBroken = false;
-	ArrayList<PortEvent.SwitchPort> failedSwitchPort = new ArrayList<>();
-	for ( PortEvent.SwitchPort swp : deviceEvent.getAttachmentPoints() ) {
-	    // Attached Ports must exist
-	    Port port = networkGraph.getPort(swp.dpid, swp.number);
-	    if (port == null) {
-		preconditionBroken = true;
-		failedSwitchPort.add(swp);
-		continue;
-	    }
-	    // Attached Ports must not have Link
-	    if (port.getOutgoingLink() != null ||
-		port.getIncomingLink() != null) {
-		preconditionBroken = true;
-		failedSwitchPort.add(swp);
-		continue;
-	    }
-	}
-
-	// Rewriting event to exclude failed attachmentPoint
-	// XXX Assumption behind this is that inapplicable device event should
-	// be dropped, not deferred. If we decide to defer Device event,
-	// rewriting can become a problem
-	List<SwitchPort>  attachmentPoints = deviceEvent.getAttachmentPoints();
-	attachmentPoints.removeAll(failedSwitchPort);
-	deviceEvent.setAttachmentPoints(attachmentPoints);
-
-	if (deviceEvent.getAttachmentPoints().isEmpty() &&
-	    deviceEvent.getIpAddresses().isEmpty()) {
-	    // return false to represent: Nothing left to do for this event.
-	    // Caller should drop event
-	    return false;
-	}
-
-	// Should we return false to tell caller that the event was trimmed?
-	// if ( preconditionBroken ) {
-	//     return false;
-	// }
-
-	return true;
-    }
-
-    private boolean prepareForRemoveDeviceEvent(DeviceEvent deviceEvent) {
-	// No show stopping precondition?
-	// Prep: none
-	return true;
-    }
-
     private SwitchImpl getSwitchImpl(Switch sw) {
 	if (sw instanceof SwitchImpl) {
-	    return (SwitchImpl) sw;
+	    return (SwitchImpl)sw;
 	}
 	throw new ClassCastException("SwitchImpl expected, but found: " + sw);
     }
 
-    private PortImpl getPortImpl(Port p) {
-	if (p instanceof PortImpl) {
-	    return (PortImpl) p;
+    /**
+     * Get the PortImpl-casted port implementation.
+     *
+     * @param port the Port to cast.
+     * @return the PortImpl-casted port implementation.
+     */
+    private PortImpl getPortImpl(Port port) {
+	if (port instanceof PortImpl) {
+	    return (PortImpl)port;
 	}
-	throw new ClassCastException("PortImpl expected, but found: " + p);
+	throw new ClassCastException("PortImpl expected, but found: " + port);
     }
 
-    private LinkImpl getLinkImpl(Link l) {
-	if (l instanceof LinkImpl) {
-	    return (LinkImpl) l;
+    /**
+     * Get the LinkImpl-casted link implementation.
+     *
+     * @param link the Link to cast.
+     * @return the LinkImpl-casted link implementation.
+     */
+    private LinkImpl getLinkImpl(Link link) {
+	if (link instanceof LinkImpl) {
+	    return (LinkImpl)link;
 	}
-	throw new ClassCastException("LinkImpl expected, but found: " + l);
+	throw new ClassCastException("LinkImpl expected, but found: " + link);
     }
 
-    private DeviceImpl getDeviceImpl(Device d) {
-	if (d instanceof DeviceImpl) {
-	    return (DeviceImpl) d;
+    /**
+     * Get the DeviceImpl-casted device implementation.
+     *
+     * @param device the Device to cast.
+     * @return the DeviceImpl-casted device implementation.
+     */
+    private DeviceImpl getDeviceImpl(Device device) {
+	if (device instanceof DeviceImpl) {
+	    return (DeviceImpl)device;
 	}
-	throw new ClassCastException("DeviceImpl expected, but found: " + d);
+	throw new ClassCastException("DeviceImpl expected, but found: " + device);
     }
 
-    @Deprecated
+    /**
+     * Read the whole topology from the database.
+     *
+     * @return a collection of EventEntry-encapsulated Topology Events for
+     * the whole topology.
+     */
     private Collection<EventEntry<TopologyEvent>> readWholeTopologyFromDB() {
 	Collection<EventEntry<TopologyEvent>> collection =
 	    new LinkedList<EventEntry<TopologyEvent>>();
@@ -1266,25 +1190,4 @@
 
 	return collection;
     }
-
-    @Deprecated
-    private void removeLinkDiscoveryEvent(LinkEvent linkEvent,
-					  boolean dstCheckBeforeDBmodify) {
-	if (prepareForRemoveLinkEvent(linkEvent)) {
-	    if (dstCheckBeforeDBmodify) {
-		// write to DB only if it is owner of the dst dpid
-	    // XXX this will cause link remove events to be dropped
-		// if the dst switch just disconnected
-		if (registryService.hasControl(linkEvent.getDst().dpid)) {
-		    datastore.removeLink(linkEvent);
-		}
-	    } else {
-		datastore.removeLink(linkEvent);
-	    }
-	    removeLink(linkEvent);
-	    // Send out notification
-	    eventChannel.removeEntry(linkEvent.getID());
-	}
-	// TODO handle invariant violation
-    }
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphShortestPathResource.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphShortestPathResource.java
new file mode 100644
index 0000000..43e5806
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphShortestPathResource.java
@@ -0,0 +1,81 @@
+package net.onrc.onos.ofcontroller.networkgraph.web;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.onrc.onos.intent.ConstrainedBFSTree;
+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 net.onrc.onos.ofcontroller.networkgraph.Path;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+import net.onrc.onos.ofcontroller.networkgraph.serializers.LinkSerializer;
+import net.onrc.onos.ofcontroller.util.Dpid;
+
+import org.codehaus.jackson.Version;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.module.SimpleModule;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetworkGraphShortestPathResource extends ServerResource {
+
+    private static final Logger log = LoggerFactory.getLogger(NetworkGraphShortestPathResource.class);
+
+    @Get("json")
+    public String retrieve() {
+	INetworkGraphService networkGraphService =
+	    (INetworkGraphService)getContext().getAttributes().
+	    get(INetworkGraphService.class.getCanonicalName());
+
+	NetworkGraph graph = networkGraphService.getNetworkGraph();
+
+	ObjectMapper mapper = new ObjectMapper();
+	SimpleModule module = new SimpleModule("module", new Version(1, 0, 0, null));
+	module.addSerializer(new LinkSerializer());
+	mapper.registerModule(module);
+
+	//
+	// Fetch the attributes
+	//
+	String srcDpidStr = (String)getRequestAttributes().get("src-dpid");
+	String dstDpidStr = (String)getRequestAttributes().get("dst-dpid");
+	Dpid srcDpid = new Dpid(srcDpidStr);
+	Dpid dstDpid = new Dpid(dstDpidStr);
+	log.debug("Getting Shortest Path {}--{}", srcDpidStr, dstDpidStr);
+
+	//
+	// Do the Shortest Path computation and return the result: list of
+	// links.
+	//
+	try {
+	    graph.acquireReadLock();
+	    Switch srcSwitch = graph.getSwitch(srcDpid.value());
+	    Switch dstSwitch = graph.getSwitch(dstDpid.value());
+	    if ((srcSwitch == null) || (dstSwitch == null))
+		return "";
+	    ConstrainedBFSTree bfsTree = new ConstrainedBFSTree(srcSwitch);
+	    Path path = bfsTree.getPath(dstSwitch);
+	    List<Link> links = new LinkedList<>();
+	    for (LinkEvent linkEvent : path) {
+		Link link = graph.getLink(linkEvent.getSrc().getDpid(),
+					  linkEvent.getSrc().getNumber(),
+					  linkEvent.getDst().getDpid(),
+					  linkEvent.getDst().getNumber());
+		if (link == null)
+		    return "";
+		links.add(link);
+	    }
+	    return mapper.writeValueAsString(links);
+	} catch (IOException e) {
+	    log.error("Error writing Shortest Path to JSON", e);
+	    return "";
+	} finally {
+	    graph.releaseReadLock();
+	}
+    }
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphWebRoutable.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphWebRoutable.java
index 6a7ab99..e245801 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphWebRoutable.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/web/NetworkGraphWebRoutable.java
@@ -16,6 +16,7 @@
 		router.attach("/rc/ports/json", RamcloudPortsResource.class);
 		router.attach("/ng/switches/json", NetworkGraphSwitchesResource.class);
 		router.attach("/ng/links/json", NetworkGraphLinksResource.class);
+		router.attach("/ng/shortest-path/{src-dpid}/{dst-dpid}/json", NetworkGraphShortestPathResource.class);
 		return router;
 	}
 
diff --git a/src/main/java/net/onrc/onos/ofcontroller/util/serializers/KryoFactory.java b/src/main/java/net/onrc/onos/ofcontroller/util/serializers/KryoFactory.java
index df93614..f020a8f 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/util/serializers/KryoFactory.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/util/serializers/KryoFactory.java
@@ -178,16 +178,17 @@
 	kryo.register(TopologyEvent.class);
 
 	// Intent-related classes
-	kryo.register(IntentOperationList.class);
-	kryo.register(IntentOperation.class);
-	kryo.register(PathIntent.class);
+	kryo.register(Path.class);
 	kryo.register(Intent.class);
-	kryo.register(ErrorIntent.class);
+	kryo.register(Intent.IntentState.class);
+	kryo.register(PathIntent.class);
 	kryo.register(ShortestPathIntent.class);
 	kryo.register(ConstrainedShortestPathIntent.class);
-	kryo.register(Intent.IntentState.class);
-	kryo.register(Path.class);
+	kryo.register(ErrorIntent.class);
+	kryo.register(ErrorIntent.ErrorType.class);
+	kryo.register(IntentOperation.class);
 	kryo.register(IntentOperation.Operator.class);
+	kryo.register(IntentOperationList.class);
 	kryo.register(IntentStateList.class);
 
 	return kryo;
diff --git a/src/main/resources/hazelcast.xml b/src/main/resources/hazelcast.xml
deleted file mode 120000
index f8f4972..0000000
--- a/src/main/resources/hazelcast.xml
+++ /dev/null
@@ -1 +0,0 @@
-/home/mininet/ONOS/conf/hazelcast.xml
\ No newline at end of file
diff --git a/src/test/java/net/onrc/onos/intent/ConstrainedBFSTreeTest.java b/src/test/java/net/onrc/onos/intent/ConstrainedBFSTreeTest.java
new file mode 100644
index 0000000..f4413b8
--- /dev/null
+++ b/src/test/java/net/onrc/onos/intent/ConstrainedBFSTreeTest.java
@@ -0,0 +1,144 @@
+package net.onrc.onos.intent;
+
+import static org.junit.Assert.*;
+import net.onrc.onos.intent.IntentOperation.Operator;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.Path;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class ConstrainedBFSTreeTest {
+	static long LOCAL_PORT = 0xFFFEL;
+
+	@Before
+	public void setUp() throws Exception {
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void testCreate() {
+		MockNetworkGraph graph = new MockNetworkGraph();
+		graph.createSampleTopology1();
+		ConstrainedBFSTree tree = new ConstrainedBFSTree(graph.getSwitch(1L));
+		assertNotNull(tree);
+	}
+
+	@Test
+	public void testCreateConstrained() {
+		MockNetworkGraph graph = new MockNetworkGraph();
+		graph.createSampleTopology1();
+		PathIntentMap intents = new PathIntentMap();
+		ConstrainedBFSTree tree = new ConstrainedBFSTree(graph.getSwitch(1L), intents, 1000.0);
+		assertNotNull(tree);
+	}
+
+	@Test
+	public void testGetPath() {
+		MockNetworkGraph graph = new MockNetworkGraph();
+		graph.createSampleTopology1();
+		ConstrainedBFSTree tree = new ConstrainedBFSTree(graph.getSwitch(1L));
+		Path path11 = tree.getPath(graph.getSwitch(1L));
+		Path path12 = tree.getPath(graph.getSwitch(2L));
+		Path path13 = tree.getPath(graph.getSwitch(3L));
+		Path path14 = tree.getPath(graph.getSwitch(4L));
+
+		assertNotNull(path11);
+		assertEquals(0, path11.size());
+
+		assertNotNull(path12);
+		assertEquals(1, path12.size());
+		assertEquals(new LinkEvent(graph.getLink(1L, 12L)), path12.get(0));
+
+		assertNotNull(path13);
+		assertEquals(2, path13.size());
+		if (path13.get(0).getDst().getDpid() == 2L) {
+			assertEquals(new LinkEvent(graph.getLink(1L, 12L)), path13.get(0));
+			assertEquals(new LinkEvent(graph.getLink(2L, 23L)), path13.get(1));
+		}
+		else {
+			assertEquals(new LinkEvent(graph.getLink(1L, 14L)), path13.get(0));
+			assertEquals(new LinkEvent(graph.getLink(4L, 43L)), path13.get(1));
+		}
+
+		assertNotNull(path14);
+		assertEquals(1, path14.size());
+		assertEquals(new LinkEvent(graph.getLink(1L, 14L)), path14.get(0));
+	}
+
+	@Test
+	public void testGetPathNull() {
+		MockNetworkGraph graph = new MockNetworkGraph();
+		graph.createSampleTopology1();
+		graph.removeLink(1L, 12L, 2L, 21L);
+		graph.removeLink(1L, 14L, 4L, 41L);
+		// now, there is no path from switch 1, but to switch1
+
+		ConstrainedBFSTree tree1 = new ConstrainedBFSTree(graph.getSwitch(1L));
+		Path path12 = tree1.getPath(graph.getSwitch(2L));
+		Path path13 = tree1.getPath(graph.getSwitch(3L));
+		Path path14 = tree1.getPath(graph.getSwitch(4L));
+
+		ConstrainedBFSTree tree2 = new ConstrainedBFSTree(graph.getSwitch(2L));
+		Path path21 = tree2.getPath(graph.getSwitch(1L));
+
+		assertNull(path12);
+		assertNull(path13);
+		assertNull(path14);
+		assertNotNull(path21);
+		assertEquals(1, path21.size());
+		assertEquals(new LinkEvent(graph.getLink(2L, 21L)), path21.get(0));
+	}
+
+	@Test
+	public void testGetConstrainedPath() {
+		MockNetworkGraph graph = new MockNetworkGraph();
+		graph.createSampleTopology1();
+		PathIntentMap intents = new PathIntentMap();
+		IntentOperationList intentOps = new IntentOperationList();
+
+		// create constrained shortest path intents that have the same source destination ports
+		ConstrainedShortestPathIntent intent1 = new ConstrainedShortestPathIntent(
+				"1", 1L, LOCAL_PORT, 0x111L, 2L, LOCAL_PORT, 0x222L, 600.0);
+		ConstrainedShortestPathIntent intent2 = new ConstrainedShortestPathIntent(
+				"2", 1L, LOCAL_PORT, 0x333L, 2L, LOCAL_PORT, 0x444L, 600.0);
+
+		// calculate path of the intent1
+		ConstrainedBFSTree tree = new ConstrainedBFSTree(graph.getSwitch(1L), intents, 600.0);
+		Path path1 = tree.getPath(graph.getSwitch(2L));
+
+		assertNotNull(path1);
+		assertEquals(1, path1.size());
+		assertEquals(new LinkEvent(graph.getLink(1L, 12L)), path1.get(0));
+
+		PathIntent pathIntent1 = new PathIntent("pi1", path1, 600.0, intent1);
+		intentOps.add(Operator.ADD, pathIntent1);
+		intents.executeOperations(intentOps);
+
+		// calculate path of the intent2
+		tree = new ConstrainedBFSTree(graph.getSwitch(1L), intents, 600.0);
+		Path path2 = tree.getPath(graph.getSwitch(2L));
+
+		assertNotNull(path2);
+		assertEquals(2, path2.size());
+		assertEquals(new LinkEvent(graph.getLink(1L, 14L)), path2.get(0));
+		assertEquals(new LinkEvent(graph.getLink(4L, 42L)), path2.get(1));
+
+		PathIntent pathIntent2 = new PathIntent("pi2", path2, 600.0, intent2);
+		intentOps.add(Operator.ADD, pathIntent2);
+		intents.executeOperations(intentOps);
+
+		// calculate path of the intent3
+		tree = new ConstrainedBFSTree(graph.getSwitch(1L), intents, 600.0);
+		Path path3 = tree.getPath(graph.getSwitch(2L));
+
+		assertNull(path3);
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/net/onrc/onos/intent/ConstrainedShortestPathIntentTest.java b/src/test/java/net/onrc/onos/intent/ConstrainedShortestPathIntentTest.java
index aba4c11..99fa0b3 100644
--- a/src/test/java/net/onrc/onos/intent/ConstrainedShortestPathIntentTest.java
+++ b/src/test/java/net/onrc/onos/intent/ConstrainedShortestPathIntentTest.java
@@ -1,6 +1,7 @@
 package net.onrc.onos.intent;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import net.onrc.onos.ofcontroller.util.serializers.KryoFactory;
 
 import org.junit.After;
 import org.junit.Before;
@@ -23,21 +24,36 @@
 	}
 
 	@Test
-	public void test() {
-		Kryo kryo = new Kryo();
-		Output output = new Output(1024);
-
+	public void testCreate() {
 		ConstrainedShortestPathIntent intent1 =
 				new ConstrainedShortestPathIntent("1", 2L, 3L, 4L, 5L, 6L, 7L, 1000.0);
 
+		assertEquals("1", intent1.getId());
+		assertEquals(2L, intent1.getSrcSwitchDpid());
+		assertEquals(3L, intent1.getSrcPortNumber());
+		assertEquals(4L, intent1.getSrcMac());
+		assertEquals(5L, intent1.getDstSwitchDpid());
+		assertEquals(6L, intent1.getDstPortNumber());
+		assertEquals(7L, intent1.getDstMac());
+		assertEquals(1000.0, intent1.getBandwidth(), 0.0);
+	}
+
+	@Test
+	public void testKryo() {
+		KryoFactory factory = new KryoFactory();
+		Kryo kryo = factory.newKryo();
+		Output output = new Output(1000);
+
+		ConstrainedShortestPathIntent intent1 =
+				new ConstrainedShortestPathIntent("1", 2L, 3L, 4L, 5L, 6L, 7L, 1000.0);
 		kryo.writeObject(output, intent1);
+
 		output.close();
+		byte bytes[] = output.toBytes();
 
-		Input input = new Input(output.toBytes());
-		ConstrainedShortestPathIntent intent2 =
-				kryo.readObject(input, ConstrainedShortestPathIntent.class);
+		Input input = new Input(bytes);
+		ConstrainedShortestPathIntent intent2 = kryo.readObject(input, ConstrainedShortestPathIntent.class);
 		input.close();
-
 		assertEquals("1", intent2.getId());
 		assertEquals(2L, intent2.getSrcSwitchDpid());
 		assertEquals(3L, intent2.getSrcPortNumber());
diff --git a/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java b/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
index 5ae7b13..9d85e6f 100644
--- a/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
+++ b/src/test/java/net/onrc/onos/intent/MockNetworkGraph.java
@@ -9,9 +9,15 @@
 import net.onrc.onos.ofcontroller.networkgraph.SwitchImpl;
 
 /**
+ * A mock class of NetworkGraph.
+ * This class should be used only by test codes.
+ *
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public class MockNetworkGraph extends NetworkGraphImpl {
+	public static Long LOCAL_PORT = 0xFFFEL;
+	public SwitchImpl sw1, sw2, sw3, sw4;
+
 	class DetachableLinkImpl extends LinkImpl {
 		public DetachableLinkImpl(NetworkGraph graph, Port srcPort, Port dstPort) {
 			super(graph, srcPort, dstPort);
@@ -25,7 +31,6 @@
 		SwitchImpl sw = new SwitchImpl(this, switchId);
 		this.putSwitch(sw);
 		return sw;
-
 	}
 
 	public Link addLink(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
@@ -43,34 +48,41 @@
 		return links;
 	}
 
-	public void createSampleTopology() {
-		// add 10 switches (24 ports switch)
-		for (Long dpid=1L; dpid<10L; dpid++) {
-			SwitchImpl sw = (SwitchImpl) addSwitch(dpid);
-			for (Long j=1L; j<=24L; j++) {
-				sw.addPort(j);
-			}
-		}
+	/**
+	 * create sample topology of 4 switches and 5 bidirectional links.
+	 * <pre>
+	 * [1] --- [2]
+	 *  |    /  |
+	 *  |  /    |
+	 * [4] --- [3]
+	 * </pre>
+	 */
+	public void createSampleTopology1() {
+		sw1 = (SwitchImpl) addSwitch(1L);
+		sw1.addPort(LOCAL_PORT);
+		sw2 = (SwitchImpl) addSwitch(2L);
+		sw2.addPort(LOCAL_PORT);
+		sw3 = (SwitchImpl) addSwitch(3L);
+		sw3.addPort(LOCAL_PORT);
+		sw4 = (SwitchImpl) addSwitch(4L);
+		sw4.addPort(LOCAL_PORT);
 
-		// add loop path
-		addBidirectionalLinks(1L, 1L, 2L, 2L);
-		addBidirectionalLinks(2L, 1L, 3L, 2L);
-		addBidirectionalLinks(3L, 1L, 4L, 2L);
-		addBidirectionalLinks(4L, 1L, 5L, 2L);
-		addBidirectionalLinks(5L, 1L, 6L, 2L);
-		addBidirectionalLinks(6L, 1L, 7L, 2L);
-		addBidirectionalLinks(7L, 1L, 8L, 2L);
-		addBidirectionalLinks(8L, 1L, 9L, 2L);
-		addBidirectionalLinks(9L, 1L, 1L, 2L);
+		sw1.addPort(12L); // sw1 -> sw2
+		sw1.addPort(14L); // sw1 -> sw4
+		sw2.addPort(21L); // sw2 -> sw1
+		sw2.addPort(23L); // sw2 -> sw3
+		sw2.addPort(24L); // sw2 -> sw4
+		sw3.addPort(32L); // sw3 -> sw2
+		sw3.addPort(34L); // sw3 -> sw4
+		sw4.addPort(41L); // sw4 -> sw1
+		sw4.addPort(42L); // sw4 -> sw2
+		sw4.addPort(43L); // sw4 -> sw3
 
-		// add other links
-		addBidirectionalLinks(1L, 3L, 5L, 3L);
-		addBidirectionalLinks(2L, 4L, 5L, 4L);
-		addBidirectionalLinks(2L, 5L, 7L, 5L);
-		addBidirectionalLinks(3L, 6L, 7L, 6L);
-		addBidirectionalLinks(3L, 7L, 8L, 7L);
-		addBidirectionalLinks(3L, 8L, 9L, 8L);
-		addBidirectionalLinks(4L, 9l, 9L, 9L);
+		addBidirectionalLinks(1L, 12L, 2L, 21L);
+		addBidirectionalLinks(2L, 23L, 3L, 32L);
+		addBidirectionalLinks(3L, 34L, 4L, 43L);
+		addBidirectionalLinks(4L, 41L, 1L, 14L);
+		addBidirectionalLinks(2L, 24L, 4L, 42L);
 
 		// set capacity of all links to 1000Mbps
 		for (Link link: getLinks()) {
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 0c1746f..f24fc99 100755
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -54,11 +54,13 @@
 	@SuppressWarnings("rawtypes")
 	private IEventChannel eventChannel;
 
+	private static Long LOCAL_PORT = 0xFFFEL;
+
 	@SuppressWarnings("unchecked")
 	@Before
 	public void setUp() throws Exception {
 		MockNetworkGraph graph = new MockNetworkGraph();
-		graph.createSampleTopology();
+		graph.createSampleTopology1();
 		g = graph;
 
 		datagridService = createMock(IDatagridService.class);
@@ -124,9 +126,9 @@
 	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));
-		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));
+		opList.add(Operator.ADD, new ShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 2L, 23L, LOCAL_PORT, 3L, 32L, LOCAL_PORT));
 
 		// compile high-level intent operations into low-level intent operations (calculate paths)
 		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
@@ -147,11 +149,11 @@
 	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));
-		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 18L, 400.0));
-		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 19L, 400.0));
-		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("4", 3L, 20L, 4L, 8L, 20L, 20L, 400.0));
-		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 21L, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("3", 2L, 24L, LOCAL_PORT, 4L, 42L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("4", 2L, 23L, LOCAL_PORT, 3L, 32L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 3L, 34L, LOCAL_PORT, 4L, 43L, LOCAL_PORT, 400.0));
 
 		// compile high-level intent operations into low-level intent operations (calculate paths)
 		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
@@ -172,11 +174,11 @@
 	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));
-		opList.add(Operator.ADD, new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 8L));
-		opList.add(Operator.ADD, new ShortestPathIntent("4", 4L, 20L, 4L, 8L, 20L, 9L));
-		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 10L, 600.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L, LOCAL_PORT, 400.0));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 2L, 24L, LOCAL_PORT, 4L, 42L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ShortestPathIntent("4", 2L, 23L, LOCAL_PORT, 3L, 32L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 3L, 34L, LOCAL_PORT, 4L, 43L, LOCAL_PORT, 400.0));
 
 		// compile high-level intent operations into low-level intent operations (calculate paths)
 		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
@@ -206,9 +208,9 @@
 
 		// 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));
+		opList.add(Operator.ADD, new ShortestPathIntent("1", 1L, 12L, LOCAL_PORT, 2L, 21L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ShortestPathIntent("2", 1L, 14L, LOCAL_PORT, 4L, 41L, LOCAL_PORT));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 2L, 23L, LOCAL_PORT, 3L, 32L, LOCAL_PORT));
 
 		// compile high-level intent operations into low-level intent operations (calculate paths)
 		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
@@ -237,10 +239,10 @@
 		runtime1.getPathIntents().changeStates(states);
 
 		// link down
-		((MockNetworkGraph)g).removeLink(1L, 2L, 9L, 1L); // This link is used by the intent "1"
-		((MockNetworkGraph)g).removeLink(9L, 1L, 1L, 2L);
-		LinkEvent linkEvent1 = new LinkEvent(1L, 2L, 9L, 1L);
-		LinkEvent linkEvent2 = new LinkEvent(9L, 1L, 1L, 2L);
+		((MockNetworkGraph)g).removeLink(1L, 12L, 2L, 21L); // This link is used by the intent "1"
+		((MockNetworkGraph)g).removeLink(2L, 21L, 1L, 12L);
+		LinkEvent linkEvent1 = new LinkEvent(1L, 12L, 2L, 21L);
+		LinkEvent linkEvent2 = new LinkEvent(2L, 21L, 1L, 12L);
 		removedLinkEvents.clear();
 		removedLinkEvents.add(linkEvent1);
 		removedLinkEvents.add(linkEvent2);
diff --git a/start-rest.sh b/start-rest.sh
index 94bbd46..a79a415 100755
--- a/start-rest.sh
+++ b/start-rest.sh
@@ -1,13 +1,13 @@
 #! /bin/bash
 
 # Change this accordingly
-ONOSDIR=${HOME}/ONOS
+ONOSDIR=${ONOS_HOME:-${HOME}/ONOS}
 script_name="topology_rest.py"
 
 #######################
 WEBDIR=${ONOSDIR}/web
 restscript=${WEBDIR}/$script_name
-LOGDIR=${ONOSDIR}/onos-logs
+LOGDIR=${ONOS_LOGDIR:-${ONOSDIR}/onos-logs}
 REST_LOG="${LOGDIR}/rest.`hostname`.log"
 #######################
 
diff --git a/web/get_datagrid_ngevents.py b/web/get_datagrid_ngevents.py
index 34c3338..f7ef7d4 100755
--- a/web/get_datagrid_ngevents.py
+++ b/web/get_datagrid_ngevents.py
@@ -33,7 +33,7 @@
 # Sample output:
 
 def print_datagrid_map(parsedResult):
-  print '%s' % (parsedResult)
+  print '%s' % (parsedResult),
 
 def get_datagrid_map():
   try:
diff --git a/web/get_network_graph.py b/web/get_network_graph.py
new file mode 100755
index 0000000..375e5ff
--- /dev/null
+++ b/web/get_network_graph.py
@@ -0,0 +1,149 @@
+#! /usr/bin/env python
+# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
+
+#
+# Get Network Graph Information:
+#  - Switches
+#  - Links
+#  - Shortest Path
+#
+
+import pprint
+import os
+import sys
+import subprocess
+import json
+import collections
+import argparse
+import io
+import time
+
+from flask import Flask, json, Response, render_template, make_response, request
+
+## Global Var ##
+ControllerIP="127.0.0.1"
+ControllerPort=8080
+
+DEBUG=0
+pp = pprint.PrettyPrinter(indent=4)
+
+app = Flask(__name__)
+
+## Worker Functions ##
+def log_error(txt):
+  print '%s' % (txt)
+
+def debug(txt):
+  if DEBUG:
+    print '%s' % (txt)
+
+# @app.route("/wm/onos/ng/links/json ")
+# @app.route("/wm/onos/ng/switches/json ")
+# @app.route("/wm/onos/ng/shortest-path/<src-dpid>/<dst-dpid>/json ")
+# Sample output:
+
+def print_parsed_result(parsedResult):
+  print '%s' % (parsedResult),
+
+def get_network_switches():
+  try:
+    command = "curl -s \"http://%s:%s/wm/onos/ng/switches/json\"" % (ControllerIP, ControllerPort)
+    debug("get_network_switches %s" % command)
+
+    result = os.popen(command).read()
+    debug("result %s" % result)
+    if len(result) == 0:
+      print "No Switches found"
+      return;
+
+    # parsedResult = result
+    # parsedResult = json.loads(result)
+    parsedResult = json.dumps(json.loads(result), indent=4)
+    debug("parsed %s" % parsedResult)
+  except:
+    log_error("Controller IF has issue")
+    exit(1)
+
+  print_parsed_result(parsedResult)
+
+def get_network_links():
+  try:
+    command = "curl -s \"http://%s:%s/wm/onos/ng/links/json\"" % (ControllerIP, ControllerPort)
+    debug("get_network_links %s" % command)
+
+    result = os.popen(command).read()
+    debug("result %s" % result)
+    if len(result) == 0:
+      print "No Links found"
+      return;
+
+    # parsedResult = result
+    # parsedResult = json.loads(result)
+    parsedResult = json.dumps(json.loads(result), indent=4)
+    debug("parsed %s" % parsedResult)
+  except:
+    log_error("Controller IF has issue")
+    exit(1)
+
+  print_parsed_result(parsedResult)
+
+def get_network_shortest_path(src_dpid, dst_dpid):
+  try:
+    command = "curl -s \"http://%s:%s/wm/onos/ng/shortest-path/%s/%s/json\"" % (ControllerIP, ControllerPort, src_dpid, dst_dpid)
+    debug("get_network_switches %s" % command)
+
+    result = os.popen(command).read()
+    debug("result %s" % result)
+    if len(result) == 0:
+      print "No Path found"
+      return;
+
+    # parsedResult = result
+    parsedResult = json.loads(result, object_pairs_hook=collections.OrderedDict)
+    parsedResult = json.dumps(parsedResult, indent=4)
+    debug("parsed %s" % parsedResult)
+  except:
+    log_error("Controller IF has issue")
+    exit(1)
+
+  print_parsed_result(parsedResult)
+
+
+if __name__ == "__main__":
+  usage_msg1 = "Usage:\n"
+  usage_msg2 = "%s <arguments> : Print network information\n" % (sys.argv[0])
+  usage_msg3 = "  Valid element names:\n"
+  usage_msg4 = "    all              : Print all network elements\n"
+  usage_msg5 = "    switches         : Print all switches and ports\n"
+  usage_msg6 = "    links            : Print all links\n"
+  usage_msg7 = "    shortest-path <src-dpid> <dst-dpid> : Print shortest-path\n"
+  usage_msg8 = "                                      (links between <src-dpid> and <dst-dpid>)\n"
+  usage_msg = usage_msg1 + usage_msg2 + usage_msg3 + usage_msg4 + usage_msg5
+  usage_msg = usage_msg + usage_msg6 + usage_msg7 + usage_msg8
+
+  # Usage info
+  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
+    print(usage_msg)
+    exit(0)
+
+  # Check arguments
+  if len(sys.argv) < 2:
+    log_error(usage_msg)
+    exit(1)
+
+  if (sys.argv[1] != "all" and sys.argv[1] != "switches" and sys.argv[1] != "links" and sys.argv[1] != "shortest-path"):
+    log_error(usage_msg)
+    exit(1)
+
+  if (sys.argv[1] == "shortest-path"):
+    if len(sys.argv) < 4:
+      log_error(usage_msg)
+      exit(1)
+
+  # Do the work
+  if (sys.argv[1] == "all" or sys.argv[1] == "switches"):
+    get_network_switches()
+  if (sys.argv[1] == "all" or sys.argv[1] == "links"):
+    get_network_links()
+  if (sys.argv[1] == "shortest-path"):
+    get_network_shortest_path(sys.argv[2], sys.argv[3])
diff --git a/web/rest-intent/intent-installer.rb b/web/rest-intent/intent-installer.rb
index b2d21d8..41361be 100755
--- a/web/rest-intent/intent-installer.rb
+++ b/web/rest-intent/intent-installer.rb
@@ -72,6 +72,9 @@
   opts.on('b', '--bulk_limit bulk_limit', 'bulk request upto this limit') do |bulk_limit|
     options[:bulk_limit] = bulk_limit
   end
+  opts.on('f', '--freeze_intents', 'freeze dont reroute intents') do
+    options[:freeze] = "F"
+  end
   opts.on('-h', '--help', 'Display help') do
     puts opts
     exit
@@ -173,13 +176,15 @@
     @port ||= 8080
     @bulk_limit = options[:bulk_limit]
     @bulk_limit ||= 10000
+    @freeze = options[:freeze]
+    @freeze ||= ""
   end
 
 
   def _create_intent json_intents
     (0...@max_switches).each do |idx|
       intent = {
-        :intent_id => "#{@intent_id}",
+        :intent_id => "#{@freeze}#{@intent_id}",
         :intent_type => @intent_type,
         :intent_op => @intent_op,
         :srcSwitch => "258".to_s,
diff --git a/web/topology_rest_napi.py b/web/topology_rest_napi.py
index 45fc814..e16c197 100755
--- a/web/topology_rest_napi.py
+++ b/web/topology_rest_napi.py
@@ -5,6 +5,7 @@
 from flask import Flask, json, Response, render_template, make_response, request
 
 ## Global Var for ON.Lab local REST ##
+# The GUI can be accessed at <this_host>:9000/onos-topology.html
 RestIP="localhost"
 RestPort=8080
 ONOS_DEFAULT_HOST="localhost" ;# Has to set if LB=False