WIP: Initial implementation of runtime #2: take PathIntents and build a plan

Change-Id: I33acebecb76bd693f01a7440cee60c0fcfb49623
diff --git a/src/main/java/net/onrc/onos/intent/Action.java b/src/main/java/net/onrc/onos/intent/Action.java
new file mode 100644
index 0000000..ddf4474
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/Action.java
@@ -0,0 +1,11 @@
+package net.onrc.onos.intent;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class Action {
+
+}
diff --git a/src/main/java/net/onrc/onos/intent/FlowEntry.java b/src/main/java/net/onrc/onos/intent/FlowEntry.java
new file mode 100644
index 0000000..4c52324
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/FlowEntry.java
@@ -0,0 +1,32 @@
+package net.onrc.onos.intent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.networkgraph.Port;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class FlowEntry {
+	protected Switch sw;
+	protected Match match;
+	protected Set<Action> actions;
+	
+	public FlowEntry(Switch sw, Port srcPort, Port dstPort, 
+					 MACAddress srcMac, MACAddress dstMac) {
+		this.sw = sw;
+		this.match = new Match(sw, srcPort, srcMac, dstMac);
+		this.actions = new HashSet<Action>();
+		this.actions.add(new ForwardAction(dstPort));
+	}
+	
+	public String toString() {
+		return match + "->" + actions;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/intent/ForwardAction.java b/src/main/java/net/onrc/onos/intent/ForwardAction.java
new file mode 100644
index 0000000..245a918
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/ForwardAction.java
@@ -0,0 +1,22 @@
+package net.onrc.onos.intent;
+
+import net.onrc.onos.ofcontroller.networkgraph.Port;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+class ForwardAction extends Action {
+	protected Port dstPort;
+	
+	public ForwardAction(Port dstPort) {
+		this.dstPort = dstPort;
+	}
+	
+	public String toString() {
+		return dstPort.toString();
+	}
+	
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/intent/Match.java b/src/main/java/net/onrc/onos/intent/Match.java
new file mode 100644
index 0000000..c81fe00
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/Match.java
@@ -0,0 +1,44 @@
+package net.onrc.onos.intent;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.networkgraph.Port;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class Match {
+	protected Switch sw;
+	protected MACAddress srcMac;
+	protected MACAddress dstMac;
+	protected Port srcPort;
+	
+	public Match(Switch sw, Port srcPort, 
+				 MACAddress srcMac, MACAddress dstMac) {
+		this.sw = sw;
+		this.srcPort = srcPort;
+		this.srcMac = srcMac;
+		this.dstMac = dstMac;
+	}
+	
+	@Override
+	public boolean equals(Object obj) {
+		if(obj instanceof Match) {
+			Match other = (Match) obj;
+			return this.sw == other.sw &&
+					this.srcMac == other.srcMac &&
+					this.dstMac == other.dstMac &&
+					this.srcPort == other.srcPort;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public String toString() {
+		return "(" + srcPort + "," + srcMac + "," + dstMac + ")";
+	}
+}
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
new file mode 100644
index 0000000..9a46530
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
@@ -0,0 +1,108 @@
+package net.onrc.onos.intent.runtime;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.intent.FlowEntry;
+import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.PathIntent;
+import net.onrc.onos.intent.PathIntents;
+import net.onrc.onos.intent.ShortestPathIntent;
+import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.Port;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class PlanCalcRuntime {
+	NetworkGraph graph;
+	protected PathIntents intents;
+	protected Set<Collection<FlowEntry>> flowEntries;
+	protected List<Set<FlowEntry>> plan;
+	
+	public PlanCalcRuntime(NetworkGraph graph) {
+		this.graph = graph;
+		this.flowEntries = new HashSet<>();
+		this.plan = new ArrayList<>();
+	}
+	
+	public void addIntents(PathIntents intents) {
+		this.intents = intents;
+		computeFlowEntries();
+		constructPlan();
+	}
+	
+	public List<Set<FlowEntry>> getPlan() {
+		return plan;
+	}
+
+	public void computeFlowEntries() {
+		for(PathIntent intent : intents.getIntents()) {
+			Intent parent = intent.getParentIntent();
+			Port srcPort, dstPort, lastDstPort = null;
+			MACAddress srcMac, dstMac;
+			if(parent instanceof ShortestPathIntent) {
+				ShortestPathIntent pathIntent = (ShortestPathIntent) parent;
+				Switch srcSwitch = graph.getSwitch(pathIntent.getSrcSwitchDpid());
+				srcPort = srcSwitch.getPort(pathIntent.getSrcPortNumber());
+				srcMac = MACAddress.valueOf(pathIntent.getSrcMac());
+				dstMac = MACAddress.valueOf(pathIntent.getDstMac());
+				Switch dstSwitch = graph.getSwitch(pathIntent.getDstSwitchDpid());
+				lastDstPort = dstSwitch.getPort(pathIntent.getDstPortNumber());
+			}
+			else {
+				// TODO: log this error
+				continue;
+			}
+			List<FlowEntry> entries = new ArrayList<>();
+			for(Link link : intent.getPath(graph)) {
+				Switch sw = link.getSourceSwitch();
+				dstPort = link.getSourcePort();
+				FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac);
+				entries.add(fe);
+				srcPort = link.getDestinationPort();
+			}
+			if(lastDstPort != null) {
+				Switch sw = lastDstPort.getSwitch();
+				dstPort = lastDstPort;
+				FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac);
+				entries.add(fe);
+			}
+			// install flow entries in reverse order
+			Collections.reverse(entries);
+			flowEntries.add(entries);
+		}
+	}
+	
+	public void constructPlan() {
+		Map<FlowEntry, Integer> map = new HashMap<>();
+		for(Collection<FlowEntry> c : flowEntries) {
+			for(FlowEntry e: c) {
+				Integer i = map.get(e);
+				if(i == null) {
+					map.put(e, 1);
+				}
+				else {
+					i += 1;
+				}
+				
+			}
+		}
+		
+		// really simple first iteration of plan
+		//TODO: optimize the map in phases
+		plan.add(map.keySet());
+	}
+}
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 e2a9a64..d31b9b6 100644
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -56,9 +56,14 @@
 		// compile high-level intents into low-level intents (calculate paths)
 		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
 		runtime1.addInputIntents(intents);
+		
+		// compile low-level intents into flow entry installation plan
+		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
+		runtime2.addIntents(runtime1.getOutputIntents());
 
 		// show results
 		showResult(runtime1.getOutputIntents());
+		System.out.println(runtime2.getPlan());
 	}
 
 	@Test
@@ -75,8 +80,13 @@
 		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
 		runtime1.addInputIntents(intents);
 
+		// compile low-level intents into flow entry installation plan
+		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
+		runtime2.addIntents(runtime1.getOutputIntents());
+
 		// show results
 		showResult(runtime1.getOutputIntents());
+		System.out.println(runtime2.getPlan());
 	}
 
 	@Test
@@ -93,7 +103,12 @@
 		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
 		runtime1.addInputIntents(intents);
 
+		// compile low-level intents into flow entry installation plan
+		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
+		runtime2.addIntents(runtime1.getOutputIntents());
+
 		// show results
 		showResult(runtime1.getOutputIntents());
+		System.out.println(runtime2.getPlan());
 	}
 }