Update rerouting features

- add REROUTE_REQ state to Intent class
- add static methods to PathIntent class to create IDs
- add getPathIntentId() to ShortestPathIntent class to retrieve low-level intent
- add mapping of inserting or replacing high level ids to lower level ids.
- change high level intent state to REROUTE_REQ if the state was INST_ACK and the intent was requested rerouting

Change-Id: I50970a0255e2b19451aaedb7dc3c19015031e5d0
diff --git a/src/main/java/net/onrc/onos/intent/ErrorIntent.java b/src/main/java/net/onrc/onos/intent/ErrorIntent.java
index 3365350..27daaa3 100644
--- a/src/main/java/net/onrc/onos/intent/ErrorIntent.java
+++ b/src/main/java/net/onrc/onos/intent/ErrorIntent.java
@@ -22,7 +22,7 @@
 	}
 
 	public ErrorIntent(ErrorType errorType, String message, Intent parentIntent) {
-		this.id = parentIntent.getId();
+		super(parentIntent.getId());
 		this.errorType = errorType;
 		this.message = message;
 		this.parentIntent = parentIntent;
diff --git a/src/main/java/net/onrc/onos/intent/Intent.java b/src/main/java/net/onrc/onos/intent/Intent.java
index 20088bd..9a40325 100644
--- a/src/main/java/net/onrc/onos/intent/Intent.java
+++ b/src/main/java/net/onrc/onos/intent/Intent.java
@@ -12,10 +12,11 @@
 		DEL_REQ,
 		DEL_PENDING,
 		DEL_ACK,
+		REROUTE_REQ,
 	}
 
-	protected String id;
-	protected IntentState state = IntentState.CREATED;
+	private String id;
+	private IntentState state = IntentState.CREATED;
 
 	/**
 	 * Default constructor for Kryo deserialization
@@ -48,7 +49,22 @@
 
 	@Override
 	public int hashCode() {
-		return id.hashCode();
+		return (id == null) ? 0 : id.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if ((obj == null) || (getClass() != obj.getClass()))
+			return false;
+		Intent other = (Intent) obj;
+		if (id == null) {
+			if (other.id != null)
+				return false;
+		} else if (!id.equals(other.id))
+			return false;
+		return true;
 	}
 
 	@Override
diff --git a/src/main/java/net/onrc/onos/intent/IntentOperation.java b/src/main/java/net/onrc/onos/intent/IntentOperation.java
index 93a9f88..57d2667 100644
--- a/src/main/java/net/onrc/onos/intent/IntentOperation.java
+++ b/src/main/java/net/onrc/onos/intent/IntentOperation.java
@@ -23,16 +23,16 @@
 		ERROR,
 	}
 
-	public IntentOperation() {}
+	public Operator operator;
+	public Intent intent;
+
+	protected IntentOperation() {}
 
 	public IntentOperation(Operator operator, Intent intent) {
 		this.operator = operator;
 		this.intent = intent;
 	}
 
-	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 e000828..28e2dd3 100644
--- a/src/main/java/net/onrc/onos/intent/PathIntent.java
+++ b/src/main/java/net/onrc/onos/intent/PathIntent.java
@@ -10,6 +10,15 @@
 	protected double bandwidth;
 	protected Intent parentIntent;
 
+	public static String createFirstId(String parentId) {
+		return String.format("pi%s___0", parentId);
+	}
+
+	public static String createNextId(String currentId) {
+		String parts[] = currentId.split("___");
+		return String.format("%s___%d", parts[0], Long.valueOf(parts[1])+1);
+	}
+
 	/**
 	 * Default constructor for Kryo deserialization
 	 */
diff --git a/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java b/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
index 4198b18..5b14fce 100644
--- a/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
+++ b/src/main/java/net/onrc/onos/intent/ShortestPathIntent.java
@@ -13,6 +13,7 @@
 	protected long dstSwitchDpid;
 	protected long dstPortNumber;
 	protected long dstMacAddress;
