Renamed the intent package

net.onrc.onos.intent.* => net.onrc.onos.core.intent.*

Change-Id: Id61f79ed52acf3b91af4ebad2515ac5b7d6dc5e1
diff --git a/src/main/java/net/onrc/onos/core/datagrid/web/GetNGFlowsSummaryResource.java b/src/main/java/net/onrc/onos/core/datagrid/web/GetNGFlowsSummaryResource.java
index 0ebeec9..33b9a5f 100644
--- a/src/main/java/net/onrc/onos/core/datagrid/web/GetNGFlowsSummaryResource.java
+++ b/src/main/java/net/onrc/onos/core/datagrid/web/GetNGFlowsSummaryResource.java
@@ -4,13 +4,12 @@
 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.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.PathIntent;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.Intent.IntentState;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
 import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
 import net.onrc.onos.ofcontroller.networkgraph.Path;
 import net.onrc.onos.ofcontroller.util.CallerId;
diff --git a/src/main/java/net/onrc/onos/core/datagrid/web/IntentResource.java b/src/main/java/net/onrc/onos/core/datagrid/web/IntentResource.java
index 7d338b5..e666eb7 100755
--- a/src/main/java/net/onrc/onos/core/datagrid/web/IntentResource.java
+++ b/src/main/java/net/onrc/onos/core/datagrid/web/IntentResource.java
@@ -8,19 +8,21 @@
 import java.util.Collection;
 import java.util.Iterator;
 
-import net.onrc.onos.intent.ConstrainedShortestPathIntent;
-import net.onrc.onos.intent.ShortestPathIntent;
-import net.onrc.onos.intent.IntentOperation;
-import net.onrc.onos.intent.IntentMap;
-import net.onrc.onos.intent.Intent;
-import net.onrc.onos.intent.runtime.IPathCalcRuntimeService;
-import net.onrc.onos.intent.IntentOperationList;
+import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
+
 import org.codehaus.jackson.JsonGenerationException;
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.JsonMappingException;
 import org.restlet.resource.Post;
 import org.restlet.resource.ServerResource;
 import org.codehaus.jackson.map.ObjectMapper;
+
 import net.floodlightcontroller.util.MACAddress;
 
 import java.util.HashMap;
