Merged conflicts
diff --git a/scripts/link.sh b/scripts/link.sh
index d87464b..dc202e7 100755
--- a/scripts/link.sh
+++ b/scripts/link.sh
@@ -1,4 +1,5 @@
#! /bin/bash
+
controller=`hostname`
switches=`ifconfig -a | grep sw |grep -v eth | awk '{print $1}'`
@@ -20,21 +21,26 @@
for s in $switches; do
dpid=`sudo ovs-ofctl show $s |grep dpid | awk '{print $4}'`
if [ "x$dpid" == "x$src_dpid" ]; then
- intfs=`sudo ovs-ofctl show $s |grep addr | awk '{print $1}' | sed 's/.*(//g' | sed 's/):$//g'`
- intf_list=()
- for i in $intfs; do
- intf_list+=($i)
- done
- intf=${intf_list[$src_port]}
- if [ x$cmd == "xup" ]; then
- echo "sudo ifconfig ${intf} up"
- sudo ifconfig ${intf} up
- elif [ x$cmd == "xdown" ]; then
- echo "sudo ifconfig ${intf} down"
- sudo ifconfig ${intf} down
- else
- echo "sudo ifconfig ${intf}"
- sudo ifconfig ${intf}
+
+# intf=`sudo ovs-ofctl show $s |grep addr | awk -v p=$src_port 'BEGIN {pat="^ "p"\("}
+# $0 ~ pat {w=match ($0, /\(.*\)/); if (w) print substr($0, RSTART+1, RLENGTH-2)}'`
+
+ sudo ovs-ofctl show $s |grep addr | sed 's/[\(\)]/,/g'>/tmp/baz.out
+ intf=`cat /tmp/baz.out | awk -v p=$src_port 'BEGIN {pat="^ "p","}
+ $0 ~ pat {w=match($0, /,.*,/); if (w) print substr($0, RSTART+1, RLENGTH-2)}'`
+
+ if [ x$intf != "x" ]; then
+ if [ x$cmd == "xup" ]; then
+ echo "sudo ifconfig ${intf} up"
+ sudo ifconfig ${intf} up
+ elif [ x$cmd == "xdown" ]; then
+ echo "sudo ifconfig ${intf} down"
+ sudo ifconfig ${intf} down
+ else
+ echo "sudo ifconfig ${intf}"
+ sudo ifconfig ${intf}
+ fi
+ break
fi
fi
done
diff --git a/src/main/java/net/floodlightcontroller/core/INetMapTopologyObjects.java b/src/main/java/net/floodlightcontroller/core/INetMapTopologyObjects.java
index be56ec2..18da3dd 100644
--- a/src/main/java/net/floodlightcontroller/core/INetMapTopologyObjects.java
+++ b/src/main/java/net/floodlightcontroller/core/INetMapTopologyObjects.java
@@ -283,26 +283,29 @@
@Property("actionOutput")
public void setActionOutput(Short actionOutput);
-
+
+ @Adjacency(label="flow")
+ public IFlowPath getFlow();
+
+ @Adjacency(label="flow")
+ public void setFlow(IFlowPath flow);
+
@Adjacency(label="switch")
public ISwitchObject getSwitch();
-
+
@Adjacency(label="switch")
public void setSwitch(ISwitchObject sw);
-
+
@Adjacency(label="inport")
public IPortObject getInPort();
-
+
@Adjacency(label="inport")
public void setInPort(IPortObject port);
-
+
@Adjacency(label="outport")
public IPortObject getOutPort();
-
- @Adjacency(label="switch")
+
+ @Adjacency(label="outport")
public void setOutPort(IPortObject port);
-
- @Adjacency(label="flow")
- public IFlowPath getFlowPath();
}
}
diff --git a/src/main/java/net/floodlightcontroller/flowcache/FlowManager.java b/src/main/java/net/floodlightcontroller/flowcache/FlowManager.java
index 257add8..0e37e17 100644
--- a/src/main/java/net/floodlightcontroller/flowcache/FlowManager.java
+++ b/src/main/java/net/floodlightcontroller/flowcache/FlowManager.java
@@ -5,6 +5,7 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -695,7 +696,11 @@
flowEntryObj.setType("flow_entry");
//
- // Set the Flow Entry attributes:
+ // Set the Flow Entry Edges and attributes:
+ // - Switch edge
+ // - InPort edge
+ // - OutPort edge
+ //
// - flowEntry.flowEntryMatch()
// - flowEntry.flowEntryActions()
// - flowEntry.dpid()
@@ -710,13 +715,14 @@
// - flowEntry.matchDstMac()
// - flowEntry.actionOutput()
//
- ISwitchObject sw = conn.utils().searchSwitch(conn,flowEntry.dpid().toString());
+ ISwitchObject sw =
+ conn.utils().searchSwitch(conn, flowEntry.dpid().toString());
flowEntryObj.setSwitchDpid(flowEntry.dpid().toString());
-
flowEntryObj.setSwitch(sw);
if (flowEntry.flowEntryMatch().matchInPort()) {
- IPortObject inport = conn.utils().searchPort(conn,flowEntry.dpid().toString(),
- flowEntry.flowEntryMatch().inPort().value());
+ IPortObject inport =
+ conn.utils().searchPort(conn, flowEntry.dpid().toString(),
+ flowEntry.flowEntryMatch().inPort().value());
flowEntryObj.setMatchInPort(flowEntry.flowEntryMatch().inPort().value());
flowEntryObj.setInPort(inport);
}
@@ -738,10 +744,12 @@
for (FlowEntryAction fa : flowEntry.flowEntryActions()) {
if (fa.actionOutput() != null) {
- IPortObject outport = conn.utils().searchPort(conn,flowEntry.dpid().toString(),
- fa.actionOutput().port().value());
- flowEntryObj.setActionOutput(fa.actionOutput().port().value());
- flowEntryObj.setOutPort(outport);
+ IPortObject outport =
+ conn.utils().searchPort(conn,
+ flowEntry.dpid().toString(),
+ fa.actionOutput().port().value());
+ flowEntryObj.setActionOutput(fa.actionOutput().port().value());
+ flowEntryObj.setOutPort(outport);
}
}
// TODO: Hacks with hard-coded state names!
@@ -751,18 +759,16 @@
flowEntryObj.setUserState("FE_USER_ADD");
flowEntryObj.setSwitchState("FE_SWITCH_NOT_UPDATED");
//
- // TODO: Take care of the FlowEntryMatch, FlowEntryAction set,
- // and FlowEntryErrorState.
+ // TODO: Take care of the FlowEntryErrorState.
//
// Flow Entries edges:
// Flow
- // NextFE
- // InPort
- // OutPort
- // Switch
- if (! found)
+ // NextFE (TODO)
+ if (! found) {
flowObj.addFlowEntry(flowEntryObj);
+ flowEntryObj.setFlow(flowObj);
+ }
}
conn.endTx(Transaction.COMMIT);
@@ -1116,12 +1122,29 @@
//
// Extract the Flow state
//
- flowPath.setFlowId(new FlowId(flowObj.getFlowId()));
- flowPath.setInstallerId(new CallerId(flowObj.getInstallerId()));
- flowPath.dataPath().srcPort().setDpid(new Dpid(flowObj.getSrcSwitch()));
- flowPath.dataPath().srcPort().setPort(new Port(flowObj.getSrcPort()));
- flowPath.dataPath().dstPort().setDpid(new Dpid(flowObj.getDstSwitch()));
- flowPath.dataPath().dstPort().setPort(new Port(flowObj.getDstPort()));
+ String flowIdStr = flowObj.getFlowId();
+ String installerIdStr = flowObj.getInstallerId();
+ String srcSwitchStr = flowObj.getSrcSwitch();
+ Short srcPortStr = flowObj.getSrcPort();
+ String dstSwitchStr = flowObj.getDstSwitch();
+ Short dstPortStr = flowObj.getDstPort();
+
+ if ((flowIdStr == null) ||
+ (installerIdStr == null) ||
+ (srcSwitchStr == null) ||
+ (srcPortStr == null) ||
+ (dstSwitchStr == null) ||
+ (dstPortStr == null)) {
+ // TODO: A work-around, becauuse of some bogus database objects
+ return null;
+ }
+
+ flowPath.setFlowId(new FlowId(flowIdStr));
+ flowPath.setInstallerId(new CallerId(installerIdStr));
+ flowPath.dataPath().srcPort().setDpid(new Dpid(srcSwitchStr));
+ flowPath.dataPath().srcPort().setPort(new Port(srcPortStr));
+ flowPath.dataPath().dstPort().setDpid(new Dpid(dstSwitchStr));
+ flowPath.dataPath().dstPort().setPort(new Port(dstPortStr));
//
// Extract all Flow Entries
@@ -1129,8 +1152,20 @@
Iterable<IFlowEntry> flowEntries = flowObj.getFlowEntries();
for (IFlowEntry flowEntryObj : flowEntries) {
FlowEntry flowEntry = new FlowEntry();
- flowEntry.setFlowEntryId(new FlowEntryId(flowEntryObj.getFlowEntryId()));
- flowEntry.setDpid(new Dpid(flowEntryObj.getSwitchDpid()));
+ String flowEntryIdStr = flowEntryObj.getFlowEntryId();
+ String switchDpidStr = flowEntryObj.getSwitchDpid();
+ String userState = flowEntryObj.getUserState();
+ String switchState = flowEntryObj.getSwitchState();
+
+ if ((flowEntryIdStr == null) ||
+ (switchDpidStr == null) ||
+ (userState == null) ||
+ (switchState == null)) {
+ // TODO: A work-around, becauuse of some bogus database objects
+ continue;
+ }
+ flowEntry.setFlowEntryId(new FlowEntryId(flowEntryIdStr));
+ flowEntry.setDpid(new Dpid(switchDpidStr));
//
// Extract the match conditions
@@ -1167,10 +1202,7 @@
actions.add(action);
}
flowEntry.setFlowEntryActions(actions);
-
- String userState = flowEntryObj.getUserState();
flowEntry.setFlowEntryUserState(FlowEntryUserState.valueOf(userState));
- String switchState = flowEntryObj.getSwitchState();
flowEntry.setFlowEntrySwitchState(FlowEntrySwitchState.valueOf(switchState));
//
// TODO: Take care of the FlowEntryMatch, FlowEntryAction set,
@@ -1185,7 +1217,7 @@
/**
* Add and maintain a shortest-path flow.
*
- * NOTE: The Flow Path does NOT contain all flow entries.
+ * NOTE: The Flow Path argument does NOT contain all flow entries.
* Instead, it contains a single dummy flow entry that is used to
* store the matching condition(s).
* That entry is replaced by the appropriate entries from the
@@ -1193,12 +1225,10 @@
*
* @param flowPath the Flow Path with the endpoints and the match
* conditions to install.
- * @param flowId the return-by-reference Flow ID as assigned internally.
- * @return true on success, otherwise false.
+ * @return the added shortest-path flow on success, otherwise null.
*/
@Override
- public boolean addAndMaintainShortestPathFlow(FlowPath flowPath,
- FlowId flowId) {
+ public FlowPath addAndMaintainShortestPathFlow(FlowPath flowPath) {
//
// Do the shortest path computation
//
@@ -1206,7 +1236,7 @@
topoRouteService.getShortestPath(flowPath.dataPath().srcPort(),
flowPath.dataPath().dstPort());
if (dataPath == null)
- return false;
+ return null;
FlowEntryMatch userFlowEntryMatch = null;
if (! flowPath.dataPath().flowEntries().isEmpty()) {
@@ -1241,16 +1271,18 @@
//
// Prepare the computed Flow Path
//
- FlowPath resultFlowPath = new FlowPath();
- resultFlowPath.setFlowId(new FlowId(flowPath.flowId().value()));
- resultFlowPath.setInstallerId(new CallerId(flowPath.installerId().value()));
- resultFlowPath.setDataPath(dataPath);
+ FlowPath computedFlowPath = new FlowPath();
+ computedFlowPath.setFlowId(new FlowId(flowPath.flowId().value()));
+ computedFlowPath.setInstallerId(new CallerId(flowPath.installerId().value()));
+ computedFlowPath.setDataPath(dataPath);
- boolean returnValue = addFlow(resultFlowPath, flowId);
+ FlowId flowId = new FlowId();
+ if (! addFlow(computedFlowPath, flowId))
+ return null;
// TODO: Mark the flow for maintenance purpose
- return (returnValue);
+ return (computedFlowPath);
}
/**
@@ -1261,6 +1293,7 @@
* @param src_port the source port.
* @param dest_port the destination port.
*/
+ @Override
public void createFlow(IPortObject src_port, IPortObject dest_port) {
// TODO: We don't need it for now.
}
@@ -1274,6 +1307,7 @@
* @param dest_port the destination port to match.
* @return all flows matching the source and the destination port.
*/
+ @Override
public Iterable<FlowPath> getFlows(IPortObject src_port,
IPortObject dest_port) {
// TODO: Pankaj might be implementing it later.
@@ -1288,23 +1322,123 @@
* @param port the port to match.
* @return the list of flows that are going out from the port.
*/
+ @Override
public Iterable<FlowPath> getFlows(IPortObject port) {
// TODO: We need it now: Pankaj
return null;
}
/**
- * Reconcile all flows on inactive port (src port of link which might be
- * broken).
+ * Reconcile all flows on inactive switch port.
*
- * TODO: We need it now: Pavlin
- *
- * @param src_port the port that has become inactive.
+ * @param portObject the port that has become inactive.
*/
- public void reconcileFlows(IPortObject src_port) {
- // TODO: We need it now: Pavlin
+ @Override
+ public void reconcileFlows(IPortObject portObject) {
+ Iterable<IFlowEntry> inFlowEntries = portObject.getInFlowEntries();
+ Iterable<IFlowEntry> outFlowEntries = portObject.getOutFlowEntries();
- // TODO: It should call installFlowEntry() as appropriate.
+ //
+ // Collect all affected Flow IDs from the affected flow entries
+ //
+ HashSet<IFlowPath> flowObjSet = new HashSet<IFlowPath>();
+ for (IFlowEntry flowEntryObj: inFlowEntries) {
+ IFlowPath flowObj = flowEntryObj.getFlow();
+ if (flowObj != null)
+ flowObjSet.add(flowObj);
+ }
+ for (IFlowEntry flowEntryObj: outFlowEntries) {
+ IFlowPath flowObj = flowEntryObj.getFlow();
+ if (flowObj != null)
+ flowObjSet.add(flowObj);
+ }
+ // conn.endTx(Transaction.COMMIT);
+
+ //
+ // Remove the old Flow Entries, and add the new Flow Entries
+ //
+ Map<Long, IOFSwitch> mySwitches = floodlightProvider.getSwitches();
+ for (IFlowPath flowObj : flowObjSet) {
+ FlowPath flowPath = extractFlowPath(flowObj);
+ if (flowPath == null)
+ continue;
+
+ //
+ // Remove my Flow Entries from the Network MAP
+ //
+ Iterable<IFlowEntry> flowEntries = flowObj.getFlowEntries();
+ for (IFlowEntry flowEntryObj : flowEntries) {
+ String dpidStr = flowEntryObj.getSwitchDpid();
+ if (dpidStr == null)
+ continue;
+ Dpid dpid = new Dpid(dpidStr);
+ IOFSwitch mySwitch = mySwitches.get(dpid.value());
+ if (mySwitch == null)
+ continue; // Ignore the entry: not my switch
+ flowObj.removeFlowEntry(flowEntryObj);
+ conn.utils().removeFlowEntry(conn, flowEntryObj);
+ }
+
+ //
+ // Delete the flow entries from the switches
+ //
+ for (FlowEntry flowEntry : flowPath.dataPath().flowEntries()) {
+ flowEntry.setFlowEntryUserState(FlowEntryUserState.FE_USER_DELETE);
+ IOFSwitch mySwitch = mySwitches.get(flowEntry.dpid().value());
+ if (mySwitch == null) {
+ // Not my switch
+ installRemoteFlowEntry(flowEntry);
+ } else {
+ installFlowEntry(mySwitch, flowEntry);
+ }
+ }
+
+ //
+ // Calculate the new shortest path and install it in the
+ // Network MAP.
+ //
+ FlowPath addedFlowPath = addAndMaintainShortestPathFlow(flowPath);
+ if (addedFlowPath == null) {
+ log.error("Cannot add Shortest Path Flow from {} to {}: path not found?",
+ flowPath.dataPath().srcPort().toString(),
+ flowPath.dataPath().dstPort().toString());
+ continue;
+ }
+
+ //
+ // Add the flow entries to the switches
+ //
+ for (FlowEntry flowEntry : addedFlowPath.dataPath().flowEntries()) {
+ flowEntry.setFlowEntryUserState(FlowEntryUserState.FE_USER_ADD);
+ IOFSwitch mySwitch = mySwitches.get(flowEntry.dpid().value());
+ if (mySwitch == null) {
+ // Not my switch
+ installRemoteFlowEntry(flowEntry);
+ continue;
+ }
+
+ IFlowEntry flowEntryObj =
+ conn.utils().searchFlowEntry(conn, flowEntry.flowEntryId());
+ if (flowEntryObj == null) {
+ //
+ // TODO: Remove the "new Object[] wrapper in the statement
+ // below after the SLF4J logger is upgraded to
+ // Version 1.7.5
+ //
+ log.error("Cannot add Flow Entry to switch {} for Path Flow from {} to {} : Flow Entry not in the Network MAP",
+ new Object[] {
+ flowEntry.dpid(),
+ flowPath.dataPath().srcPort(),
+ flowPath.dataPath().dstPort()
+ });
+ continue;
+ }
+ // Update the Flow Entry Switch State in the Network MAP
+ if (installFlowEntry(mySwitch, flowEntry)) {
+ flowEntryObj.setSwitchState("FE_SWITCH_UPDATED");
+ }
+ }
+ }
}
/**
@@ -1315,6 +1449,7 @@
* @param src_port the source port.
* @param dest_port the destination port.
*/
+ @Override
public void reconcileFlow(IPortObject src_port, IPortObject dest_port) {
// TODO: We don't need it for now.
}
@@ -1331,6 +1466,7 @@
* Installer ID, and any additional matching conditions for the
* flow entries (e.g., source or destination MAC address, etc).
*/
+ @Override
public FlowPath computeFlowPath(IPortObject src_port,
IPortObject dest_port) {
//
@@ -1393,6 +1529,7 @@
* @param flow the flow whose flow entries should be returned.
* @return the flow entries of the flow.
*/
+ @Override
public Iterable<FlowEntry> getFlowEntries(FlowPath flow) {
return flow.dataPath().flowEntries();
}
@@ -1400,19 +1537,12 @@
/**
* Install a Flow Entry on a switch.
*
- * @param mySwitches the DPID-to-Switch mapping for the switches
- * controlled by this controller.
+ * @param mySwitch the switch to install the Flow Entry into.
* @param flowEntry the flow entry to install.
* @return true on success, otherwise false.
*/
- public boolean installFlowEntry(Map<Long, IOFSwitch> mySwitches,
- FlowEntry flowEntry) {
- IOFSwitch mySwitch = mySwitches.get(flowEntry.dpid().value());
- if (mySwitch == null) {
- // Not my switch
- return (installRemoteFlowEntry(flowEntry));
- }
-
+ @Override
+ public boolean installFlowEntry(IOFSwitch mySwitch, FlowEntry flowEntry) {
//
// Create the OpenFlow Flow Modification Entry to push
//
@@ -1528,18 +1658,17 @@
/**
* Remove a Flow Entry from a switch.
*
- * TODO: We need it now: Pavlin
- * - Remove only for local switches
- * - It will call the removeRemoteFlowEntry() for remote switches.
- * - To be called by reconcileFlow()
- *
- * @param entry the flow entry to remove.
+ * @param mySwitch the switch to remove the Flow Entry from.
+ * @param flowEntry the flow entry to remove.
+ * @return true on success, otherwise false.
*/
- public void removeFlowEntry(FlowEntry entry) {
- // TODO: We need it now: Pavlin
- // - Remove only for local switches
- // - It will call the removeRemoteFlowEntry() for remote switches.
- // - To be called by reconcileFlow()
+ @Override
+ public boolean removeFlowEntry(IOFSwitch mySwitch, FlowEntry flowEntry) {
+ //
+ // The installFlowEntry() method implements both installation
+ // and removal of flow entries.
+ //
+ return (installFlowEntry(mySwitch, flowEntry));
}
/**
@@ -1549,10 +1678,11 @@
* - For now it will make a REST call to the remote controller.
* - Internally, it needs to know the name of the remote controller.
*
- * @param entry the flow entry to install.
+ * @param flowEntry the flow entry to install.
* @return true on success, otherwise false.
*/
- public boolean installRemoteFlowEntry(FlowEntry entry) {
+ @Override
+ public boolean installRemoteFlowEntry(FlowEntry flowEntry) {
// TODO: We need it now: Jono
// - For now it will make a REST call to the remote controller.
// - Internally, it needs to know the name of the remote controller.
@@ -1562,15 +1692,15 @@
/**
* Remove a flow entry on a remote controller.
*
- * TODO: We need it now: Jono
- * - For now it will make a REST call to the remote controller.
- * - Internally, it needs to know the name of the remote controller.
- *
- * @param entry the flow entry to remove.
+ * @param flowEntry the flow entry to remove.
+ * @return true on success, otherwise false.
*/
- public void removeRemoteFlowEntry(FlowEntry entry) {
- // TODO: We need it now: Jono
- // - For now it will make a REST call to the remote controller.
- // - Internally, it needs to know the name of the remote controller.
+ @Override
+ public boolean removeRemoteFlowEntry(FlowEntry flowEntry) {
+ //
+ // The installRemoteFlowEntry() method implements both installation
+ // and removal of flow entries.
+ //
+ return (installRemoteFlowEntry(flowEntry));
}
}
diff --git a/src/main/java/net/floodlightcontroller/flowcache/IFlowService.java b/src/main/java/net/floodlightcontroller/flowcache/IFlowService.java
index 45cdde0..b6df1e2 100644
--- a/src/main/java/net/floodlightcontroller/flowcache/IFlowService.java
+++ b/src/main/java/net/floodlightcontroller/flowcache/IFlowService.java
@@ -86,7 +86,7 @@
/**
* Add and maintain a shortest-path flow.
*
- * NOTE: The Flow Path does NOT contain all flow entries.
+ * NOTE: The Flow Path argument does NOT contain all flow entries.
* Instead, it contains a single dummy flow entry that is used to
* store the matching condition(s).
* That entry is replaced by the appropriate entries from the
@@ -94,9 +94,7 @@
*
* @param flowPath the Flow Path with the endpoints and the match
* conditions to install.
- * @param flowId the return-by-reference Flow ID as assigned internally.
- * @return true on success, otherwise false.
+ * @return the added shortest-path flow on success, otherwise null.
*/
- public boolean addAndMaintainShortestPathFlow(FlowPath flowPath,
- FlowId flowId);
+ public FlowPath addAndMaintainShortestPathFlow(FlowPath flowPath);
}
diff --git a/src/main/java/net/floodlightcontroller/flowcache/web/AddShortestPathFlowResource.java b/src/main/java/net/floodlightcontroller/flowcache/web/AddShortestPathFlowResource.java
index e00a4b5..3c5bcd8 100644
--- a/src/main/java/net/floodlightcontroller/flowcache/web/AddShortestPathFlowResource.java
+++ b/src/main/java/net/floodlightcontroller/flowcache/web/AddShortestPathFlowResource.java
@@ -52,10 +52,12 @@
// Process the request
if (flowPath != null) {
- if (flowService.addAndMaintainShortestPathFlow(flowPath, result)
- != true) {
+ FlowPath addedFlowPath =
+ flowService.addAndMaintainShortestPathFlow(flowPath);
+ if (addedFlowPath == null)
result = new FlowId(); // Error: Return empty Flow Id
- }
+ else
+ result = addedFlowPath.flowId();
}
return result;
diff --git a/src/main/java/net/floodlightcontroller/util/FlowEntry.java b/src/main/java/net/floodlightcontroller/util/FlowEntry.java
index 56a1631..2e61636 100644
--- a/src/main/java/net/floodlightcontroller/util/FlowEntry.java
+++ b/src/main/java/net/floodlightcontroller/util/FlowEntry.java
@@ -23,7 +23,7 @@
* support multiple in-ports and multiple out-ports.
*/
public class FlowEntry {
- private FlowId flowId; //FlowID of flowEntry
+ private FlowId flowId; // FlowID of flowEntry
private FlowEntryId flowEntryId; // The Flow Entry ID
private FlowEntryMatch flowEntryMatch; // The Flow Entry Match
private ArrayList<FlowEntryAction> flowEntryActions; // The Flow Entry Actions
@@ -127,6 +127,22 @@
}
/**
+ * Get the Flow ID.
+ * @return the Flow ID.
+ */
+ @JsonIgnore
+ public FlowId getFlowId() { return flowId; }
+
+ /**
+ * Set the Flow ID.
+ *
+ * @param flowId the Flow ID to set.
+ */
+ public void setFlowId(FlowId flowId) {
+ this.flowId = flowId;
+ }
+
+ /**
* Get the Flow Entry ID.
*
* @return the Flow Entry ID.
@@ -329,19 +345,4 @@
return ret;
}
-
- /**
- * @return the flowId
- */
- @JsonIgnore
- public FlowId getFlowId() {
- return flowId;
- }
-
- /**
- * @param flowId the flowId to set
- */
- public void setFlowId(FlowId flowId) {
- this.flowId = flowId;
- }
}
diff --git a/src/main/java/net/onrc/onos/flow/IFlowManager.java b/src/main/java/net/onrc/onos/flow/IFlowManager.java
index 1c90e56..d163760 100644
--- a/src/main/java/net/onrc/onos/flow/IFlowManager.java
+++ b/src/main/java/net/onrc/onos/flow/IFlowManager.java
@@ -41,14 +41,11 @@
public Iterable<FlowPath> getOutFlows(IPortObject port);
/**
- * Reconcile all flows on inactive port (src port of link which might be
- * broken).
+ * Reconcile all flows on inactive switch port.
*
- * TODO: We need it now: Pavlin
- *
- * @param src_port the port that has become inactive.
+ * @param portObject the port that has become inactive.
*/
- public void reconcileFlows(IPortObject src_port);
+ public void reconcileFlows(IPortObject portObject);
/**
* Reconcile all flows between a source and a destination port.
@@ -63,12 +60,14 @@
/**
* Compute the shortest path between a source and a destination ports.
*
- * TODO: We need it now: Pavlin
- *
* @param src_port the source port.
* @param dest_port the destination port.
* @return the computed shortest path between the source and the
- * destination ports.
+ * destination ports. The flow entries in the path itself would
+ * contain the incoming port matching and the outgoing port output
+ * actions set. However, the path itself will NOT have the Flow ID,
+ * Installer ID, and any additional matching conditions for the
+ * flow entries (e.g., source or destination MAC address, etc).
*/
public FlowPath computeFlowPath(IPortObject src_port,
IPortObject dest_port);
@@ -76,8 +75,6 @@
/**
* Get all Flow Entries of a Flow.
*
- * TODO: We need it now: Pavlin
- *
* @param flow the flow whose flow entries should be returned.
* @return the flow entries of the flow.
*/
@@ -86,30 +83,20 @@
/**
* Install a Flow Entry on a switch.
*
- * TODO: We need it now: Pavlin
- * - Install only for local switches
- * - It will call the installRemoteFlowEntry() for remote switches.
- * - To be called by reconcileFlow()
- *
- * @param mySwitches the DPID-to-Switch mapping for the switches
- * controlled by this controller.
+ * @param mySwitch the switch to install the Flow Entry into.
* @param flowEntry the flow entry to install.
* @return true on success, otherwise false.
*/
- public boolean installFlowEntry(Map<Long, IOFSwitch> mySwitches,
- FlowEntry flowEntry);
+ public boolean installFlowEntry(IOFSwitch mySwitch, FlowEntry flowEntry);
/**
* Remove a Flow Entry from a switch.
*
- * TODO: We need it now: Pavlin
- * - Remove only for local switches
- * - It will call the removeRemoteFlowEntry() for remote switches.
- * - To be called by reconcileFlow()
- *
- * @param entry the flow entry to remove.
+ * @param mySwitch the switch to remove the Flow Entry from.
+ * @param flowEntry the flow entry to remove.
+ * @return true on success, otherwise false.
*/
- public void removeFlowEntry(FlowEntry entry);
+ public boolean removeFlowEntry(IOFSwitch mySwitch, FlowEntry flowEntry);
/**
* Install a Flow Entry on a remote controller.
@@ -118,19 +105,16 @@
* - For now it will make a REST call to the remote controller.
* - Internally, it needs to know the name of the remote controller.
*
- * @param entry the flow entry to install.
+ * @param flowEntry the flow entry to install.
* @return true on success, otherwise false.
*/
- public boolean installRemoteFlowEntry(FlowEntry entry);
+ public boolean installRemoteFlowEntry(FlowEntry flowEntry);
/**
* Remove a flow entry on a remote controller.
*
- * TODO: We need it now: Jono
- * - For now it will make a REST call to the remote controller.
- * - Internally, it needs to know the name of the remote controller.
- *
- * @param entry the flow entry to remove.
+ * @param flowEntry the flow entry to remove.
+ * @return true on success, otherwise false.
*/
- public void removeRemoteFlowEntry(FlowEntry entry);
+ public boolean removeRemoteFlowEntry(FlowEntry flowEntry);
}
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 95786fd..48834fb 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,13 @@
+** March 28, 2013 **
+- basic flow chooser
+ - click in "eye" to show full list
+ - click on "eye" in full list to monitor that flow in the top slot (and show the flow in topology)
+ - other flows get pushed down one slot
+ - when a flow is pushed off the list, it is no longer displayed in topology
+- bug fix for link disappearing after being added
+- color improvements
+- draw vector while linking to make it clearer what's going to happen
+
** March 27, 2013 **
- click onos node "eye" icon to highlight switches associated with that controller
- double click onos node else where to activate/deactivate
diff --git a/web/ons-demo/assets/eye.svg b/web/ons-demo/assets/black-eye.svg
similarity index 100%
rename from web/ons-demo/assets/eye.svg
rename to web/ons-demo/assets/black-eye.svg
diff --git a/web/ons-demo/assets/white-eye.svg b/web/ons-demo/assets/white-eye.svg
new file mode 100644
index 0000000..8f3b180
--- /dev/null
+++ b/web/ons-demo/assets/white-eye.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="512"
+ height="512"
+ viewBox="0 0 512 512"
+ id="Layer_1"
+ xml:space="preserve"><metadata
+ id="metadata13"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs11" />
+<g
+ id="g3"
+ style="fill:#ffffff;fill-opacity:1">
+ <path
+ d="m 506.637,242.501 c -5.362,-6.347 -11.263,-12.33 -17.171,-18.193 -31.897,-31.679 -68.549,-59.921 -108.648,-80.411 -25.618,-13.08 -53.038,-23.655 -81.451,-28.721 -14.453,-2.586 -28.617,-3.912 -43.474,-3.938 -14.447,0.025 -28.908,1.353 -43.361,3.938 -28.412,5.065 -55.775,15.641 -81.393,28.721 -40.102,20.489 -76.724,48.733 -108.622,80.411 -5.909,5.862 -11.794,11.847 -17.155,18.193 -7.147,8.484 -7.147,18.515 0,27 16.344,19.353 35.774,36.575 55.542,52.321 42.57,33.915 91.25,62.278 144.993,73.711 16.621,3.524 33.299,5.244 49.998,5.228 16.904,0.018 33.488,-1.702 50.107,-5.228 53.744,-11.433 102.534,-39.796 145.104,-73.711 19.768,-15.745 39.194,-32.969 55.538,-52.321 7.146,-8.483 7.139,-18.514 -0.007,-27 z M 255.892,354.552 c -54.334,-0.104 -98.348,-44.177 -98.348,-98.554 0,-54.351 44.014,-98.438 98.348,-98.543 54.809,0.104 98.347,44.192 98.347,98.543 0.001,54.376 -43.538,98.447 -98.347,98.554 z"
+ id="path5"
+ style="fill:#ffffff;fill-opacity:1" />
+ <path
+ d="m 255.86,217.881 c -21.06,0 -38.106,17.059 -38.106,38.115 0,21.068 17.047,38.123 38.106,38.123 21.058,0 38.124,-17.055 38.124,-38.123 0,-21.056 -17.067,-38.115 -38.124,-38.115 z"
+ id="path7"
+ style="fill:#ffffff;fill-opacity:1" />
+</g>
+</svg>
\ No newline at end of file
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index e46f165..018e728 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -44,25 +44,33 @@
-webkit-box-orient: vertical;
}
+#flowChooser {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 100%;
+ display: -webkit-box;
+ overflow: scroll;
+}
+
.selectedFlow {
display: -webkit-box;
+ position: relative;
+}
+
+#showFlowChooser {
+ position: relative;
+ display: -webkit-box;
+ -webkit-box-pack: center;
+}
+
+.selectedFlow .srcDPID, .selectedFlow .dstDPID {
-webkit-user-select: auto;
}
#selectedFlowsHeader {
display: -webkit-box;
-}
-
-.flowIndex {
- width: 1.5em;
-}
-
-.flowId {
- width: 5em;
-}
-
-.srcDPID, .dstDPID {
- width: 20em;
+ height: 1.5em;
}
.iperf {
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 579d940..aebf68b 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -6,6 +6,14 @@
margin: 0px;
}
+#topology.linking {
+ cursor: crosshair;
+}
+
+.nodrop {
+ cursor: not-allowed;
+}
+
.status {
padding: 1em;
}
@@ -87,23 +95,40 @@
background-color: black;
}
+#flowChooser .selectedFlow {
+ background-color: rgba(255, 255, 255, .75);
+ color: black;
+}
+
+#flowChooser .flowId {
+ padding-left: 2em;
+}
+
+
.selectedFlow.selected {
color: black;
background-color:#AAA;
}
-circle.highlight {
+.highlight circle {
stroke: rgba(255, 255, 255, .5);
stroke-width: 2px;
}
+#linkVector {
+ fill: none;
+ stroke-width: 1px;
+ stroke: rgba(255, 255, 255, .75);
+ pointer-events: none;
+}
+
path {
pointer-events: none;
}
path.flow {
fill: none;
- stroke-width: 3px;
+ stroke-width: 5px;
stroke: rgba(255, 255, 255, .35);
}
@@ -111,13 +136,26 @@
border-top: 1px solid #AAA;
}
-.flowIndex, .flowId, .srcDPID, .dstDPID, .iperf {
+#flowChooser {
+ pointer-events: none;
+ background-color: rgba(0, 0, 0, .25);
+}
+
+
+.flowId, .srcDPID, .dstDPID, .iperf {
display: -webkit-box;
-webkit-box-pack: center;
-webkit-box-align: center;
+ width: 3em;
}
-.flowIndex, .flowId, .srcDPID, .dstDPID {
+
+.srcDPID, .dstDPID {
+ width: 12em;
+}
+
+
+.flowId, .srcDPID, .dstDPID {
border-right: 1px solid #AAA;
}
@@ -132,13 +170,22 @@
position: relative;
}
-.controllerEye {
+.black-eye {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
- width: 2em;
- background-image: url('../assets/eye.svg');
+ width: 2.25em;
+ background-image: url('../assets/black-eye.svg');
+ background-size: auto 100%;
+ background-repeat: no-repeat;
+ background-position: .25em center;
+}
+
+.white-eye {
+ height: 100%;
+ width: 2.25em;
+ background-image: url('../assets/white-eye.svg');
background-size: auto 100%;
background-repeat: no-repeat;
background-position: .25em center;
@@ -202,14 +249,14 @@
}
.color1 {
- opacity: .2;
+ opacity: .15;
pointer-events: none;
fill: #EC0033;
background-color: #EC0033;
}
.color2 {
- opacity: .2;
+ opacity: .15;
fill: #FFBA00;
background-color: #FFBA00;
}
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index e89f409..d157ecc 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -38,13 +38,13 @@
</div>
</div>
<div id='selectedFlowsHeader'>
- <div class='flowIndex'></div>
- <div class='flowId'>flow id</div>
+ <div id='showFlowChooser' class='flowId'><div class='white-eye'></div></div>
<div class='srcDPID'>src</div>
<div class='dstDPID'>dst</div>
<div class='iperf'>iperf</div>
</div>
<div id='selectedFlows'></div>
+<div id='flowChooser'></div>
<script src="js/app.js"></script>
</body>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index daf767c..0fa5fa3 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -15,7 +15,7 @@
});
var model;
-var svg, selectedFlowsView;
+var svg;
var updateTopology;
var pendingLinks = {};
@@ -24,13 +24,10 @@
'color2',
'color3',
'color4',
- 'color5',
- 'color6',
'color7',
'color8',
'color9',
- 'color10',
- 'color11',
+// 'color11',
'color12'
];
colors.reverse();
@@ -49,7 +46,7 @@
window.addEventListener('resize', function () {
// this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
// either that or is it possible to position the paths so they get the automatic transform as well?
-// updateTopology(svg, model);
+// updateTopology();
});
var svg = d3.select('#svg-container').append('svg:svg');
@@ -68,26 +65,31 @@
attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
}
-var selectedFlowsData = [
- {selected: false, flow: null},
- {selected: false, flow: null},
- {selected: false, flow: null}
-];
+var selectedFlows = [null, null, null];
function drawFlows() {
// DRAW THE FLOWS
- var flows = d3.select('svg').selectAll('.flow').data(selectedFlowsData, function (d) {
- return d.flow ? d.flow.flowId.value : null;
+ var flows = d3.select('svg').selectAll('.flow').data(selectedFlows, function (d) {
+ return d ? d.flowId.value : null;
});
- flows.enter().append("svg:path")
- .attr('class', 'flow')
- .attr('d', function (d) {
- if (!d.flow) {
+ flows.enter().append("svg:path").attr('class', 'flow')
+ .attr('stroke-dasharray', '4, 10')
+ .append('svg:animate')
+ .attr('attributeName', 'stroke-dashoffset')
+ .attr('attributeType', 'xml')
+ .attr('from', '500')
+ .attr('to', '-500')
+ .attr('dur', '20s')
+ .attr('repeatCount', 'indefinite');
+
+
+ flows.attr('d', function (d) {
+ if (!d) {
return;
}
var pts = [];
- d.flow.dataPath.flowEntries.forEach(function (flowEntry) {
+ d.dataPath.flowEntries.forEach(function (flowEntry) {
var s = d3.select(document.getElementById(flowEntry.dpid.value));
var pt = document.querySelector('svg').createSVGPoint();
pt.x = s.attr('x');
@@ -97,102 +99,111 @@
});
return line(pts);
})
- .attr('stroke-dasharray', '3, 10')
- .append('svg:animate')
- .attr('attributeName', 'stroke-dashoffset')
- .attr('attributeType', 'xml')
- .attr('from', '500')
- .attr('to', '-500')
- .attr('dur', '20s')
- .attr('repeatCount', 'indefinite');
-
- flows.style('visibility', function (d) {
- if (d) {
- return d.selected ? '' : 'hidden';
- }
- })
// "marching ants"
- // TODO: this will only be true if there's an iperf session running
- flows.select('animate').attr('from', function (d) {
- if (d.flow) {
- if (d.selected) {
- return '500';
- } else {
- return '-500';
- }
- }
- });
+ flows.select('animate').attr('from', 500);
+
+ flows.exit().remove();
}
-function updateFlowView() {
- selectedFlowsView.data(selectedFlowsData);
-
- selectedFlowsView.classed('selected', function (d) {
- if (d.flow) {
- return d.selected;
- }
- });
-
- selectedFlowsView.select('.flowId')
- .text(function (d) {
- if (d.flow) {
- return d.flow.flowId.value;
- }
- });
-
- selectedFlowsView.select('.srcDPID')
- .text(function (d) {
- if (d.flow) {
- return d.flow.dataPath.srcPort.dpid.value;
- }
- });
-
- selectedFlowsView.select('.dstDPID')
- .text(function (d) {
- if (d.flow) {
- return d.flow.dataPath.dstPort.dpid.value;
- }
- });
-}
-
-function createFlowView() {
- function rowEnter(d, i) {
+function showFlowChooser() {
+ function rowEnter(d) {
var row = d3.select(this);
- row.on('click', function () {
- selectedFlowsData[i].selected = !selectedFlowsData[i].selected;
- updateFlowView();
- drawFlows();
- });
-
row.append('div')
- .classed('flowIndex', true)
- .text(function () {
- return i+1;
+ .classed('black-eye', true).
+ on('click', function () {
+ selectedFlows.unshift(d);
+ selectedFlows = selectedFlows.slice(0, 3);
+
+ updateSelectedFlows();
+ updateTopology();
});
row.append('div')
- .classed('flowId', true);
+ .classed('flowId', true)
+ .text(function (d) {
+ return d.flowId.value;
+ });
row.append('div')
- .classed('srcDPID', true);
+ .classed('srcDPID', true)
+ .text(function (d) {
+ return d.dataPath.srcPort.dpid.value;
+ });
+
row.append('div')
- .classed('dstDPID', true);
+ .classed('dstDPID', true)
+ .text(function (d) {
+ return d.dataPath.dstPort.dpid.value;
+ });
- row.append('div')
- .classed('iperf', true);
}
- var flows = d3.select('#selectedFlows')
+ var flows = d3.select('#flowChooser')
+ .append('div')
+ .style('pointer-events', 'auto')
.selectAll('.selectedFlow')
- .data(selectedFlowsData)
+ .data(model.flows)
.enter()
.append('div')
.classed('selectedFlow', true)
.each(rowEnter);
+ setTimeout(function () {
+ d3.select(document.body).on('click', function () {
+ d3.select('#flowChooser').html('');
+ d3.select(document.body).on('click', null);
+ });
+ }, 0);
+}
+
+function updateSelectedFlows() {
+ function rowEnter(d) {
+ var row = d3.select(this);
+ row.append('div').classed('flowId', true);
+ row.append('div').classed('srcDPID', true);
+ row.append('div').classed('dstDPID', true);
+ row.append('div').classed('iperf', true);
+ }
+
+ function rowUpdate(d) {
+ var row = d3.select(this);
+ row.select('.flowId')
+ .text(function (d) {
+ if (d) {
+ return d.flowId.value;
+ }
+ });
+
+ row.select('.srcDPID')
+ .text(function (d) {
+ if (d) {
+ return d.dataPath.srcPort.dpid.value;
+ }
+ });
+
+ row.select('.dstDPID')
+ .text(function (d) {
+ if (d) {
+ return d.dataPath.dstPort.dpid.value;
+ }
+ });
+ }
+
+ var flows = d3.select('#selectedFlows')
+ .selectAll('.selectedFlow')
+ .data(selectedFlows);
+
+ flows.enter()
+ .append('div')
+ .classed('selectedFlow', true)
+ .each(rowEnter);
+
+ flows.each(rowUpdate);
+
+ flows.exit().remove();
return flows;
}
@@ -329,12 +340,8 @@
return linkMap;
}
-updateTopology = function(svg, model) {
-
- // DRAW THE SWITCHES
- var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
-
-
+// removes links from the pending list that are now in the model
+function reconcilePendingLinks(model) {
var links = [];
model.links.forEach(function (link) {
links.push(link);
@@ -344,19 +351,32 @@
for (linkId in pendingLinks) {
links.push(pendingLinks[linkId]);
}
+ return links
+}
+updateTopology = function() {
+
+ // DRAW THE SWITCHES
+ var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
+
+ var links = reconcilePendingLinks(model);
var linkMap = createLinkMap(links);
// var flowMap = createFlowMap(model);
function mouseOverSwitch(data) {
+
+ d3.event.preventDefault();
+
+ d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+
if (data.highlighted) {
return;
}
// only highlight valid link or flow destination by checking for class of existing highlighted circle
- var highlighted = svg.selectAll('circle.highlight')[0];
+ var highlighted = svg.selectAll('.highlight')[0];
if (highlighted.length == 1) {
- var s = d3.select(highlighted[0]);
+ var s = d3.select(highlighted[0]).select('circle');
// only allow links
// edge->edge (flow)
// aggregation->core
@@ -380,21 +400,20 @@
data.target = true;
}
-
- d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
var node = d3.select(document.getElementById(data.dpid));
- node.select('circle').classed('highlight', true).transition().duration(100).attr("r", widths.core);
+ node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
data.highlighted = true;
node.moveToFront();
}
function mouseOutSwitch(data) {
+ d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+
if (data.mouseDown)
return;
- d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
var node = d3.select(document.getElementById(data.dpid));
- node.select('circle').classed('highlight', false).transition().duration(100).attr("r", widths[data.className]);
+ node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
data.highlighted = false;
data.target = false;
}
@@ -402,12 +421,42 @@
function mouseDownSwitch(data) {
mouseOverSwitch(data);
data.mouseDown = true;
+ d3.select('#topology').classed('linking', true);
+
+ d3.select('svg')
+ .append('svg:path')
+ .attr('id', 'linkVector')
+ .attr('d', function () {
+ var s = d3.select(document.getElementById(data.dpid));
+
+ var pt = document.querySelector('svg').createSVGPoint();
+ pt.x = s.attr('x');
+ pt.y = s.attr('y');
+ pt = pt.matrixTransform(s[0][0].getCTM());
+
+ return line([pt, pt]);
+ });
+
+
+ if (data.className === 'core') {
+ d3.selectAll('.edge').classed('nodrop', true);
+ }
+ if (data.className === 'edge') {
+ d3.selectAll('.core').classed('nodrop', true);
+ d3.selectAll('.aggregation').classed('nodrop', true);
+ }
+ if (data.className === 'aggregation') {
+ d3.selectAll('.edge').classed('nodrop', true);
+ d3.selectAll('.aggregation').classed('nodrop', true);
+ }
}
function mouseUpSwitch(data) {
if (data.mouseDown) {
data.mouseDown = false;
+ d3.select('#topology').classed('linking', false);
d3.event.stopPropagation();
+ d3.selectAll('.nodrop').classed('nodrop', false);
}
}
@@ -515,14 +564,60 @@
// always on top
var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
+ d3.select(document.body).on('mousemove', function () {
+ if (!d3.select('#topology').classed('linking')) {
+ return;
+ }
+ var linkVector = document.getElementById('linkVector');
+ if (!linkVector) {
+ return;
+ }
+ linkVector = d3.select(linkVector);
+
+ var highlighted = svg.selectAll('.highlight')[0];
+ var s1 = null, s2 = null;
+ if (highlighted.length > 1) {
+ var s1 = d3.select(highlighted[0]);
+ var s2 = d3.select(highlighted[1]);
+
+ } else if (highlighted.length > 0) {
+ var s1 = d3.select(highlighted[0]);
+ }
+ var src = s1;
+ if (s2 && !s2.data()[0].target) {
+ src = s2;
+ }
+ if (src) {
+ linkVector.attr('d', function () {
+ var srcPt = document.querySelector('svg').createSVGPoint();
+ srcPt.x = src.attr('x');
+ srcPt.y = src.attr('y');
+ srcPt = srcPt.matrixTransform(src[0][0].getCTM());
+
+ var svg = document.getElementById('topology');
+ var mouse = d3.mouse(viewbox);
+ var dstPt = document.querySelector('svg').createSVGPoint();
+ dstPt.x = mouse[0];
+ dstPt.y = mouse[1];
+ dstPt = dstPt.matrixTransform(viewbox.getCTM());
+
+ return line([srcPt, dstPt]);
+ });
+ }
+ });
+
d3.select(document.body).on('mouseup', function () {
function clearHighlight() {
svg.selectAll('circle').each(function (data) {
data.mouseDown = false;
+ d3.select('#topology').classed('linking', false);
mouseOutSwitch(data);
- })
+ });
+ d3.select('#linkVector').remove();
};
+ d3.selectAll('.nodrop').classed('nodrop', false);
+
function removeLink(link) {
var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
@@ -538,10 +633,10 @@
}
- var highlighted = svg.selectAll('circle.highlight')[0];
+ var highlighted = svg.selectAll('.highlight')[0];
if (highlighted.length == 2) {
- var s1Data = d3.select(highlighted[0]).data()[0];
- var s2Data = d3.select(highlighted[1]).data()[0];
+ var s1Data = highlighted[0].__data__;
+ var s2Data = highlighted[1].__data__;
var srcData, dstData;
if (s1Data.target) {
@@ -592,16 +687,16 @@
pending: true
};
pendingLinks[makeLinkKey(link2)] = link2;
- updateTopology(svg, model);
+ updateTopology();
linkUp(link1);
- // remove the pending link after 10s
+ // remove the pending links after 10s
setTimeout(function () {
delete pendingLinks[makeLinkKey(link1)];
delete pendingLinks[makeLinkKey(link2)];
- updateTopology(svg, model);
+ updateTopology();
}, 10000);
}
}
@@ -718,7 +813,7 @@
var dstPt = document.querySelector('svg').createSVGPoint();
dstPt.x = dst.attr('x');
- dstPt.y = dst.attr('y'); // tmp: make up and down links distinguishable
+ dstPt.y = dst.attr('y');
dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
var midPt = document.querySelector('svg').createSVGPoint();
@@ -740,7 +835,7 @@
drawFlows();
}
-function updateControllers(model) {
+function updateControllers() {
var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
controllers.enter().append('div')
.each(function (c) {
@@ -751,7 +846,7 @@
return d;
})
.append('div')
- .attr('class', 'controllerEye');
+ .attr('class', 'black-eye');
controllers.attr('class', function (d) {
var color = 'colorInactive';
@@ -781,7 +876,7 @@
}
});
- controllers.select('.controllerEye').on('click', function (c) {
+ controllers.select('.black-eye').on('click', function (c) {
var allSelected = true;
for (var key in controllerColorMap) {
if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
@@ -806,30 +901,26 @@
}
-function sync(svg, selectedFlowsView) {
+function sync(svg) {
var d = Date.now();
updateModel(function (newModel) {
// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
+ var modelChanged = false;
if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
- updateControllers(newModel);
-
- // fake flows right now
- var i;
- for (i = 0; i < newModel.flows.length && i < selectedFlowsData.length; i+=1) {
- var selected = selectedFlowsData[i] ? selectedFlowsData[i].selected : false;
- selectedFlowsData[i].flow = newModel.flows[i];
- selectedFlowsData[i].selected = selected;
- }
-
- updateFlowView(newModel);
- updateTopology(svg, newModel);
+ modelChanged = true;
+ model = newModel;
} else {
// console.log('no change');
}
- updateHeader(newModel);
- model = newModel;
+ if (modelChanged) {
+ updateControllers();
+ updateSelectedFlows();
+ updateTopology();
+ }
+
+ updateHeader(newModel);
// do it again in 1s
setTimeout(function () {
@@ -839,7 +930,13 @@
}
svg = createTopologyView();
-selectedFlowsView = createFlowView();
+updateSelectedFlows();
+
+d3.select('#showFlowChooser').on('click', function () {
+ showFlowChooser();
+});
+
+
// workaround for Chrome v25 bug
// if executed immediately, the view box transform logic doesn't work properly
// fixed in Chrome v27
@@ -848,5 +945,5 @@
// viewbox transform stuff doesn't work in combination with browser zoom
// also works in Chrome v27
d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
- sync(svg, selectedFlowsView);
+ sync(svg);
}, 100);
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index 17100b1..4f6d0c1 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -11,4 +11,14 @@
}
return parameters;
+}
+
+function findLink(model, dpid) {
+ var links = [];
+ model.links.forEach(function (link) {
+ if (link['src-switch'] == dpid || link['dst-switch'] == dpid) {
+ links.push(link);
+ }
+ });
+ return links;
}
\ No newline at end of file
diff --git a/web/topology_rest.py b/web/topology_rest.py
index e4a2684..40e36ae 100755
--- a/web/topology_rest.py
+++ b/web/topology_rest.py
@@ -721,12 +721,40 @@
return result
-#* Link Up/Down
+#* Link Up
#http://localhost:9000/gui/link/up/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>
-#http://localhost:9000/gui/link/down/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>
+@app.route("/gui/link/up/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>")
+def link_up(src_dpid, src_port, dst_dpid, dst_port):
+ cmd = 'up'
+ result=""
+
+ for dpid in (src_dpid, dst_dpid):
+ if dpid in core_switches:
+ host = controllers[0]
+ src_ports = [1, 2, 3, 4, 5]
+ else:
+ hostid=int(src_dpid.split(':')[-2])
+ host = controllers[hostid-1]
+ if hostid == 2 :
+ src_ports = [51]
+ else :
+ src_ports = [26]
+
+ for port in src_ports :
+ cmd_string="ssh -i ~/.ssh/onlabkey.pem %s 'cd ONOS/scripts; ./link.sh %s %s %s'" % (host, dpid, port, cmd)
+ print cmd_string
+ res=os.popen(cmd_string).read()
+ result = result + ' ' + res
+
+ return result
+
+
+#* Link Down
+#http://localhost:9000/gui/link/down/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>
@app.route("/gui/link/<cmd>/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>")
-def link_change(cmd, src_dpid, src_port, dst_dpid, dst_port):
+def link_down(cmd, src_dpid, src_port, dst_dpid, dst_port):
+
if src_dpid in core_switches:
host = controllers[0]
else:
@@ -736,8 +764,7 @@
cmd_string="ssh -i ~/.ssh/onlabkey.pem %s 'cd ONOS/scripts; ./link.sh %s %s %s'" % (host, src_dpid, src_port, cmd)
print cmd_string
- if cmd =="up" or cmd=="down":
- result=os.popen(cmd_string).read()
+ result=os.popen(cmd_string).read()
return result
@@ -770,10 +797,10 @@
return errcode
#* Start Iperf Througput
-#http://localhost:9000/gui/iperf/start/<flow_id>
-@app.route("/gui/iperf/start/<flow_id>")
-def iperf_start(flow_id):
- command = "iperf -xCMSV -t30 -i1 -u -c 127.0.0.1 > iperf_%s.out &" % (flow_id)
+#http://localhost:9000/gui/iperf/start/<flow_id>/<duration>
+@app.route("/gui/iperf/start/<flow_id>/<duration>")
+def iperf_start(flow_id,duration):
+ command = "iperf -xCMSV -t%d -i 0.5 -y c -u -c 127.0.0.1 > iperf_%s.out 2>/dev/null &" % (duration, flow_id)
print command
errcode = os.popen(command).read()
return errcode