+	protected String pathIntentId = null;
 
 	/**
 	 * Default constructor for Kryo deserialization
@@ -56,10 +57,18 @@
 		return dstMacAddress;
 	}
 
+	public void setPathIntent(PathIntent pathIntent) {
+		pathIntentId = pathIntent.getId();
+	}
+
+	public String getPathIntentId() {
+		return pathIntentId;
+	}
+
 	@Override
 	public String toString() {
 		return String.format("id:%s, state:%s, srcDpid:%s, srcPort:%d, srcMac:%s, dstDpid:%s, dstPort:%d, dstMac:%s",
-				getId(), state,
+				getId(), getState(),
 				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 201e97d..043a006 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
@@ -8,6 +8,7 @@
 import net.onrc.onos.intent.ErrorIntent;
 import net.onrc.onos.intent.ErrorIntent.ErrorType;
 import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.Intent.IntentState;
 import net.onrc.onos.intent.IntentOperation;
 import net.onrc.onos.intent.IntentOperation.Operator;
 import net.onrc.onos.intent.IntentOperationList;
@@ -63,13 +64,13 @@
 					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
 							ErrorType.SWITCH_NOT_FOUND,
 							"Switch not found.",
-							intentOp.intent));
+							spIntent));
 					continue;
 				}
 
 				double bandwidth = 0.0;
 				ConstrainedBFSTree tree = null;
-				if (intentOp.intent instanceof ConstrainedShortestPathIntent) {
+				if (spIntent instanceof ConstrainedShortestPathIntent) {
 					bandwidth = ((ConstrainedShortestPathIntent) intentOp.intent).getBandwidth();
 					tree = new ConstrainedBFSTree(srcSwitch, pathIntents, bandwidth);
 				}
@@ -82,18 +83,36 @@
 				}
 				Path path = tree.getPath(dstSwitch);
 				if (path == null) {
-					log.error("Path not found: {}", intentOp.intent.toString());
+					log.error("Path not found: {}", spIntent.toString());
 					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
 							ErrorType.PATH_NOT_FOUND,
 							"Path not found.",
-							intentOp.intent));
+							spIntent));
 					continue;
 				}
-				PathIntent pathIntent = new PathIntent("pi" + intentOp.intent.getId(), path, bandwidth, intentOp.intent);
+
+				// generate new path-intent ID
+				String oldPathIntentId = spIntent.getPathIntentId();
+				String newPathIntentId;
+				if (oldPathIntentId == null)
+					newPathIntentId = PathIntent.createFirstId(spIntent.getId());
+				else {
+					newPathIntentId = PathIntent.createNextId(oldPathIntentId);
+
+					// Request removal of low-level intent if it exists.
+					pathIntentOpList.add(Operator.REMOVE, new Intent(oldPathIntentId));
+				}
+
+				// create new path-intent
+				PathIntent pathIntent = new PathIntent(newPathIntentId, path, bandwidth, spIntent);
+				pathIntent.setState(IntentState.INST_REQ);
+				spIntent.setPathIntent(pathIntent);
 				pathIntentOpList.add(Operator.ADD, pathIntent);
+
 				break;
 			case REMOVE:
-				pathIntentOpList.add(Operator.REMOVE, new Intent("pi" + intentOp.intent.getId()));
+				pathIntentOpList.add(Operator.REMOVE, new Intent(
+						((ShortestPathIntent) intentOp.intent).getPathIntentId()));
 				break;
 			case ERROR:
 				// just ignore
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 6bdca51..b5521e2 100755
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
@@ -13,7 +13,6 @@
 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.Intent.IntentState;
 import net.onrc.onos.intent.IntentMap;
 import net.onrc.onos.intent.IntentOperation;
@@ -60,11 +59,9 @@
 
 		if (oldPaths.isEmpty())
 			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);
@@ -123,21 +120,26 @@
 		// update the map of high-level intents
 		highLevelIntents.executeOperations(list);
 
-		// prepare high-level intents' state changes
+		// change states of high-level intents
 		HashMap<String, IntentState> states = new HashMap<>();
 		for (IntentOperation op : list) {
 			String id = op.intent.getId();
-			states.put(id, IntentState.INST_REQ);
+			if (op.intent.getState().equals(IntentState.INST_ACK))
+				states.put(id, IntentState.REROUTE_REQ);
+			else
+				states.put(id, IntentState.INST_REQ);
 		}
+		highLevelIntents.changeStates(states);
 
 		// calculate path-intents (low-level operations)
 		IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, pathIntents);
 
-		// persist calculated low-level operations
+		// persist calculated low-level operations into data store
 		long key = persistIntent.getKey();
 		persistIntent.persistIfLeader(key, pathIntentOperations);
 
 		// remove error-intents and reflect them to high-level intents
+		states.clear();
 		Iterator<IntentOperation> i = pathIntentOperations.iterator();
 		while (i.hasNext()) {
 			IntentOperation op = i.next();
@@ -185,7 +187,7 @@
 			Collection<DeviceEvent> addedDeviceEvents,
 			Collection<DeviceEvent> removedDeviceEvents) {
 		// TODO need optimization.
-		// This reroutes only if when some links are removed for now.
+		// Only high-level intents that affected by link down are rerouted.
 		reroutePaths(removedLinkEvents);
 	}
 }
\ No newline at end of file
diff --git a/src/test/java/net/onrc/onos/intent/PathIntentTest.java b/src/test/java/net/onrc/onos/intent/PathIntentTest.java
index a465118..3c76f3f 100644
--- a/src/test/java/net/onrc/onos/intent/PathIntentTest.java
+++ b/src/test/java/net/onrc/onos/intent/PathIntentTest.java
@@ -29,6 +29,18 @@
 	}
 
 	@Test
+	public void testCreateFirstId() {
+		String id = PathIntent.createFirstId("100");
+		assertEquals("pi100___0", id);
+	}
+
+	@Test
+	public void testCreateNextId() {
+		String id = PathIntent.createNextId("pi100___999");
+		assertEquals("pi100___1000", id);
+	}
+
+	@Test
 	public void test() {
 		KryoFactory factory = new KryoFactory();
 		Kryo kryo = factory.newKryo();
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 698f790..00da877 100755
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -1,5 +1,6 @@
 package net.onrc.onos.intent.runtime;
 
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -11,7 +12,9 @@
 import net.onrc.onos.intent.ConstrainedShortestPathIntent;
 import net.onrc.onos.intent.FlowEntry;
 import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.Intent.IntentState;
 import net.onrc.onos.intent.IntentOperation.Operator;
+import net.onrc.onos.intent.IntentOperation;
 import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.intent.MockNetworkGraph;
 import net.onrc.onos.intent.PathIntent;
@@ -217,25 +220,21 @@
 		showResult((PathIntentMap) runtime1.getPathIntents());
 		System.out.println(plan);
 
+		// TODO this state changes should be triggered by notification of plan module
+		HashMap<String, IntentState> states = new HashMap<>();
+		states.put("1", IntentState.INST_ACK);
+		states.put("2", IntentState.INST_ACK);
+		states.put("3", IntentState.INST_ACK);
+		runtime1.getHighLevelIntents().changeStates(states);
+
 		// 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);
-		removedLinkEvents.clear();
-		removedLinkEvents.add(linkEvent);
-		runtime1.networkGraphEvents(
-				addedSwitchEvents,
-				removedSwitchEvents,
-				addedPortEvents,
-				removedPortEvents,
-				addedLinkEvents,
-				removedLinkEvents,
-				addedDeviceEvents,
-				removedDeviceEvents);
-
 		((MockNetworkGraph)g).removeLink(9L, 1L, 1L, 2L);
-		linkEvent = new LinkEvent(9L, 1L, 1L, 2L);
+		LinkEvent linkEvent1 = new LinkEvent(1L, 2L, 9L, 1L);
+		LinkEvent linkEvent2 = new LinkEvent(9L, 1L, 1L, 2L);
 		removedLinkEvents.clear();
-		removedLinkEvents.add(linkEvent);
+		removedLinkEvents.add(linkEvent1);
+		removedLinkEvents.add(linkEvent2);
 		runtime1.networkGraphEvents(
 				addedSwitchEvents,
 				removedSwitchEvents,