Merge branch 'master' of https://github.com/OPENNETWORKINGLAB/ONOS
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 916a965..90af6d1 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,11 +1,20 @@
** April 4, 2013 **
+iperf display implemented
+- scaled to 50,000,000
+- update rate is every 2s
+- the display does not draw until receiving 2 buffers of data (this way if there is a stale buffer it doesn't get displayed)
+- duration is 10000 seconds. seems like there is no need for a button to restart?
+- displaying 10s data
+- if the data underruns (either because the server response is too slow or because the iperf data stops being updated) the display draws 0s
+- seeing the data stall a lot (timestamp and end-time remain the same through many fetches)
+
+** April 4, 2013 **
Fix issues:
305 - "close x" now unselects flow. double click to delete a flow
323 - gui now recovers on timeout errors and polls again
324 - fixed problem with added flows not displaying
325 - fixed logic displaying flows in topology view
-
** March 28, 2013 **
- add and delete flow implemented
- to add flow
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index 018e728..5e1f1ce 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -76,6 +76,15 @@
.iperf {
width: 100%;
-webkit-box-flex: 1.0;
+ position: relative;
+ display: -webkit-box;
+}
+
+.iperf-container {
+ position: absolute;
+ top: 0px;
+ height: 100%;
+ width: 100%;
}
#controllers {
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 1ce4897..1d656b0 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -145,6 +145,12 @@
border-top: 1px solid #AAA;
}
+path.iperfdata {
+ fill: none;
+ stroke-width: 2px;
+ stroke: rgba(255, 255, 255, .75);
+}
+
#flowChooser {
pointer-events: none;
background-color: rgba(0, 0, 0, .25);
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index 695e4bb..d6fa983 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -96,7 +96,7 @@
return;
}
var pts = [];
- if (!d.dataPath.flowEntries || !d.dataPath.flowEntries.length) {
+ if (!d.dataPath.flowEntries) {
// create a temporary vector to indicate the pending flow
var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
@@ -128,7 +128,11 @@
}
});
}
- return line(pts);
+ if (pts.length) {
+ return line(pts);
+ } else {
+ return "M0,0";
+ }
})
.attr('id', function (d) {
if (d) {
@@ -153,6 +157,16 @@
row.append('div').classed('dstDPID', true);
row.append('div').classed('iperf', true);
+ row.select('.iperf')
+ .append('div')
+ .attr('class', 'iperf-container')
+ .append('svg:svg')
+ .attr('viewBox', '0 0 1000 32')
+ .attr('preserveAspectRatio', 'none')
+ .append('svg:g')
+ .append('svg:path')
+ .attr('class', 'iperfdata');
+
row.on('mouseover', function (d) {
if (d) {
var path = document.getElementById(makeFlowKey(d));
@@ -164,14 +178,24 @@
var path = document.getElementById(makeFlowKey(d));
d3.select(path).classed('highlight', false);
}
- })
+ });
}
function rowUpdate(d) {
var row = d3.select(this);
+ row.attr('id', function (d) {
+ if (d) {
+ return makeSelectedFlowKey(d);
+ }
+ });
+
+ if (!d || !hasIPerf(d)) {
+ row.select('.iperfdata')
+ .attr('d', 'M0,0');
+ }
+
row.select('.deleteFlow').on('click', function () {
- selectedFlows[selectedFlows.indexOf(d)] = null;
- updateSelectedFlows();
+ deselectFlow(d);
});
row.on('dblclick', function () {
if (d) {
@@ -232,6 +256,73 @@
flows.exit().remove();
}
+// TODO: cancel the interval when the flow is desel
+function startIPerfForFlow(flow) {
+ var duration = 10000; // seconds
+ var interval = 100; // ms. this is defined by the server
+ var updateRate = 2000; // ms
+
+ function makePoints() {
+ var pts = [];
+ var i;
+ for (i=0; i < 100; ++i) {
+ var sample = flow.iperfData.samples[i];
+ var height = 32 * sample/50000000;
+ if (height > 32)
+ height = 32;
+ pts.push({
+ x: i * 1000/99,
+ y: 32 - height
+ })
+ }
+ return pts;
+ }
+
+ if (flow.flowId) {
+ console.log('starting iperf for: ' + flow.flowId.value);
+ startIPerf(flow, duration, updateRate/interval);
+ flow.iperfDisplayInterval = setInterval(function () {
+ if (flow.iperfData) {
+ while (flow.iperfData.samples.length < 100) {
+ flow.iperfData.samples.push(0);
+ }
+ var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
+ iperfPath.attr('d', line(makePoints()));
+ flow.iperfData.samples.shift();
+ }
+
+
+ }, interval);
+ flow.iperfFetchInterval = setInterval(function () {
+ getIPerfData(flow, function (data) {
+ try {
+ if (!flow.iperfData) {
+ flow.iperfData = {
+ samples: []
+ };
+ var i;
+ for (i = 0; i < 100; ++i) {
+ flow.iperfData.samples.push(0);
+ }
+ }
+
+ var iperfData = JSON.parse(data);
+ // if the data is fresh
+ if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
+ iperfData.samples.forEach(function (s) {
+ flow.iperfData.samples.push(s);
+ });
+ }
+ flow.iperfData.timestamp = iperfData.timestamp;
+ } catch (e) {
+ console.log('bad iperf data: ' + data);
+ }
+// console.log(data);
+ });
+ }, updateRate/2); // over sample to avoid gaps
+ }
+}
+
function updateSelectedFlows() {
// make sure that all of the selected flows are either
// 1) valid (meaning they are in the latest list of flows)
@@ -249,13 +340,22 @@
if (liveFlow) {
newSelectedFlows.push(liveFlow);
liveFlow.deletePending = flow.deletePending;
+ liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
+ liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
} else if (flow.createPending) {
newSelectedFlows.push(flow);
+ } else if (hasIPerf(flow)) {
+ clearIPerf(flow);
}
}
});
selectedFlows = newSelectedFlows;
}
+ selectedFlows.forEach(function (flow) {
+ if (!hasIPerf(flow)) {
+ startIPerfForFlow(flow);
+ }
+ });
while (selectedFlows.length < 3) {
selectedFlows.push(null);
}
@@ -280,6 +380,19 @@
}
}
+function hasIPerf(flow) {
+ return flow && flow.iperfFetchInterval;
+}
+
+function clearIPerf(flow) {
+ console.log('clearing iperf interval for: ' + flow.flowId.value);
+ clearInterval(flow.iperfFetchInterval);
+ delete flow.iperfFetchInterval;
+ clearInterval(flow.iperfDisplayInterval);
+ delete flow.iperfDisplayInterval;
+ delete flow.iperfData;
+}
+
function deselectFlow(flow, ifCreatePending) {
var flowKey = makeFlowKey(flow);
var newSelectedFlows = [];
@@ -288,6 +401,10 @@
flowKey !== makeFlowKey(flow) ||
flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
newSelectedFlows.push(flow);
+ } else {
+ if (hasIPerf(flow)) {
+ clearIPerf(flow);
+ }
}
});
selectedFlows = newSelectedFlows;
@@ -474,6 +591,10 @@
return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
}
+function makeSelectedFlowKey(flow) {
+ return 'S' + makeFlowKey(flow);
+}
+
function createLinkMap(links) {
var linkMap = {};
links.forEach(function (link) {
@@ -1066,6 +1187,7 @@
}
+var modelString;
function sync(svg) {
var d = Date.now();
updateModel(function (newModel) {
@@ -1073,9 +1195,11 @@
if (newModel) {
var modelChanged = false;
- if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
+ var newModelString = JSON.stringify(newModel);
+ if (!modelString || newModelString != modelString) {
modelChanged = true;
model = newModel;
+ modelString = newModelString;
} else {
// console.log('no change');
}
diff --git a/web/ons-demo/js/controller.js b/web/ons-demo/js/controller.js
index fd3f6ae..c7c80ec 100644
--- a/web/ons-demo/js/controller.js
+++ b/web/ons-demo/js/controller.js
@@ -1,11 +1,14 @@
/*global d3*/
-function callURL(url) {
+function callURL(url, cb) {
d3.text(url, function (error, result) {
if (error) {
alert(url + ' : ' + error.status);
} else {
- console.log(result);
+ if (cb) {
+ cb(result);
+ }
+// console.log(result);
}
});
}
@@ -37,6 +40,16 @@
delFlowCmd: function (flow) {
var url = '/proxy/gui/delflow/' + flow.flowId.value;
callURL(url);
+ },
+ startIPerfCmd: function (flow, duration, numSamples) {
+ var flowId = parseInt(flow.flowId.value, 16);
+ var url = '/proxy/gui/iperf/start/' + [flowId, duration, numSamples].join('/');
+ callURL(url)
+ },
+ getIPerfDataCmd: function (flow, cb) {
+ var flowId = parseInt(flow.flowId.value, 16);
+ var url = '/proxy/gui/iperf/rate/' + flowId;
+ callURL(url, cb);
}
};
@@ -70,4 +83,12 @@
function deleteFlow(flow) {
controllerFunctions.delFlowCmd(flow);
+}
+
+function startIPerf(flow, duration, numSamples) {
+ controllerFunctions.startIPerfCmd(flow, duration, numSamples);
+}
+
+function getIPerfData(flow, cb) {
+ controllerFunctions.getIPerfDataCmd(flow, cb);
}
\ No newline at end of file
diff --git a/web/topology_rest.py b/web/topology_rest.py
index f17d6ba..84e12da 100755
--- a/web/topology_rest.py
+++ b/web/topology_rest.py
@@ -153,6 +153,34 @@
resp = Response(result, status=200, mimetype='application/json')
return resp
+
+@app.route("/proxy/gui/iperf/start/<flow_id>/<duration>/<samples>")
+def proxy_iperf_start(flow_id,duration,samples):
+ try:
+ command = "curl -s %s/gui/iperf/start/%s/%s/%s" % (ONOS_GUI3_CONTROL_HOST, flow_id, duration, samples)
+ print command
+ result = os.popen(command).read()
+ except:
+ print "REST IF has issue"
+ exit
+
+ resp = Response(result, status=200, mimetype='application/json')
+ return resp
+
+@app.route("/proxy/gui/iperf/rate/<flow_id>")
+def proxy_iperf_rate(flow_id):
+ try:
+ command = "curl -s %s/gui/iperf/rate/%s" % (ONOS_GUI3_CONTROL_HOST, flow_id)
+ print command
+ result = os.popen(command).read()
+ except:
+ print "REST IF has issue"
+ exit
+
+ resp = Response(result, status=200, mimetype='application/json')
+ return resp
+
+
@app.route("/wm/core/topology/switches/all/json")
def switches():
if request.args.get('proxy') == None:
@@ -204,7 +232,7 @@
print "REST IF has issue"
exit
-
+
resp = Response(result, status=200, mimetype='application/json')
return resp