Added Intent Subscriber and Module

Module is used to drive plan computation and installation
Also, added support for ADD/REMOVE in plan computation

Change-Id: Ib88eae8b13a1f5ed1503c5ff7762980f8ed032ac
diff --git a/src/main/java/net/onrc/onos/intent/FlowEntry.java b/src/main/java/net/onrc/onos/intent/FlowEntry.java
index 14d1038..13bfa21 100644
--- a/src/main/java/net/onrc/onos/intent/FlowEntry.java
+++ b/src/main/java/net/onrc/onos/intent/FlowEntry.java
@@ -4,11 +4,13 @@
 import java.util.Set;
 
 import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.intent.IntentOperation.Operator;
 import net.onrc.onos.ofcontroller.networkgraph.Port;
 import net.onrc.onos.ofcontroller.networkgraph.Switch;
 import net.onrc.onos.ofcontroller.util.Dpid;
 import net.onrc.onos.ofcontroller.util.FlowEntryActions;
 import net.onrc.onos.ofcontroller.util.FlowEntryId;
+import net.onrc.onos.ofcontroller.util.FlowEntryUserState;
 
 /**
  * 
@@ -20,13 +22,16 @@
 	protected Switch sw;
 	protected Match match;
 	protected Set<Action> actions;
+	protected Operator operator;
 	
 	public FlowEntry(Switch sw, Port srcPort, Port dstPort, 
-					 MACAddress srcMac, MACAddress dstMac) {
+			 MACAddress srcMac, MACAddress dstMac,
+			 Operator operator) {
 		this.sw = sw;
 		this.match = new Match(sw, srcPort, srcMac, dstMac);
 		this.actions = new HashSet<Action>();
 		this.actions.add(new ForwardAction(dstPort));
+		this.operator = operator;
 	}
 	
 	public String toString() {
@@ -37,6 +42,14 @@
 	    return sw;
 	}
 	
+	public Operator getOperator() {
+	    return operator;
+	}
+	
+	public void setOperator(Operator op) {
+	    operator = op;
+	}
+	
 	public net.onrc.onos.ofcontroller.util.FlowEntry getFlowEntry() {
 		net.onrc.onos.ofcontroller.util.FlowEntry entry = new net.onrc.onos.ofcontroller.util.FlowEntry();
 		entry.setDpid(new Dpid(sw.getDpid()));
@@ -47,6 +60,17 @@
 		    flowEntryActions.addAction(action.getFlowEntryAction());
 		}
 		entry.setFlowEntryActions(flowEntryActions);
+		switch(operator) {
+		case ADD:
+		    entry.setFlowEntryUserState(FlowEntryUserState.FE_USER_MODIFY);
+		    break;
+		case REMOVE:
+		    entry.setFlowEntryUserState(FlowEntryUserState.FE_USER_DELETE);
+		    break;
+		}
 		return entry;
 	}
+	
+	//TODO: implement hash for cookie
+	//TODO: implement equals (don't include operator!)
 }
\ No newline at end of file
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 18e962f..63892cb 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanCalcRuntime.java
@@ -12,9 +12,10 @@
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.intent.FlowEntry;
 import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.IntentOperation;
+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.intent.ShortestPathIntent;
 import net.onrc.onos.ofcontroller.networkgraph.Link;
 import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
@@ -29,88 +30,103 @@
  */
 
 public class PlanCalcRuntime {
-	NetworkGraph graph;
-	protected PathIntentMap 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<>();
-		this.intents = new PathIntentMap();
+    NetworkGraph graph;
+
+    public PlanCalcRuntime(NetworkGraph graph) {
+	this.graph = graph;
+    }
+
+    public List<Set<FlowEntry>> computePlan(IntentOperationList intentOps) {
+	Set<Collection<FlowEntry>> flowEntries = computeFlowEntries(intentOps);
+	return buildPhases(flowEntries);
+    }
+
+    private Set<Collection<FlowEntry>> computeFlowEntries(IntentOperationList intentOps) {
+	Set<Collection<FlowEntry>> flowEntries = new HashSet<>();
+	for(IntentOperation i : intentOps) {
+	    PathIntent intent = (PathIntent) i.intent;
+	    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(LinkEvent linkEvent : intent.getPath()) {
+		Link link = graph.getLink(linkEvent.getSrc().getDpid(),
+			  linkEvent.getSrc().getNumber(),
+			  linkEvent.getDst().getDpid(),
+			  linkEvent.getDst().getNumber());
+		Switch sw = link.getSrcSwitch();
+		dstPort = link.getSrcPort();
+		FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac, i.operator);
+		entries.add(fe);
+		srcPort = link.getDstPort();
+	    }
+	    if(lastDstPort != null) {
+		Switch sw = lastDstPort.getSwitch();
+		dstPort = lastDstPort;
+		FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac, i.operator);
+		entries.add(fe);
+	    }
+	    // install flow entries in reverse order
+	    Collections.reverse(entries);
+	    flowEntries.add(entries);
 	}