diff --git a/src/main/java/net/onrc/onos/core/intent/Action.java b/src/main/java/net/onrc/onos/core/intent/Action.java
new file mode 100644
index 0000000..feea8e4
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/Action.java
@@ -0,0 +1,14 @@
+package net.onrc.onos.core.intent;
+
+import net.onrc.onos.ofcontroller.util.FlowEntryAction;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public abstract class Action {
+
+    public abstract FlowEntryAction getFlowEntryAction();
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/ConstrainedBFSTree.java b/src/main/java/net/onrc/onos/core/intent/ConstrainedBFSTree.java
new file mode 100644
index 0000000..ed03a8e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/ConstrainedBFSTree.java
@@ -0,0 +1,72 @@
+package net.onrc.onos.core.intent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.Path;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+
+/**
+ * This class creates bandwidth constrained breadth first tree
+ * and returns paths from root switch to leaf switches
+ * which satisfies the bandwidth condition.
+ * If bandwidth parameter is not specified, the normal breadth first tree will be calculated.
+ * The paths are snapshot paths at the point of the class instantiation.
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class ConstrainedBFSTree {
+	LinkedList<Switch> switchQueue = new LinkedList<>();
+	HashSet<Switch> switchSearched = new HashSet<>();
+	HashMap<Long, LinkEvent> upstreamLinks = new HashMap<>();
+	HashMap<Switch, Path> paths = new HashMap<>();
+	Switch rootSwitch;
+	PathIntentMap intents = null;
+	double bandwidth = 0.0; // 0.0 means no limit for bandwidth (normal BFS tree)
+
+	public ConstrainedBFSTree(Switch rootSwitch) {
+		this.rootSwitch = rootSwitch;
+		calcTree();
+	}
+
+	public ConstrainedBFSTree(Switch rootSwitch, PathIntentMap intents, double bandwidth) {
+		this.rootSwitch = rootSwitch;
+		this.intents = intents;
+		this.bandwidth = bandwidth;
+		calcTree();
+	}
+
+	protected void calcTree() {
+		switchQueue.add(rootSwitch);
+		switchSearched.add(rootSwitch);
+		while (!switchQueue.isEmpty()) {
+			Switch sw = switchQueue.poll();
+			for (Link link: sw.getOutgoingLinks()) {
+				Switch reachedSwitch = link.getDstPort().getSwitch();
+				if (switchSearched.contains(reachedSwitch)) continue;
+				if (intents != null && intents.getAvailableBandwidth(link) < bandwidth) continue;
+				switchQueue.add(reachedSwitch);
+				switchSearched.add(reachedSwitch);
+				upstreamLinks.put(reachedSwitch.getDpid(), new LinkEvent(link));
+			}
+		}
+	}
+
+	public Path getPath(Switch leafSwitch) {
+		Path path = paths.get(leafSwitch);
+		Long rootSwitchDpid = rootSwitch.getDpid();
+		if (path == null && switchSearched.contains(leafSwitch)) {
+			path = new Path();
+			Long sw = leafSwitch.getDpid();
+			while (!sw.equals(rootSwitchDpid)) {
+				LinkEvent upstreamLink = upstreamLinks.get(sw);
+				path.add(0, upstreamLink);
+				sw = upstreamLink.getSrc().getDpid();
+			}
+			paths.put(leafSwitch, path);
+		}
+		return path;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/ConstrainedShortestPathIntent.java b/src/main/java/net/onrc/onos/core/intent/ConstrainedShortestPathIntent.java
new file mode 100644
index 0000000..ca4f753
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/ConstrainedShortestPathIntent.java
@@ -0,0 +1,26 @@
+package net.onrc.onos.core.intent;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class ConstrainedShortestPathIntent extends ShortestPathIntent {
+	protected double bandwidth;
+
+	/**
+	 * Default constructor for Kryo deserialization
+	 */
+	protected ConstrainedShortestPathIntent() {
+	}
+
+	public ConstrainedShortestPathIntent(String id,
+			long srcSwitch, long srcPort, long srcMac,
+			long dstSwitch, long dstPort, long dstMac,
+			double bandwidth) {
+		super(id, srcSwitch, srcPort, srcMac, dstSwitch, dstPort, dstMac);
+		this.bandwidth = bandwidth;
+	}
+
+	public double getBandwidth() {
+		return bandwidth;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/ErrorIntent.java b/src/main/java/net/onrc/onos/core/intent/ErrorIntent.java
new file mode 100644
index 0000000..a5046e8
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/ErrorIntent.java
@@ -0,0 +1,30 @@
+package net.onrc.onos.core.intent;
+
+/**
+ * This class is instantiated by Run-times to express intent calculation error
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class ErrorIntent extends Intent {
+	public enum ErrorType {
+		UNSUPPORTED_INTENT,
+		SWITCH_NOT_FOUND,
+		PATH_NOT_FOUND,
+	}
+
+	public ErrorType errorType;
+	public String message;
+	public Intent parentIntent;
+
+	/**
+	 * Default constructor for Kryo deserialization
+	 */
+	protected ErrorIntent() {
+	}
+
+	public ErrorIntent(ErrorType errorType, String message, Intent parentIntent) {
+		super(parentIntent.getId());
+		this.errorType = errorType;
+		this.message = message;
+		this.parentIntent = parentIntent;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/FlowEntry.java b/src/main/java/net/onrc/onos/core/intent/FlowEntry.java
new file mode 100644
index 0000000..7c9c696
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/FlowEntry.java
@@ -0,0 +1,89 @@
+package net.onrc.onos.core.intent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.intent.IntentOperation.Operator;
+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;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class FlowEntry {
+	protected long sw;
+	protected Match match;
+	protected Set<Action> actions;
+	protected Operator operator;
+	
+	public FlowEntry(long sw, long srcPort, long dstPort, 
+			 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() {
+		return match + "->" + actions;
+	}
+	
+	public long getSwitch() {
+	    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));
+		entry.setFlowEntryId(new FlowEntryId(hashCode())); // naive, but useful for now
+		entry.setFlowEntryMatch(match.getFlowEntryMatch());
+		FlowEntryActions flowEntryActions = new FlowEntryActions();
+		for(Action action : actions) {
+		    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;
+		default:
+		    break;
+		}
+		return entry;
+	}
+	
+	
+	public int hashCode() {
+	    return match.hashCode();
+	}
+	
+	public boolean equals(Object o) {
+	    if(!(o instanceof FlowEntry)) {
+		return false;
+	    }
+	    FlowEntry other = (FlowEntry) o;
+	    // Note: we should not consider the operator for this comparison
+	    return this.match.equals(other.match) 
+		&& this.actions.containsAll(other.actions)
+		&& other.actions.containsAll(this.actions);
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/ForwardAction.java b/src/main/java/net/onrc/onos/core/intent/ForwardAction.java
new file mode 100644
index 0000000..2294d65
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/ForwardAction.java
@@ -0,0 +1,40 @@
+package net.onrc.onos.core.intent;
+
+import net.onrc.onos.ofcontroller.util.FlowEntryAction;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+class ForwardAction extends Action {
+	protected long dstPort;
+	
+	public ForwardAction(long dstPort) {
+		this.dstPort = dstPort;
+	}
+	
+	public String toString() {
+		return Long.toString(dstPort);
+	}
+
+	@Override
+	public FlowEntryAction getFlowEntryAction() {
+	    FlowEntryAction action = new FlowEntryAction();
+	    action.setActionOutput(new net.onrc.onos.ofcontroller.util.Port((short) dstPort));
+	    return action;
+	}
+
+	public int hashCode() {
+	    return (int) dstPort;
+	}
+	
+	public boolean equals(Object o) {
+	    if(!(o instanceof ForwardAction)) {
+		return false;
+	    }
+  	    ForwardAction action = (ForwardAction) o;
+	    return this.dstPort == action.dstPort;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/Intent.java b/src/main/java/net/onrc/onos/core/intent/Intent.java
new file mode 100644
index 0000000..3c4dbea
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/Intent.java
@@ -0,0 +1,102 @@
+package net.onrc.onos.core.intent;
+
+import java.util.LinkedList;
+
+import com.esotericsoftware.kryo.serializers.FieldSerializer.Optional;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class Intent {
+	public enum IntentState {
+		CREATED,
+		INST_REQ,
+		INST_NACK,
+		INST_ACK,
+		DEL_REQ,
+		DEL_PENDING,
+		DEL_ACK,
+		REROUTE_REQ,
+	}
+
+	private String id;
+	private IntentState state = IntentState.CREATED;
+	private boolean pathFrozen = false;
+
+	@Optional(value="logs")
+	private LinkedList<String> logs = new LinkedList<>();
+
+	/**
+	 * Default constructor for Kryo deserialization
+	 */
+	protected Intent() {
+		logs.add(String.format("created, time:%d", System.nanoTime())); // for measurement
+	}
+
+	public Intent(String id) {
+		logs.add(String.format("created, time:%d", System.nanoTime())); // for measurement
+		this.id = id;
+	}
+
+	public Intent(String id, IntentState state) {
+		logs.add(String.format("created, time:%d", System.nanoTime())); // for measurement
+		setState(state);
+		this.id = id;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public IntentState getState() {
+		return state;
+	}
+
+	public IntentState setState(IntentState newState) {
+		logs.add(String.format("setState, oldState:%s, newState:%s, time:%d",
+				state, newState, System.nanoTime())); // for measurement
+		if (logs.size() > 20) { // TODO this size should be configurable
+			logs.removeFirst();
+		}
+		IntentState oldState = state;
+		state = newState;
+		return oldState;
+	}
+
+	public boolean isPathFrozen() {
+		return pathFrozen;
+	}
+
+	public void setPathFrozen(boolean isFrozen) {
+		pathFrozen = isFrozen;
+	}
+
+	public LinkedList<String> getLogs() {
+		return logs;
+	}
+
+	@Override
+	public int 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
+	public String toString() {
+		return id.toString() + ", " + state.toString();
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/IntentMap.java b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
new file mode 100644
index 0000000..ae314a7
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
@@ -0,0 +1,190 @@
+package net.onrc.onos.core.intent;
+
+import java.util.Collection;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map.Entry;
+
+import net.onrc.onos.core.intent.Intent.IntentState;
+import net.onrc.onos.core.intent.runtime.IntentStateList;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class IntentMap {
+	private HashSet<ChangedListener> listeners = new HashSet<>();
+	private HashMap<String, Intent> intents = new HashMap<>();
+	private	LinkedList<ChangedEvent> events = new LinkedList<>();
+
+	public enum ChangedEventType {
+		/**
+		 * Added new intent.
+		 */
+		ADDED,
+
+		/**
+		 * Removed existing intent.
+		 * The specified intent is an instance of Intent class (not a child class)
+		 * Only id and state are valid.
+		 */
+		REMOVED,
+
+		/**
+		 * Changed state of existing intent.
+		 * The specified intent is an instance of Intent class (not a child class)
+		 * Only id and state are valid.
+		 */
+		STATE_CHANGED,
+	}
+
+	public class ChangedEvent {
+		public ChangedEvent(ChangedEventType eventType, Intent intent) {
+			this.eventType = eventType;
+			this.intent = intent;
+		}
+		public ChangedEventType eventType;
+		public Intent intent;
+	}
+
+	public interface ChangedListener extends EventListener {
+		void intentsChange(LinkedList<ChangedEvent> events);
+	}
+
+	//================================================================================
+	// public methods
+	//================================================================================
+
+	public void executeOperations(IntentOperationList operations) {
+		for (IntentOperation operation: operations) {
+			switch (operation.operator) {
+			case ADD:
+				handleAddOperation(operation);
+				break;
+			case REMOVE:
+				handleRemoveOperation(operation);
+				break;
+			case ERROR:
+				handleErrorOperation(operation);
+				break;
+			}
+		}
+		notifyEvents();
+	}
+
+	public void purge() {
+		LinkedList<String> removeIds = new LinkedList<>();
+		for (Entry<String, Intent> entry: intents.entrySet()) {
+			Intent intent = entry.getValue();
+			if (intent.getState() == IntentState.DEL_ACK
+					|| intent.getState() == IntentState.INST_NACK) {
+				removeIds.add(intent.getId());
+			}
+		}
+		for (String intentId: removeIds) {
+			removeIntent(intentId);
+		}
+		notifyEvents();
+	}
+
+	public void changeStates(IntentStateList states) {
+		for (Entry<String, IntentState> state: states.entrySet()) {
+			setState(state.getKey(), state.getValue());
+		}
+		notifyEvents();
+	}
+
+	public Intent getIntent(String intentId) {
+		return intents.get(intentId);
+	}
+
+	public Collection<Intent> getAllIntents() {
+		return intents.values();
+	}
+
+	public void addChangeListener(ChangedListener listener) {
+		listeners.add(listener);
+	}
+
+	public void removeChangedListener(ChangedListener listener) {
+		listeners.remove(listener);
+	}
+
+	//================================================================================
+	// methods that affect intents map (protected)
+	//================================================================================
+
+	protected void putIntent(Intent intent) {
+		if (intents.containsKey(intent.getId()))
+			removeIntent(intent.getId());
+		intents.put(intent.getId(), intent);
+		events.add(new ChangedEvent(ChangedEventType.ADDED, intent));
+	}
+
+	protected void removeIntent(String intentId) {
+		Intent intent = intents.remove(intentId);
+		if (intent == null) return;
+		events.add(new ChangedEvent(ChangedEventType.REMOVED, intent));
+	}
+
+	protected void setState(String intentId, IntentState state) {
+		Intent intent = intents.get(intentId);
+		if (intent == null) return;
+		intent.setState(state);
+		events.add(new ChangedEvent(ChangedEventType.STATE_CHANGED, intent));
+	}
+
+	//================================================================================
+	// helper methods (protected)
+	//================================================================================
+
+	protected void handleAddOperation(IntentOperation operation) {
+		putIntent(operation.intent);
+	}
+
+	protected void handleRemoveOperation(IntentOperation operation) {
+		Intent intent = getIntent(operation.intent.getId());
+		if (intent == null) {
+			// TODO error handling
+		}
+		else {
+			setState(intent.getId(), IntentState.DEL_REQ);
+		}
+	}
+
+	protected void handleErrorOperation(IntentOperation operation) {
+		//TODO put error message into the intent
+
+		ErrorIntent errorIntent = (ErrorIntent) operation.intent;
+		Intent targetIntent = intents.get(errorIntent.getId());
+		if (targetIntent == null) {
+			// TODO error handling
+			return;
+		}
+
+		switch (targetIntent.getState()) {
+		case CREATED:
+		case INST_REQ:
+		case INST_ACK:
+		case REROUTE_REQ:
+			setState(targetIntent.getId(), IntentState.INST_NACK);
+			break;
+		case DEL_REQ:
+			setState(targetIntent.getId(), IntentState.DEL_PENDING);
+			break;
+		case INST_NACK:
+		case DEL_PENDING:
+		case DEL_ACK:
+			// do nothing
+			break;
+		}
+	}
+
+	protected void notifyEvents() {
+		for (ChangedListener listener: listeners) {
+			listener.intentsChange(events);
+		}
+		events.clear();
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/IntentOperation.java b/src/main/java/net/onrc/onos/core/intent/IntentOperation.java
new file mode 100644
index 0000000..006c159
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/IntentOperation.java
@@ -0,0 +1,40 @@
+package net.onrc.onos.core.intent;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class IntentOperation {
+	public enum Operator {
+		/**
+		 * Add new intent specified by intent field.
+		 */
+		ADD,
+
+		/**
+		 * Remove existing intent specified by intent field.
+		 * The instance of intent field should be an instance of Intent class (not a child class)
+		 */
+		REMOVE,
+
+		/**
+		 * Do error handling.
+		 * The instance of intent field should be an instance of ErrorIntent
+		 */
+		ERROR,
+	}
+
+	public Operator operator;
+	public Intent intent;
+
+	protected IntentOperation() {}
+
+	public IntentOperation(Operator operator, Intent intent) {
+		this.operator = operator;
+		this.intent = intent;
+	}
+
+	@Override
+	public String toString() {
+		return operator.toString() + ", (" + intent.toString() + ")";
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/IntentOperationList.java b/src/main/java/net/onrc/onos/core/intent/IntentOperationList.java
new file mode 100644
index 0000000..a3981fa
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/IntentOperationList.java
@@ -0,0 +1,14 @@
+package net.onrc.onos.core.intent;
+
+import java.util.LinkedList;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class IntentOperationList extends LinkedList<IntentOperation> {
+	private static final long serialVersionUID = -3894081461861052610L;
+
+	public boolean add(IntentOperation.Operator op, Intent intent) {
+		return add(new IntentOperation(op, intent));
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/Match.java b/src/main/java/net/onrc/onos/core/intent/Match.java
new file mode 100644
index 0000000..0d5d38a
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/Match.java
@@ -0,0 +1,66 @@
+package net.onrc.onos.core.intent;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.util.MACAddress;
+//import net.onrc.onos.ofcontroller.networkgraph.Port;
+//import net.onrc.onos.ofcontroller.networkgraph.Switch;
+import net.onrc.onos.ofcontroller.util.FlowEntryMatch;
+
+/**
+ *
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class Match {
+	protected long sw;
+	protected MACAddress srcMac;
+	protected MACAddress dstMac;
+	protected long srcPort;
+	
+	public Match(long sw, long 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.equals(other.srcMac) &&
+			       this.dstMac.equals(other.dstMac) &&
+			       this.srcPort == other.srcPort;
+		}
+		else {
+			return false;
+		}
+	}
+
+	public FlowEntryMatch getFlowEntryMatch(){
+	    FlowEntryMatch match = new FlowEntryMatch();
+	    match.enableSrcMac(srcMac);
+	    match.enableDstMac(dstMac);
+	    match.enableInPort(new net.onrc.onos.ofcontroller.util.Port((short) srcPort));
+	    return match;
+	}
+
+	@Override
+	public String toString() {
+		return "Sw:" + sw + " (" + srcPort + "," + srcMac + "," + dstMac + ")";
+	}
+	
+	@Override
+	public int hashCode() {
+	    long[] nums = new long[4];
+	    nums[0] = sw;
+	    nums[1] = srcPort;
+	    nums[2] = srcMac.toLong();
+	    nums[3] = dstMac.toLong();
+	    return Arrays.hashCode(nums);
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/PathIntent.java b/src/main/java/net/onrc/onos/core/intent/PathIntent.java
new file mode 100644
index 0000000..c84d739
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/PathIntent.java
@@ -0,0 +1,60 @@
+package net.onrc.onos.core.intent;
+
+import net.onrc.onos.ofcontroller.networkgraph.Path;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class PathIntent extends Intent {
+	protected Path path;
+	protected double bandwidth;
+	protected Intent parentIntent;
+
+	public static String createFirstId(String parentId) {
+		return String.format("%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
+	 */
+	protected PathIntent() {
+	}
+
+	/**
+	 *
+	 * @param graph
+	 * @param path
+	 * @param bandwidth bandwidth which should be allocated for the path.
+	 * If 0, no intent for bandwidth allocation (best effort).
+	 * @param parentIntent parent intent. If null, this is root intent.
+	 * @param id
+	 */
+	public PathIntent(String id, Path path, double bandwidth, Intent parentIntent) {
+		super(id);
+		this.path = path;
+		this.bandwidth = bandwidth;
+		this.parentIntent = parentIntent;
+	}
+
+	public double getBandwidth() {
+		return bandwidth;
+	}
+
+	public Path getPath() {
+		return path;
+	}
+
+	public Intent getParentIntent() {
+		return parentIntent;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("%s, %s, %s", getId(), getState(), getPath());
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/PathIntentMap.java b/src/main/java/net/onrc/onos/core/intent/PathIntentMap.java
new file mode 100644
index 0000000..89e70fb
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/PathIntentMap.java
@@ -0,0 +1,111 @@
+package net.onrc.onos.core.intent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.PortEvent.SwitchPort;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class PathIntentMap extends IntentMap {
+	private HashMap<Long, HashMap<Long, HashSet<PathIntent>>> intents;
+
+	public PathIntentMap() {
+		intents = new HashMap<>();
+	}
+
+	private HashSet<PathIntent> get(SwitchPort swPort) {
+		Long dpid = swPort.getDpid();
+		Long port = swPort.getNumber();
+		HashMap<Long, HashSet<PathIntent>> portToIntents = intents.get(dpid);
+		if (portToIntents == null) {
+			portToIntents = new HashMap<>();
+			intents.put(dpid, portToIntents);
+		}
+		HashSet<PathIntent> targetIntents = portToIntents.get(port);
+		if (targetIntents == null) {
+			targetIntents = new HashSet<>();
+			portToIntents.put(port, targetIntents);
+		}
+		return targetIntents;
+	}
+
+	private void put(SwitchPort swPort, PathIntent intent) {
+		get(swPort).add(intent);
+	}
+
+	@Override
+	protected void putIntent(Intent intent) {
+		if (!(intent instanceof PathIntent)) return; // TODO throw exception
+		super.putIntent(intent);
+
+		PathIntent pathIntent = (PathIntent) intent;
+		for (LinkEvent linkEvent: pathIntent.getPath()) {
+			put(linkEvent.getSrc(), (PathIntent) intent);
+			put(linkEvent.getDst(), (PathIntent) intent);
+		}
+	}
+
+	@Override
+	protected void removeIntent(String intentId) {
+		PathIntent intent = (PathIntent) getIntent(intentId);
+		for (LinkEvent linkEvent: intent.getPath()) {
+			get(linkEvent.getSrc()).remove(intent);
+			get(linkEvent.getDst()).remove(intent);
+		}
+		super.removeIntent(intentId);
+	}
+
+	public Collection<PathIntent> getIntentsByLink(LinkEvent linkEvent) {
+		return getIntentsByPort(
+				linkEvent.getSrc().getDpid(),
+				linkEvent.getSrc().getNumber());
+	}
+
+	public Collection<PathIntent> getIntentsByPort(Long dpid, Long port) {
+		HashMap<Long, HashSet<PathIntent>> portToIntents = intents.get(dpid);
+		if (portToIntents != null) {
+			HashSet<PathIntent> targetIntents = portToIntents.get(port);
+			if (targetIntents != null) {
+				return Collections.unmodifiableCollection(targetIntents);
+			}
+		}
+		return new HashSet<>();
+	}
+
+	public Collection<PathIntent> getIntentsByDpid(Long dpid) {
+		HashSet<PathIntent> result = new HashSet<>();
+		HashMap<Long, HashSet<PathIntent>> portToIntents = intents.get(dpid);
+		if (portToIntents != null) {
+			for (HashSet<PathIntent> targetIntents: portToIntents.values()) {
+				result.addAll(targetIntents);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * calculate available bandwidth of specified link
+	 * @param link
+	 * @return
+	 */
+	public Double getAvailableBandwidth(Link link) {
+		if (link == null) return null;
+		Double bandwidth = link.getCapacity();
+		LinkEvent linkEvent = new LinkEvent(link);
+		if (!bandwidth.isInfinite()) {
+			for (PathIntent intent: getIntentsByLink(linkEvent)) {
+				Double intentBandwidth = intent.getBandwidth();
+				if (intentBandwidth == null || intentBandwidth.isInfinite() || intentBandwidth.isNaN())
+					continue;
+				bandwidth -= intentBandwidth;
+			}
+		}
+		return bandwidth;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/ShortestPathIntent.java b/src/main/java/net/onrc/onos/core/intent/ShortestPathIntent.java
new file mode 100644
index 0000000..9a39627
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/ShortestPathIntent.java
@@ -0,0 +1,75 @@
+package net.onrc.onos.core.intent;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.util.Dpid;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class ShortestPathIntent extends Intent {
+	protected long srcSwitchDpid;
+	protected long srcPortNumber;
+	protected long srcMacAddress;
+	protected long dstSwitchDpid;
+	protected long dstPortNumber;
+	protected long dstMacAddress;
+	protected String pathIntentId = null;
+
+	/**
+	 * Default constructor for Kryo deserialization
+	 */
+	protected ShortestPathIntent() {
+	}
+
+	public ShortestPathIntent(String id,
+			long srcSwitch, long srcPort, long srcMac,
+			long dstSwitch, long dstPort, long dstMac) {
+		super(id);
+		srcSwitchDpid = srcSwitch;
+		srcPortNumber = srcPort;
+		srcMacAddress = srcMac;
+		dstSwitchDpid = dstSwitch;
+		dstPortNumber = dstPort;
+		dstMacAddress = dstMac;
+	}
+
+	public long getSrcSwitchDpid() {
+		return srcSwitchDpid;
+	}
+
+	public long getSrcPortNumber() {
+		return srcPortNumber;
+	}
+
+	public long getSrcMac() {
+		return srcMacAddress;
+	}
+
+	public long getDstSwitchDpid() {
+		return dstSwitchDpid;
+	}
+
+	public long getDstPortNumber() {
+		return dstPortNumber;
+	}
+
+	public long getDstMac() {
+		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(), getState(),
+				new Dpid(srcSwitchDpid), srcPortNumber, MACAddress.valueOf(srcMacAddress),
+				new Dpid(dstSwitchDpid), dstPortNumber, MACAddress.valueOf(dstMacAddress));
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
new file mode 100644
index 0000000..c6aad89
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
@@ -0,0 +1,15 @@
+package net.onrc.onos.core.intent.runtime;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperationList;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public interface IPathCalcRuntimeService extends IFloodlightService {
+	public IntentOperationList executeIntentOperations(IntentOperationList list);
+	public IntentMap getHighLevelIntents();
+	public IntentMap getPathIntents();
+	public void purgeIntents();
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/IntentStateList.java b/src/main/java/net/onrc/onos/core/intent/runtime/IntentStateList.java
new file mode 100644
index 0000000..e6f1180
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/IntentStateList.java
@@ -0,0 +1,12 @@
+package net.onrc.onos.core.intent.runtime;
+
+import java.util.HashMap;
+
+import net.onrc.onos.core.intent.Intent.IntentState;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class IntentStateList extends HashMap<String, IntentState> {
+	private static final long serialVersionUID = -3674903999581438936L;
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntime.java b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntime.java
new file mode 100644
index 0000000..89a0b1a
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntime.java
@@ -0,0 +1,141 @@
+package net.onrc.onos.core.intent.runtime;
+
+import java.util.HashMap;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.core.intent.ConstrainedBFSTree;
+import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
+import net.onrc.onos.core.intent.ErrorIntent;
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.PathIntent;
+import net.onrc.onos.core.intent.PathIntentMap;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.ErrorIntent.ErrorType;
+import net.onrc.onos.core.intent.Intent.IntentState;
+import net.onrc.onos.core.intent.IntentOperation.Operator;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.Path;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class PathCalcRuntime implements IFloodlightService {
+	private NetworkGraph graph;
+	private final static Logger log = LoggerFactory.getLogger(PathCalcRuntime.class);
+	public PathCalcRuntime(NetworkGraph g) {
+		this.graph = g;
+	}
+
+	/**
+	 * calculate shortest-path and constrained-shortest-path intents into low-level path intents
+	 * @param intentOpList IntentOperationList having instances of ShortestPathIntent/ConstrainedShortestPathIntent
+	 * @param pathIntents a set of current low-level intents
+	 * @return IntentOperationList. PathIntent and/or ErrorIntent instances.
+	 */
+	public IntentOperationList calcPathIntents(final IntentOperationList intentOpList, final IntentMap appIntents, final PathIntentMap pathIntents) {
+		IntentOperationList pathIntentOpList = new IntentOperationList();
+		HashMap<Switch, ConstrainedBFSTree> spfTrees = new HashMap<>();
+
+		// TODO optimize locking of NetworkGraph
+		graph.acquireReadLock();
+		log.debug("NetworkGraph: {}", graph.getLinks());
+
+		for (IntentOperation intentOp: intentOpList) {
+			switch (intentOp.operator) {
+			case ADD:
+				if (!(intentOp.intent instanceof ShortestPathIntent)) {
+					log.error("Unsupported intent type: {}", intentOp.intent.getClass().getName());
+					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
+							ErrorType.UNSUPPORTED_INTENT,
+							"Unsupported intent type.",
+							intentOp.intent));
+					continue;
+				}
+
+				ShortestPathIntent spIntent = (ShortestPathIntent) intentOp.intent;
+				Switch srcSwitch = graph.getSwitch(spIntent.getSrcSwitchDpid());
+				Switch dstSwitch = graph.getSwitch(spIntent.getDstSwitchDpid());
+				if (srcSwitch == null || dstSwitch == null) {
+					log.error("Switch not found. src:{}, dst:{}, NetworkGraph:{}",
+							spIntent.getSrcSwitchDpid(),
+							spIntent.getDstSwitchDpid(),
+							graph.getLinks());
+					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
+							ErrorType.SWITCH_NOT_FOUND,
+							"Switch not found.",
+							spIntent));
+					continue;
+				}
+
+				double bandwidth = 0.0;
+				ConstrainedBFSTree tree = null;
+				if (spIntent instanceof ConstrainedShortestPathIntent) {
+					bandwidth = ((ConstrainedShortestPathIntent) intentOp.intent).getBandwidth();
+					tree = new ConstrainedBFSTree(srcSwitch, pathIntents, bandwidth);
+				}
+				else {
+					tree = spfTrees.get(srcSwitch);
+					if (tree == null) {
+						tree = new ConstrainedBFSTree(srcSwitch);
+						spfTrees.put(srcSwitch, tree);
+					}
+				}
+				Path path = tree.getPath(dstSwitch);
+				if (path == null) {
+					log.error("Path not found. Intent: {}, NetworkGraph:{}", spIntent.toString(), graph.getLinks());
+					pathIntentOpList.add(Operator.ERROR, new ErrorIntent(
+							ErrorType.PATH_NOT_FOUND,
+							"Path not found.",
+							spIntent));
+					continue;
+				}
+
+				// 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:
+				ShortestPathIntent targetAppIntent = (ShortestPathIntent) appIntents.getIntent(intentOp.intent.getId());
+				if (targetAppIntent != null) {
+					String pathIntentId = targetAppIntent.getPathIntentId();
+					if (pathIntentId != null) {
+						Intent targetPathIntent = pathIntents.getIntent(pathIntentId);
+						if (targetPathIntent != null) {
+							pathIntentOpList.add(Operator.REMOVE, targetPathIntent);
+						}
+					}
+				}
+				break;
+			case ERROR:
+				// just ignore
+				break;
+			}
+		}
+		// TODO optimize locking of NetworkGraph
+		graph.releaseReadLock();
+
+		return pathIntentOpList;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
new file mode 100755
index 0000000..9f6353e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
@@ -0,0 +1,407 @@
+package net.onrc.onos.core.intent.runtime;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.core.datagrid.IDatagridService;
+import net.onrc.onos.core.datagrid.IEventChannel;
+import net.onrc.onos.core.datagrid.IEventChannelListener;
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.PathIntent;
+import net.onrc.onos.core.intent.PathIntentMap;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.Intent.IntentState;
+import net.onrc.onos.core.intent.IntentOperation.Operator;
+import net.onrc.onos.ofcontroller.networkgraph.DeviceEvent;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphListener;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.PortEvent;
+import net.onrc.onos.ofcontroller.networkgraph.SwitchEvent;
+import net.onrc.onos.registry.controller.IControllerRegistryService;
+
+/**
+ * @author Toshio Koide (t-koide@onlab.us)
+ */
+public class PathCalcRuntimeModule implements IFloodlightModule, IPathCalcRuntimeService, INetworkGraphListener, IEventChannelListener<Long, IntentStateList> {
+	class PerfLog {
+		private String step;
+		private long time;
+
+		public PerfLog(String step) {
+			this.step = step;
+			this.time = System.nanoTime();
+		}
+
+		public void logThis() {
+			log.error("Time:{}, Step:{}", time, step);
+		}
+	}
+	class PerfLogger {
+		private LinkedList<PerfLog> logData = new LinkedList<>();
+
+		public PerfLogger(String logPhase) {
+			log("start_" + logPhase);
+		}
+
+		public void log(String step) {
+			logData.add(new PerfLog(step));
+		}
+
+		public void flushLog() {
+			log("finish");
+			for (PerfLog log: logData) {
+				log.logThis();
+			}
+			logData.clear();
+		}
+	}
+	private PathCalcRuntime runtime;
+	private IDatagridService datagridService;
+	private INetworkGraphService networkGraphService;
+	private IntentMap highLevelIntents;
+	private PathIntentMap pathIntents;
+	private IControllerRegistryService controllerRegistry;
+	private PersistIntent persistIntent;
+
+	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);
+
+	// ================================================================================
+	// private methods
+	// ================================================================================
+
+	private void reroutePaths(Collection<Intent> oldPaths) {
+		if (oldPaths == null || oldPaths.isEmpty())
+			return;
+
+		IntentOperationList reroutingOperation = new IntentOperationList();
+		for (Intent intent : oldPaths) {
+			PathIntent pathIntent = (PathIntent) intent;
+			if (pathIntent.isPathFrozen())
+				continue;
+			if (pathIntent.getState().equals(IntentState.INST_ACK) && // XXX: path intents in flight
+					!reroutingOperation.contains(pathIntent.getParentIntent())) {
+				reroutingOperation.add(Operator.ADD, pathIntent.getParentIntent());
+			}
+		}
+		executeIntentOperations(reroutingOperation);
+	}
+
+
+	// ================================================================================
+	// IFloodlightModule implementations
+	// ================================================================================
+
+	@Override
+	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+		Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(1);
+		l.add(IPathCalcRuntimeService.class);
+		return l;
+	}
+
+	@Override
+	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+		Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<>();
+		m.put(IPathCalcRuntimeService.class, this);
+		return m;
+	}
+
+	@Override
+	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+		Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(2);
+		l.add(IDatagridService.class);
+		l.add(INetworkGraphService.class);
+		return l;
+	}
+
+	@Override
+	public void init(FloodlightModuleContext context) throws FloodlightModuleException {
+		datagridService = context.getServiceImpl(IDatagridService.class);
+		networkGraphService = context.getServiceImpl(INetworkGraphService.class);
+		controllerRegistry = context.getServiceImpl(IControllerRegistryService.class);
+	}
+
+	@Override
+	public void startUp(FloodlightModuleContext context) {
+		highLevelIntents = new IntentMap();
+		runtime = new PathCalcRuntime(networkGraphService.getNetworkGraph());
+		pathIntents = new PathIntentMap();
+		opEventChannel = datagridService.createChannel(INTENT_OP_EVENT_CHANNEL_NAME, Long.class, IntentOperationList.class);
+		datagridService.addListener(INTENT_STATE_EVENT_CHANNEL_NAME, this, Long.class, IntentStateList.class);
+		networkGraphService.registerNetworkGraphListener(this);
+		persistIntent = new PersistIntent(controllerRegistry, networkGraphService);
+	}
+
+	// ================================================================================
+	// IPathCalcRuntimeService implementations
+	// ================================================================================
+
+	@Override
+	public IntentOperationList executeIntentOperations(IntentOperationList list) {
+		if (list == null || list.size() == 0)
+			return null;
+		PerfLogger p = new PerfLogger("executeIntentOperations_" + list.get(0).operator);
+
+		lock.lock(); // TODO optimize locking using smaller steps
+		try {
+			// update the map of high-level intents
+			p.log("begin_updateInMemoryIntents");
+			highLevelIntents.executeOperations(list);
+
+			// change states of high-level intents
+			IntentStateList states = new IntentStateList();
+			for (IntentOperation op : list) {
+				switch (op.operator) {
+				case ADD:
+					switch (op.intent.getState()) {
+					case CREATED:
+						states.put(op.intent.getId(), IntentState.INST_REQ);
+						break;
+					case INST_ACK:
+						states.put(op.intent.getId(), IntentState.REROUTE_REQ);
+						break;
+					default:
+						break;
+					}
+					break;
+				case REMOVE:
+					switch (op.intent.getState()) {
+					case CREATED:
+						states.put(op.intent.getId(), IntentState.DEL_REQ);
+						break;
+					default:
+						break;
+					}
+					break;
+				default:
+					break;
+				}
+			}
+			highLevelIntents.changeStates(states);
+			p.log("end_updateInMemoryIntents");
+
+			// calculate path-intents (low-level operations)
+			p.log("begin_calcPathIntents");
+			IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, highLevelIntents, pathIntents);
+			p.log("end_calcPathIntents");
+
+			// persist calculated low-level operations into data store
+			p.log("begin_persistPathIntents");
+			long key = persistIntent.getKey();
+			persistIntent.persistIfLeader(key, pathIntentOperations);
+			p.log("end_persistPathIntents");
+
+			// remove error-intents and reflect them to high-level intents
+			p.log("begin_removeErrorIntents");
+			states.clear();
+			Iterator<IntentOperation> i = pathIntentOperations.iterator();
+			while (i.hasNext()) {
+				IntentOperation op = i.next();
+				if (op.operator.equals(Operator.ERROR)) {
+					states.put(op.intent.getId(), IntentState.INST_NACK);
+					i.remove();
+				}
+			}
+			highLevelIntents.changeStates(states);
+			p.log("end_removeErrorIntents");
+
+			// update the map of path intents and publish the path operations
+			p.log("begin_updateInMemoryPathIntents");
+			pathIntents.executeOperations(pathIntentOperations);
+			p.log("end_updateInMemoryPathIntents");
+
+			// XXX Demo special: add a complete path to remove operation
+			p.log("begin_addPathToRemoveOperation");
+			for (IntentOperation op: pathIntentOperations) {
+				if(op.operator.equals(Operator.REMOVE)) {
+					op.intent = pathIntents.getIntent(op.intent.getId());
+				}
+				if (op.intent instanceof PathIntent) {
+					log.debug("operation: {}, intent:{}", op.operator, op.intent);
+				}
+			}
+			p.log("end_addPathToRemoveOperation");
+
+			// send notification
+			p.log("begin_sendNotification");
+			// 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);
+			return pathIntentOperations;
+		}
+		finally {
+			p.flushLog();
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public IntentMap getHighLevelIntents() {
+		return highLevelIntents;
+	}
+
+	@Override
+	public IntentMap getPathIntents() {
+		return pathIntents;
+	}
+
+	@Override
+	public void purgeIntents() {
+		highLevelIntents.purge();
+		pathIntents.purge();
+	}
+
+	// ================================================================================
+	// INetworkGraphListener implementations
+	// ================================================================================
+
+	@Override
+	public void networkGraphEvents(Collection<SwitchEvent> addedSwitchEvents,
+			Collection<SwitchEvent> removedSwitchEvents,
+			Collection<PortEvent> addedPortEvents,
+			Collection<PortEvent> removedPortEvents,
+			Collection<LinkEvent> addedLinkEvents,
+			Collection<LinkEvent> removedLinkEvents,
+			Collection<DeviceEvent> addedDeviceEvents,
+			Collection<DeviceEvent> removedDeviceEvents) {
+
+		PerfLogger p = new PerfLogger("networkGraphEvents");
+		HashSet<Intent> affectedPaths = new HashSet<>();
+
+		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 if (removedSwitchEvents.size() > 0 ||
+			 removedLinkEvents.size() > 0 ||
+			 removedPortEvents.size() > 0) {
+			p.log("begin_getIntentsByLink");
+			for (LinkEvent linkEvent: removedLinkEvents)
+				affectedPaths.addAll(pathIntents.getIntentsByLink(linkEvent));
+			p.log("end_getIntentsByLink");
+
+			p.log("begin_getIntentsByPort");
+			for (PortEvent portEvent: removedPortEvents)
+				affectedPaths.addAll(pathIntents.getIntentsByPort(portEvent.getDpid(), portEvent.getNumber()));
+			p.log("end_getIntentsByPort");
+
+			p.log("begin_getIntentsByDpid");
+			for (SwitchEvent switchEvent: removedSwitchEvents)
+				affectedPaths.addAll(pathIntents.getIntentsByDpid(switchEvent.getDpid()));
+			p.log("end_getIntentsByDpid");
+		}
+		p.log("begin_reroutePaths");
+		reroutePaths(affectedPaths);
+		p.log("end_reroutePaths");
+		p.flushLog();
+	}
+
+	// ================================================================================
+	// IEventChannelListener implementations
+	// ================================================================================
+
+	@Override
+	public void entryAdded(IntentStateList value) {
+		entryUpdated(value);
+	}
+
+	@Override
+	public void entryRemoved(IntentStateList value) {
+		// do nothing
+	}
+
+	@Override
+	public void entryUpdated(IntentStateList value) {
+		// TODO draw state transition diagram in multiple ONOS instances and update this method
+		PerfLogger p = new PerfLogger("entryUpdated");
+		lock.lock(); // TODO optimize locking using smaller steps
+		try {
+			// reflect state changes of path-level intent into application-level intents
+			p.log("begin_changeStateByNotification");
+			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;
+
+				Intent parentIntent = pathIntent.getParentIntent();
+				if (parentIntent == null ||
+						!(parentIntent instanceof ShortestPathIntent) ||
+						!((ShortestPathIntent) parentIntent).getPathIntentId().equals(pathIntent.getId()))
+					continue;
+
+				IntentState state = entry.getValue();
+				switch (state) {
+				//case INST_REQ:
+				case INST_ACK:
+				case INST_NACK:
+				//case DEL_REQ:
+				case DEL_ACK:
+				case DEL_PENDING:
+					highLevelIntentStates.put(parentIntent.getId(), state);
+					pathIntentStates.put(entry.getKey(), entry.getValue());
+					break;
+				default:
+					break;
+				}
+			}
+			highLevelIntents.changeStates(highLevelIntentStates);
+			pathIntents.changeStates(pathIntentStates);
+			p.log("end_changeStateByNotification");
+		}
+		finally {
+			p.flushLog();
+			lock.unlock();
+		}
+	}
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PersistIntent.java b/src/main/java/net/onrc/onos/core/intent/runtime/PersistIntent.java
new file mode 100755
index 0000000..134f437
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PersistIntent.java
@@ -0,0 +1,129 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package net.onrc.onos.core.intent.runtime;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Output;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicLong;
+
+import net.onrc.onos.core.datagrid.web.IntentResource;
+import net.onrc.onos.core.datastore.DataStoreClient;
+import net.onrc.onos.core.datastore.IKVTable;
+import net.onrc.onos.core.datastore.ObjectExistsException;
+import net.onrc.onos.core.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;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author nickkaranatsios
+ */
+public class PersistIntent {
+    private final static Logger log = LoggerFactory.getLogger(IntentResource.class);
+    private long range = 10000L;
+    private final IControllerRegistryService controllerRegistry;
+    NetworkGraph graph = null;
+    private final static String intentJournal = "G:IntentJournal";
+    private final static int valueStoreLimit = 1024 * 1024;
+    private IKVTable table;
+    private Kryo kryo;
+    private ByteArrayOutputStream stream;
+    private Output output = null;
+    private AtomicLong nextId = null;
+    private long rangeEnd;
+    private IdBlock idBlock = null;
+
+
+    public PersistIntent(final IControllerRegistryService controllerRegistry, INetworkGraphService ng) {
+        this.controllerRegistry = controllerRegistry;
+        this.graph = ng.getNetworkGraph();
+        table = DataStoreClient.getClient().getTable(intentJournal);
+        stream = new ByteArrayOutputStream(1024);
+        output = new Output(stream);
+        kryo = (new KryoFactory()).newKryo();
+    }
+
+    public long getKey() {
+        long key;
+        if (idBlock == null) {
+            key = getNextBlock();
+        } else {
+            key = nextId.incrementAndGet();
+            if (key >= rangeEnd) {
+                key = getNextBlock();
+            }
+        }
+        return key;
+    }
+
+    private long getNextBlock() {
+        // XXX This method is not thread safe, may lose allocated IdBlock
+        idBlock = controllerRegistry.allocateUniqueIdBlock(range);
+        nextId = new AtomicLong(idBlock.getStart());
+        rangeEnd = idBlock.getEnd();
+        return nextId.get();
+    }
+
+    public boolean persistIfLeader(long key, IntentOperationList operations) {
+        boolean leader = true;
+        boolean ret = false;
+        // TODO call controllerRegistry.isClusterLeader()
+        if (leader) {
+            try {
+                // reserve key 10 entries for multi-write if size over 1MB
+                key *= 10;
+                kryo.writeObject(output, operations);
+                output.close();
+                ByteBuffer keyBytes = ByteBuffer.allocate(8).putLong(key);
+                byte[] buffer = stream.toByteArray();
+                int total = buffer.length;
+                if ((total >= valueStoreLimit )) {
+                    int writeCount = total / valueStoreLimit;
+                    int remainder = total % valueStoreLimit;
+                    int upperIndex = 0;
+                    for (int i = 0; i < writeCount; i++, key++) {
+                        keyBytes.clear();
+                        keyBytes.putLong(key);
+                        keyBytes.flip();
+                        upperIndex = (i * valueStoreLimit + valueStoreLimit) - 1;
+                        log.debug("writing using indexes {}:{}", (i*valueStoreLimit) ,upperIndex);
+                        table.create(keyBytes.array(), Arrays.copyOfRange(buffer, i * valueStoreLimit, upperIndex));
+                    }
+                    if (remainder > 0) {
+                        keyBytes.clear();
+                        keyBytes.putLong(key);
+                        keyBytes.flip();
+                        log.debug("writing using indexes {}:{}" ,upperIndex ,total);
+                        table.create(keyBytes.array(), Arrays.copyOfRange(buffer, upperIndex + 1, total - 1));
+                    }
+                } else {
+                    keyBytes.flip();
+                    table.create(keyBytes.array(), buffer);
+                }
+                log.debug("key is {} value length is {}", key, buffer.length);
+                stream.reset();
+                stream.close();
+                log.debug("persist operations to ramcloud size of operations: {}", operations.size());
+                ret = true;
+            } catch (ObjectExistsException ex) {
+                log.warn("Failed to store intent journal with key " + key);
+            } catch (IOException ex) {
+                log.error("Failed to close the stream");
+            }
+        }
+        return ret;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java b/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java
new file mode 100644
index 0000000..7c92115
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java
@@ -0,0 +1,155 @@
+package net.onrc.onos.core.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.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.intent.FlowEntry;
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.PathIntent;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.IntentOperation.Operator;
+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>
+ *
+ */
+
+public class PlanCalcRuntime {
+
+//    NetworkGraph graph;
+    private final static Logger log = LoggerFactory.getLogger(PlanCalcRuntime.class);
+
+    public PlanCalcRuntime(/*NetworkGraph graph*/) {
+//	this.graph = graph;
+    }
+
+    public List<Set<FlowEntry>> computePlan(IntentOperationList intentOps) {
+	long start = System.nanoTime();
+	List<Collection<FlowEntry>> flowEntries = computeFlowEntries(intentOps);
+	long step1 = System.nanoTime();
+	List<Set<FlowEntry>> plan = buildPhases(flowEntries);
+	long step2 = System.nanoTime();
+	log.error("MEASUREMENT: Compute flow entries: {} ns, Build phases: {} ns", 
+		  (step1 - start), (step2 - step1));
+	return plan;
+    }
+
+    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);
+		continue;
+	    }
+	    PathIntent intent = (PathIntent) i.intent;
+	    Intent parent = intent.getParentIntent();
+	    long srcPort, dstPort;
+	    long lastDstSw = -1, lastDstPort = -1;
+	    MACAddress srcMac, dstMac;
+	    if(parent instanceof ShortestPathIntent) {
+		ShortestPathIntent pathIntent = (ShortestPathIntent) parent;
+//		Switch srcSwitch = graph.getSwitch(pathIntent.getSrcSwitchDpid());
+//		srcPort = srcSwitch.getPort(pathIntent.getSrcPortNumber());
+		srcPort = pathIntent.getSrcPortNumber();
+		srcMac = MACAddress.valueOf(pathIntent.getSrcMac());
+		dstMac = MACAddress.valueOf(pathIntent.getDstMac());
+//		Switch dstSwitch = graph.getSwitch(pathIntent.getDstSwitchDpid());
+		lastDstSw = pathIntent.getDstSwitchDpid();
+//		lastDstPort = dstSwitch.getPort(pathIntent.getDstPortNumber());
+		lastDstPort = pathIntent.getDstPortNumber();
+	    }
+	    else {
+		log.warn("Unsupported Intent: {}", parent);
+		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();
+		long sw = linkEvent.getSrc().getDpid();
+//		dstPort = link.getSrcPort();
+		dstPort = linkEvent.getSrc().getNumber();
+		FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac, i.operator);
+		entries.add(fe);
+//		srcPort = link.getDstPort();
+		srcPort = linkEvent.getDst().getNumber();
+	    }
+	    if(lastDstSw >= 0 && lastDstPort >= 0) {
+		//Switch sw = lastDstPort.getSwitch();
+		long sw = lastDstSw;
+		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;
+    }
+
+    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) {
+	    for(FlowEntry e : c) {
+		Integer i = map.get(e);
+		if(i == null) {
+		    i = Integer.valueOf(0);
+		}
+		switch(e.getOperator()) {
+		case ADD:
+		    i += 1;
+		    break;
+		case REMOVE:
+		    i -= 1;
+		    break;
+		default:
+		    break;
+		}
+		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/core/intent/runtime/PlanInstallModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java
new file mode 100644
index 0000000..bbc303d
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java
@@ -0,0 +1,201 @@
+package net.onrc.onos.core.intent.runtime;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+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.core.datagrid.IDatagridService;
+import net.onrc.onos.core.datagrid.IEventChannel;
+import net.onrc.onos.core.datagrid.IEventChannelListener;
+import net.onrc.onos.core.intent.FlowEntry;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.Intent.IntentState;
+import net.onrc.onos.ofcontroller.flowprogrammer.IFlowPusherService;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
+//import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PlanInstallModule implements IFloodlightModule {
+    protected volatile IFloodlightProviderService floodlightProvider;
+    protected volatile INetworkGraphService networkGraph;
+    protected volatile IDatagridService datagridService;
+    protected volatile IFlowPusherService flowPusher;
+    private PlanCalcRuntime planCalc;
+    private PlanInstallRuntime planInstall;
+    private EventListener eventListener;
+    private IEventChannel<Long, IntentStateList> intentStateChannel;
+    private final static Logger log = LoggerFactory.getLogger(PlanInstallModule.class);
+
+
+    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 {
+	floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+	networkGraph = context.getServiceImpl(INetworkGraphService.class);
+	datagridService = context.getServiceImpl(IDatagridService.class);
+	flowPusher = context.getServiceImpl(IFlowPusherService.class);
+//	NetworkGraph graph = networkGraph.getNetworkGraph();
+	planCalc = new PlanCalcRuntime();
+	planInstall = new PlanInstallRuntime(floodlightProvider, flowPusher);
+	eventListener = new EventListener();
+    }
+
+    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
+		    //      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);
+	    log("begin_computePlan");
+	    List<Set<FlowEntry>> plan = planCalc.computePlan(intents);
+	    log("end_computePlan");
+	    log.debug("Plan: {}", plan);
+	    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) {
+		IntentState newState;
+		switch(i.operator) {
+		case REMOVE:
+		    if(installed) {
+			newState = success ? IntentState.DEL_ACK : IntentState.DEL_PENDING;
+		    }
+		    else {
+			newState = IntentState.DEL_REQ;
+		    }
+		    break;
+		case ADD:
+		default:
+		    if(installed) {
+			newState = success ? IntentState.INST_ACK : IntentState.INST_NACK;
+		    }
+		    else {
+			newState = IntentState.INST_REQ;
+		    }
+		    break;
+		}
+		states.put(i.intent.getId(), newState);
+	    }
+	    intentStateChannel.addEntry(key, states);
+	    // 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);
+	    log("end_sendReceivedNotif");
+	    log("finish");
+
+	    log.debug("Added OperationList {}", value);
+	    try {
+		intentQueue.put(value);
+	    } catch (InterruptedException e) {
+		log.warn("Error putting to intent queue: {}", e.getMessage());
+	    }
+	}
+    }
+
+    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,
+				              IntentOperationList.class);
+	eventListener.start();
+	// start publisher
+	intentStateChannel = datagridService.createChannel(INTENT_STATE_EVENT_CHANNEL_NAME,
+						Long.class,
+						IntentStateList.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() {
+	// no services, for now
+	return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+	// no services, for now
+	return null;
+    }
+
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallRuntime.java b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallRuntime.java
new file mode 100644
index 0000000..e3bdc41
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallRuntime.java
@@ -0,0 +1,156 @@
+package net.onrc.onos.core.intent.runtime;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.internal.OFMessageFuture;
+import net.onrc.onos.core.intent.FlowEntry;
+import net.onrc.onos.ofcontroller.flowprogrammer.IFlowPusherService;
+//import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.util.Pair;
+
+import org.openflow.protocol.OFBarrierReply;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 
+ * @author Brian O'Connor <bocon@onlab.us>
+ *
+ */
+
+public class PlanInstallRuntime {
+//    NetworkGraph graph;
+    IFlowPusherService pusher;
+    IFloodlightProviderService provider;
+    private final static Logger log = LoggerFactory.getLogger(PlanInstallRuntime.class);
+
+    public PlanInstallRuntime(//NetworkGraph graph, 
+	    		      IFloodlightProviderService provider,
+	                      IFlowPusherService pusher) {
+//	this.graph = graph;
+	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());
+		if(sw == null) {
+		    // no active switch, skip this flow entry
+		    log.debug("Skipping flow entry: {}", entry);
+		    continue;
+		}
+		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
+	    List<Pair<IOFSwitch,OFMessageFuture<OFBarrierReply>>> barriers = new ArrayList<>();
+	    for(IOFSwitch sw : modifiedSwitches) {
+		barriers.add(new Pair<>(sw, pusher.barrierAsync(sw)));
+	    }
+	    for(Pair<IOFSwitch,OFMessageFuture<OFBarrierReply>> pair : barriers) {
+		IOFSwitch sw = pair.first;
+		OFMessageFuture<OFBarrierReply> future = pair.second;
+		try {
+		    future.get();
+		} catch (InterruptedException | ExecutionException e) {
+		    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;
+    }
+
+}