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/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;
+ }
+
+}