+	return flowEntries;
+    }
 
-	public void addIntents(IntentOperationList intentOpList) {
-		intents.executeOperations(intentOpList);
-		computeFlowEntries();
-		constructPlan();
-	}
-
-	public List<Set<FlowEntry>> getPlan() {
-		return plan;
-	}
-
-	public void computeFlowEntries() {
-		for(Intent i : intents.getAllIntents()) {
-			PathIntent intent = (PathIntent)i;
-			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(LinkEvent linkEvent : intent.getPath()) {
-				Link link = graph.getLink(linkEvent.getSrc().getDpid(),
-							  linkEvent.getSrc().getNumber(),
-							  linkEvent.getDst().getDpid(),
-							  linkEvent.getDst().getNumber());
-				Switch sw = link.getSrcSwitch();
-				dstPort = link.getSrcPort();
-				FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac);
-				entries.add(fe);
-				srcPort = link.getDstPort();
-			}
-			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);
+    private List<Set<FlowEntry>> buildPhases(Set<Collection<FlowEntry>> flowEntries) {
+	Map<FlowEntry, Integer> map = new HashMap<>();
+	List<Set<FlowEntry>> plan = new ArrayList<>();
+	for(Collection<FlowEntry> c : flowEntries) {
+	    for(FlowEntry e : c) {
+		Integer i = map.get(e);
+		if(i == null) {
+		    i = Integer.valueOf(0);
 		}
-	}
-
-	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;
-				}
-
-			}
+		switch(e.getOperator()) {
+		case ADD:
+		    i += 1;
+		    break;
+		case REMOVE:
+		    i -= 1;
+		    break;
 		}
-
-		// really simple first iteration of plan
-		//TODO: optimize the map in phases
-		plan.add(map.keySet());
+		map.put(e, i);
+		System.out.println(e + " " + e.getOperator());
+	    }
 	}
+		
+	// really simple first iteration of plan
+	//TODO: optimize the map in phases
+	Set<FlowEntry> phase = new HashSet<>();
+	for(FlowEntry e : map.keySet()) {
+	    Integer i = map.get(e);
+	    if(i == 0) {
+		continue;
+	    }
+	    else if(i > 0) {
+		e.setOperator(Operator.ADD);
+	    }
+	    else if(i < 0) {
+		e.setOperator(Operator.REMOVE);
+	    }
+	    phase.add(e);
+	}
+	plan.add(phase);
+		
+	return plan;
+    }
 }
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java
new file mode 100644
index 0000000..df4c762
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallModule.java
@@ -0,0 +1,116 @@
+package net.onrc.onos.intent.runtime;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.datagrid.IDatagridService;
+import net.onrc.onos.datagrid.IEventChannelListener;
+import net.onrc.onos.intent.FlowEntry;
+import net.onrc.onos.intent.IntentOperationList;
+import net.onrc.onos.ofcontroller.flowprogrammer.IFlowPusherService;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+
+public class PlanInstallModule implements IFloodlightModule {
+    protected volatile IFloodlightProviderService floodlightProvider;
+    protected volatile NetworkGraph networkGraph;
+    protected volatile IDatagridService datagridService;
+    protected volatile IFlowPusherService flowPusher;
+    private PlanCalcRuntime planCalc;
+    private PlanInstallRuntime planInstall;
+    private EventListener eventListener;
+
+    private static final String PATH_INTENT_CHANNEL_NAME = "onos.pathintent";
+    
+    @Override
+    public void init(FloodlightModuleContext context)
+	    throws FloodlightModuleException {
+	floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+	//networkGraph = context.getServiceImpl(INetworkGraphService.class);
+	datagridService = context.getServiceImpl(IDatagridService.class);
+	flowPusher = context.getServiceImpl(IFlowPusherService.class);
+	planCalc = new PlanCalcRuntime(networkGraph);
+	planInstall = new PlanInstallRuntime(networkGraph, floodlightProvider, flowPusher);
+	eventListener = new EventListener();
+    }
+
+    class EventListener extends Thread
+    	implements IEventChannelListener<Long, IntentOperationList> {
+	
+	private BlockingQueue<IntentOperationList> intentQueue = new LinkedBlockingQueue<>();
+	
+	@Override
+	public void run() {
+	    while(true) {
+		try {
+		    IntentOperationList intents = intentQueue.take();
+		    //TODO: drain the remaining intent lists
+		    processIntents(intents);
+		} catch (InterruptedException e) {
+		    //TODO: log the exception
+		}
+	    }
+	}
+	
+	private void processIntents(IntentOperationList intents) {
+	    List<Set<FlowEntry>> plan = planCalc.computePlan(intents);
+	    planInstall.installPlan(plan);
+	}
+	
+	@Override
+	public void entryAdded(IntentOperationList value) {
+	    intentQueue.add(value);
+	}
+
+	@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
+	}
+    }
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+	eventListener.start();
+	datagridService.addListener(PATH_INTENT_CHANNEL_NAME, 
+				    new EventListener(), 
+				    Long.class, 
+				    IntentOperationList.class);
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+	Collection<Class<? extends IFloodlightService>> l =
+		new ArrayList<Class<? extends IFloodlightService>>();
+	l.add(IFloodlightProviderService.class);
+//	l.add(INetworkGraphService.class);
+	l.add(IDatagridService.class);
+	l.add(IFlowPusherService.class);
+	return l;
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+	// TODO Auto-generated method stub
+	return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+	// TODO Auto-generated method stub
+	return null;
+    }
+
+}
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PathInstallRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
similarity index 81%
rename from src/main/java/net/onrc/onos/intent/runtime/PathInstallRuntime.java
rename to src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
index 0b74cfc..3e4ff4a 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathInstallRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PlanInstallRuntime.java
@@ -18,22 +18,23 @@
  *
  */
 
-public class PathInstallRuntime {
+public class PlanInstallRuntime {
     NetworkGraph graph;
     IFlowPusherService pusher;
     IFloodlightProviderService provider;
-    protected List<Set<FlowEntry>> plan;
 
-    public PathInstallRuntime(NetworkGraph graph) {
+    public PlanInstallRuntime(NetworkGraph graph, 
+	    		      IFloodlightProviderService provider,
+	                      IFlowPusherService pusher) {
 	this.graph = graph;
+	this.provider = provider;
+	this.pusher = pusher;
     }
 
     public void installPlan(List<Set<FlowEntry>> plan) {
-	this.plan = plan;
 	Map<Long,IOFSwitch> switches = provider.getSwitches();
 	for(Set<FlowEntry> phase : plan) {
-	    Set<Pair<IOFSwitch, net.onrc.onos.ofcontroller.util.FlowEntry>> entries 
-	    = new HashSet<>();
+	    Set<Pair<IOFSwitch, net.onrc.onos.ofcontroller.util.FlowEntry>> entries = new HashSet<>();
 	    // convert flow entries and create pairs
 	    for(FlowEntry entry : phase) {
 		entries.add(new Pair<>(switches.get(entry.getSwitch().getDpid()), 
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 02703fc..a1f3e80 100644
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -1,10 +1,14 @@
 package net.onrc.onos.intent.runtime;
 
+import java.util.List;
+import java.util.Set;
+
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.module.FloodlightModuleException;
 import net.onrc.onos.datagrid.IDatagridService;
 import net.onrc.onos.datagrid.IEventChannel;
 import net.onrc.onos.intent.ConstrainedShortestPathIntent;
+import net.onrc.onos.intent.FlowEntry;
 import net.onrc.onos.intent.Intent;
 import net.onrc.onos.intent.IntentOperation.Operator;
 import net.onrc.onos.intent.IntentOperationList;
@@ -96,11 +100,11 @@
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntentOpList);
+		List<Set<FlowEntry>> plan = runtime2.computePlan(pathIntentOpList);
 
 		// show results
 		showResult((PathIntentMap) runtime1.getPathIntents());
-		System.out.println(runtime2.getPlan());
+		System.out.println(plan);
 	}
 
 	@Test
@@ -121,11 +125,11 @@
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntentOpList);
+		List<Set<FlowEntry>> plan = runtime2.computePlan(pathIntentOpList);
 
 		// show results
 		showResult((PathIntentMap) runtime1.getPathIntents());
-		System.out.println(runtime2.getPlan());
+		System.out.println(plan);
 	}
 
 	@Test
@@ -146,11 +150,11 @@
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntentOpList);
+		List<Set<FlowEntry>> plan = runtime2.computePlan(pathIntentOpList);
 
 		// show results
 		showResult((PathIntentMap) runtime1.getPathIntents());
-		System.out.println(runtime2.getPlan());
+		System.out.println(plan);
 	}
 
 	@Test
@@ -169,11 +173,11 @@
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntentOpList);
+		List<Set<FlowEntry>> plan = runtime2.computePlan(pathIntentOpList);
 
 		// show results step1
 		showResult((PathIntentMap) runtime1.getPathIntents());
-		System.out.println(runtime2.getPlan());
+		System.out.println(plan);
 
 		// link down
 		((MockNetworkGraph)g).removeLink(1L, 2L, 9L, 1L); // This link is used by the intent "1"
@@ -187,6 +191,6 @@
 
 		// show results step2
 		showResult((PathIntentMap) runtime1.getPathIntents());
-		System.out.println(runtime2.getPlan());
+		// TODO: show results of plan computation
 	}
 }