Merge branch 'master' of https://github.com/OPENNETWORKINGLAB/ONOS
diff --git a/cluster-mgmt/bin/demo-reset-hw.sh b/cluster-mgmt/bin/demo-reset-hw.sh
new file mode 100755
index 0000000..8c586f5
--- /dev/null
+++ b/cluster-mgmt/bin/demo-reset-hw.sh
@@ -0,0 +1,38 @@
+#! /bin/bash
+DIR=${HOME}/ONOS
+echo "==== Reset Demo to the initial State ==="
+date
+start=`date +"%s"`
+echo "all link up.."
+$DIR/scripts/all-linkup-hw.sh
+echo "link up done"
+
+echo "cleanup excess flows"
+$DIR/web/delete_flow.py 201 300
+$DIR/web/clear_flow.py 201 300
+echo "cleanup excess flows done"
+echo "Adding 200 flows"
+$DIR/web/add_flow.py -m onos -f $DIR/web/flowdef_demo_start.txt
+echo "done"
+echo "killing iperf"
+dsh -g onos 'sudo pkill -KILL iperf'
+echo "done"
+echo "kill onos at 5 and 7"
+onos stop 5
+onos stop 7
+echo "done"
+echo "bringup 1 2 3 4 6 8 if dead"
+for i in 1 2 3 4 6 8; do
+  status=`onos status $i | grep instance | awk '{print $2}'`
+  echo "onos $i status $status"
+  if [ x$status == "x0" ]; then
+    onos start $i
+  fi
+done
+echo "done"
+
+sleep 2
+switch local
+endt=`date +"%s"`
+(( delta = endt -start ))
+echo "finish: took $delta sec"
diff --git a/cluster-mgmt/bin/demo-scale-out-hw.sh b/cluster-mgmt/bin/demo-scale-out-hw.sh
new file mode 100755
index 0000000..6a44c8d
--- /dev/null
+++ b/cluster-mgmt/bin/demo-scale-out-hw.sh
@@ -0,0 +1,6 @@
+#! /bin/bash
+onos start 5
+onos start 7
+switch local
+sleep 4 
+cd ~/ONOS/web; ./add_flow.py -m onos -f flowdef_demo_add.txt &
diff --git a/scripts/demo-reset-sw.sh b/scripts/demo-reset-sw.sh
new file mode 100755
index 0000000..65a2ff1
--- /dev/null
+++ b/scripts/demo-reset-sw.sh
@@ -0,0 +1,23 @@
+#! /bin/bash
+basename=$ONOS_CLUSTER_BASENAME
+DIR=${HOME}/ONOS
+tstart=`date +"%s"`
+echo "All Link Up"
+$DIR/scripts/all-linkup.sh
+echo "Delete Flows"
+$DIR/web/delete_flow.py 201 300
+$DIR/web/clear_flow.py 201 300
+echo "Adding Flows"
+$DIR/web/add_flow.py -m onos -f $DIR/web/flowdef_demo_start.txt
+ssh -i ~/.ssh/onlabkey.pem  ${basename}5 'ONOS/start-onos.sh stop'
+ssh -i ~/.ssh/onlabkey.pem  ${basename}7 'ONOS/start-onos.sh stop'
+for i in 1 2 3 4 6 8; do
+    ssh -i ~/.ssh/onlabkey.pem  ${basename}$i 'ONOS/start-onos.sh startifdown'
+done
+sleep 2
+for i in 1 2 3 4 5 6 7 8; do
+    ssh -i ~/.ssh/onlabkey.pem  ${basename}$i 'cd ONOS/scripts; ./ctrl-local.sh'
+done
+tend=`date +"%s"`
+(( delta = tend - tstart ))
+echo "Demo Reset Done: took $delta sec"
diff --git a/scripts/demo-scale-out-sw.sh b/scripts/demo-scale-out-sw.sh
new file mode 100755
index 0000000..887a025
--- /dev/null
+++ b/scripts/demo-scale-out-sw.sh
@@ -0,0 +1,14 @@
+#! /bin/bash
+basename=$ONOS_CLUSTER_BASENAME
+DIR=${HOME}/ONOS
+start=`date +"%s"`
+echo "bring up two nodes"
+ssh -i ~/.ssh/onlabkey.pem  ${basename}5 'ONOS/start-onos.sh start'
+ssh -i ~/.ssh/onlabkey.pem  ${basename}7 'ONOS/start-onos.sh start'
+sleep 2
+echo "Adding more flows"
+$DIR/web/add_flow.py -m onos -f $DIR/web/flowdef_demo_add.txt
+endt=`date +"%s"`
+(( delta = endt -start ))
+echo "Scale Up Done: took $delta sec"
+
diff --git a/src/main/java/net/floodlightcontroller/core/web/ClearFlowTableResource.java b/src/main/java/net/floodlightcontroller/core/web/ClearFlowTableResource.java
new file mode 100644
index 0000000..c2d2eb4
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/core/web/ClearFlowTableResource.java
@@ -0,0 +1,55 @@
+package net.floodlightcontroller.core.web;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.openflow.util.HexString;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClearFlowTableResource extends ServerResource {
+	static Logger log = LoggerFactory.getLogger(ClearFlowTableResource.class);
+
+	@Post("json")
+	public List<String> ClearFlowTable(String jsonData){
+		IFloodlightProviderService floodlightProvider = 
+				(IFloodlightProviderService) getContext().getAttributes()
+				.get(IFloodlightProviderService.class.getCanonicalName());
+		
+		Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches();
+		
+		List<String> response = new ArrayList<String>();
+		ObjectMapper mapper = new ObjectMapper();
+		String[] dpids = null;
+		try {
+			dpids = mapper.readValue(jsonData, String[].class);
+		} catch (IOException e) {
+			log.debug("Error parsing switch dpid array: {}", e.getMessage());
+			response.add("Error parsing input");
+			return response;
+		}
+		
+		
+		for (String dpid : dpids){
+			IOFSwitch sw = switches.get(HexString.toLong(dpid));
+			if (sw != null){
+				sw.clearAllFlowMods();
+				response.add(dpid + " cleared");
+			}
+			else {
+				response.add(dpid + " not found");
+			}
+		}
+		
+		return response;
+	}
+
+}
diff --git a/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java b/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
index c110651..2bb39ef 100644
--- a/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
+++ b/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
@@ -65,6 +65,7 @@
         router.attach("/topology/switches/{filter}/json", TopoSwitchesResource.class);
         router.attach("/topology/links/json", TopoLinksResource.class);
         router.attach("/topology/devices/json", TopoDevicesResource.class);
+        router.attach("/clearflowtable/json", ClearFlowTableResource.class);
         return router;
     }
 }
diff --git a/start-onos.sh b/start-onos.sh
index 7e23eaf..77accd0 100755
--- a/start-onos.sh
+++ b/start-onos.sh
@@ -127,6 +127,14 @@
     check_db
     start 
     ;;
+  startifdown)
+    n=`jps -l |grep "net.floodlightcontroller.core.Main" | wc -l`
+    if [ $n == 0 ]; then
+      start
+    else 
+      echo "$n instance of onos running"
+    fi
+    ;;
   stop)
     stop
     ;;
@@ -134,10 +142,10 @@
     deldb
     ;;
   status)
-    n=`ps -edalf |grep java |grep logback.xml | wc -l`
+    n=`jps -l |grep "net.floodlightcontroller.core.Main" | wc -l`
     echo "$n instance of onos running"
     ;;
   *)
-    echo "Usage: $0 {start|stop|restart|status}"
+    echo "Usage: $0 {start|stop|restart|status|startifdown}"
     exit 1
 esac
diff --git a/web/clear_core.py b/web/clear_core.py
new file mode 100755
index 0000000..ea3e964
--- /dev/null
+++ b/web/clear_core.py
@@ -0,0 +1,33 @@
+#! /usr/bin/env python
+
+import os
+import json
+
+CONFIG_FILE=os.getenv("HOME") + "/ONOS/web/config.json"
+
+def read_config():
+  global LB, TESTBED, controllers, core_switches, ONOS_GUI3_HOST, ONOS_GUI3_CONTROL_HOST
+  f = open(CONFIG_FILE)
+  conf = json.load(f)
+  LB = conf['LB']
+  TESTBED = conf['TESTBED']
+  controllers = conf['controllers']
+  core_switches=conf['core_switches']
+  ONOS_GUI3_HOST=conf['ONOS_GUI3_HOST']
+  ONOS_GUI3_CONTROL_HOST=conf['ONOS_GUI3_CONTROL_HOST']
+  f.close()
+
+if __name__ == "__main__":
+  onos_rest_port = 8080
+  read_config()
+
+  try:
+    sw_list = json.dumps(core_switches)
+    command = "curl -s -H 'Content-Type: application/json' -d '%s' http://%s:%s/wm/core/clearflowtable/json" % (sw_list, controllers[0], onos_rest_port)
+
+    print command
+    result = os.popen(command).read()
+    print result
+  except:
+    print "REST IF has issue"
+    exit
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 369e9c4..19d5e58 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,12 @@
+** April 12, 2013 **
+- fixed bug in iperf fetch
+- improved iperf logging
+
+** April 11, 2013 **
+- Use timestamps for iperf display
+This elimates spurious gaps when server responds slowly. However, gaps still appear if the server drops buffers entirely
+- Add "K" action for killing a controller
+
 ** April 9, 2013 **
 - display number of flows for each core<->core link
 - graphics tweaks
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 9f1ecf1..2b07e33 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -167,7 +167,7 @@
 
 path.flow {
 	fill: none;
-	stroke-width: 5px;
+	stroke-width: 8px;
 	stroke: rgba(255, 255, 255, .35);
 }
 
@@ -181,13 +181,13 @@
 }
 
 path.iperfdata {
-	fill: none;
+	fill: #ccc;
 	stroke-width: 1px;
 	stroke: #ccc;
 }
 
 .iperf {
-	background-color: #222;
+	background-color: black;
 }
 
 #selectedFlowsHeader .iperf {
@@ -293,7 +293,7 @@
 text {
 	stroke: none;
 	fill: white;
-	font-size: 16px;
+	font-size: 22px;
 	pointer-events: none;
 }
 
@@ -469,7 +469,7 @@
 }
 
 .flowCount {
-	font-size: 20px;
+	font-size: 24px;
 	fill: rgba(255, 255, 255, .75);
 }
 
@@ -528,3 +528,21 @@
 	background-color: black;
 }
 
+select {
+	margin-top: 1em;
+	-webkit-appearance: none;
+	border-radius: 0px;
+	font-size: 18px;
+	padding: .5em;
+	background-color: black;
+	color: white;
+	border: 1px solid white;
+}
+
+select:after {
+	content: 'a';
+	position: absolute;
+	right: 0px;
+	top: 0px;
+}
+
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 1b88b4a..ed31bee 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -23,10 +23,11 @@
 		<div id='cluster-label'>ONOS Node Cluster</div>
 		<div id='controllers'></div>
 		<div id='actions'>
-			<div id='action-all' class='action'>A</div>
 			<div id='action-local' class='action'>1</div>
-			<div id='action-scale' class='action'>S</div>
 			<div id='action-reset' class='action'>R</div>
+			<div id='action-scale' class='action'>S</div>
+			<div id='action-all' class='action'>A</div>
+			<div id='action-kill' class='action'>K</div>
 		</div>
 	</div>
 
@@ -50,6 +51,7 @@
 	<div id='confirm-background'></div>
 	<div id='confirm-panel'>
 		<div id='confirm-prompt'>A PROMPT</div>
+		<select id='confirm-select'></select>
 		<div id='confirm-buttons'>
 			<div id='confirm-ok' class='confirm-button'>OK</div>
 			<div id='confirm-cancel' class='confirm-button'>CANCEL</div>
@@ -74,6 +76,7 @@
 
 <script src="js/topologyactions.js"></script>
 <script src="js/topology.js"></script>
+<script src="js/iperf.js"></script>
 <script src="js/flows.js"></script>
 <script src="js/init.js"></script>
 <script src="js/app.js"></script>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index 2689b43..94c41e2 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -36,17 +36,24 @@
 	});
 }
 
+// workaround for another Chrome v25 bug
+// viewbox transform stuff doesn't work in combination with browser zoom
+// also works in Chrome v27
+function zoomWorkaround() {
+	var zoom = window.document.body.clientWidth/window.document.width;
+	// workaround does not seem to be effective for transforming mouse coordinates
+	// map display does not use the transform stuff, so commenting out
+//	d3.select('#svg-container').style('zoom',  zoom);
+}
+
+d3.select(window).on('resize', zoomWorkaround);
+
 appInit(function () {
 	// workaround for Chrome v25 bug
 	// if executed immediately, the view box transform logic doesn't work properly
 	// fixed in Chrome v27
 	setTimeout(function () {
-
-		// workaround for another Chrome v25 bug
-		// 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);
-
+		zoomWorkaround();
 		sync();
 	}, 100);
 });
diff --git a/web/ons-demo/js/constants.js b/web/ons-demo/js/constants.js
index 0ef0882..51b3aa6 100644
--- a/web/ons-demo/js/constants.js
+++ b/web/ons-demo/js/constants.js
@@ -25,6 +25,6 @@
 ***************************************************************************************************/
 var widths = {
 	edge: 6,
-	aggregation: 12,
-	core: 18
+	aggregation: 16,
+	core: 20
 }
\ No newline at end of file
diff --git a/web/ons-demo/js/controller.js b/web/ons-demo/js/controller.js
index 211fa97..fb516ae 100644
--- a/web/ons-demo/js/controller.js
+++ b/web/ons-demo/js/controller.js
@@ -54,6 +54,14 @@
 	switchControllerCmd: function (cmd) {
 		var url = '/proxy/gui/switchctrl/' + cmd;
 		callURL(url);
+	},
+	resetCmd: function () {
+		var url = '/proxy/gui/reset';
+		callURL(url);
+	},
+	scaleCmd: function () {
+		var url = '/proxy/gui/scale';
+		callURL(url);
 	}
 };
 
@@ -100,8 +108,15 @@
 function switchLocal() {
 	controllerFunctions.switchControllerCmd('local');
 }
-
 function switchAll() {
 	controllerFunctions.switchControllerCmd('all');
 }
 
+function resetNetwork() {
+	controllerFunctions.resetCmd();
+}
+
+function scaleNetwork() {
+	controllerFunctions.scaleCmd();;
+}
+
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
index bc30d11..9f72fe2 100644
--- a/web/ons-demo/js/flows.js
+++ b/web/ons-demo/js/flows.js
@@ -1,6 +1,9 @@
 function startFlowAnimation(flow) {
-	if (flow.select('animate').empty()) {
-		flow.append('svg:animate')
+//	console.log('starting animation for flow: ' + flow.flowId);
+
+	var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
+	if (flowSelection.select('animate').empty()) {
+		flowSelection.append('svg:animate')
 			.attr('attributeName', 'stroke-dashoffset')
 			.attr('attributeType', 'xml')
 			.attr('from', '500')
@@ -11,7 +14,8 @@
 }
 
 function stopFlowAnimation(flow) {
-	flow.select('animate').remove();
+	var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
+	flowSelection.select('animate').remove();
 }
 
 
@@ -24,7 +28,9 @@
 		}
 	});
 
-	var flows = flowLayer.selectAll('.flow').data(topologyFlows);
+	var flows = flowLayer.selectAll('.flow').data(topologyFlows, function (d) {
+		return d.flowId;
+	});
 
 	flows.enter().append("svg:path").attr('class', 'flow')
 		.attr('stroke-dasharray', '4, 10')
@@ -200,92 +206,6 @@
 	flows.exit().remove();
 }
 
-function startIPerfForFlow(flow) {
-	var duration = 10000; // seconds
-	var interval = 100; // ms. this is defined by the server
-	var updateRate = 2000; // ms
-	var pointsToDisplay = 1000;
-
-	function makePoints() {
-		var pts = [];
-		var i;
-		for (i=0; i < pointsToDisplay; ++i) {
-			var sample = flow.iperfData.samples[i];
-			var height = 28 * sample/1000000;
-			if (height > 28)
-				height = 28;
-			pts.push({
-				x: i * 1000/(pointsToDisplay-1),
-				y: 30 - height
-			})
-		}
-		return pts;
-	}
-
-	if (flow.flowId) {
-		console.log('starting iperf for: ' + flow.flowId);
-		startIPerf(flow, duration, updateRate/interval);
-		flow.iperfDisplayInterval = setInterval(function () {
-			if (flow.iperfData) {
-				while (flow.iperfData.samples.length < pointsToDisplay) {
-					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);
-
-		var animationTimeout;
-
-		flow.iperfFetchInterval = setInterval(function () {
-			getIPerfData(flow, function (data) {
-				try {
-					if (!flow.iperfData) {
-						flow.iperfData = {
-							samples: []
-						};
-						var i;
-						for (i = 0; i < pointsToDisplay; ++i) {
-							flow.iperfData.samples.push(0);
-						}
-					}
-
-					var iperfData = JSON.parse(data);
-
-//				console.log(iperfData.timestamp);
-
-					// if the data is fresh
-					if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
-
-						var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
-						startFlowAnimation(flowSelection);
-						clearTimeout(animationTimeout);
-						// kill the animation if iperfdata stops flowing
-						animationTimeout = setTimeout(function () {
-							stopFlowAnimation(flowSelection);
-						}, updateRate*1.5);
-
-						while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
-							flow.iperfData.samples.shift();
-						}
-
-						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)
@@ -301,10 +221,13 @@
 			if (flow) {
 				var liveFlow = flowMap[makeFlowKey(flow)];
 				if (liveFlow) {
-					newSelectedFlows.push(liveFlow);
-					liveFlow.deletePending = flow.deletePending;
-					liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
-					liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
+					flow.flowId = liveFlow.flowId;
+					if (flow.createPending) {
+						startIPerfForFlow(flow);
+						flow.createPending = false;
+					}
+					flow.dataPath = liveFlow.dataPath;
+					newSelectedFlows.push(flow);
 				} else if (flow.createPending) {
 					newSelectedFlows.push(flow);
 				} else if (hasIPerf(flow)) {
@@ -346,19 +269,6 @@
 	}
 }
 
-function hasIPerf(flow) {
-	return flow && flow.iperfFetchInterval;
-}
-
-function clearIPerf(flow) {
-	console.log('clearing iperf interval for: ' + flow.flowId);
-	clearInterval(flow.iperfFetchInterval);
-	delete flow.iperfFetchInterval;
-	clearInterval(flow.iperfDisplayInterval);
-	delete flow.iperfDisplayInterval;
-	delete flow.iperfData;
-}
-
 function deselectFlow(flow, ifCreatePending) {
 	if (!flow) {
 		return;
diff --git a/web/ons-demo/js/init.js b/web/ons-demo/js/init.js
index f7bd55b..bf812aa 100644
--- a/web/ons-demo/js/init.js
+++ b/web/ons-demo/js/init.js
@@ -26,11 +26,29 @@
 	});
 
 	d3.select('#action-scale').on('click', function () {
-		alert('scale')
+		var prompt = "Scale network?"
+		doConfirm(prompt, function (result) {
+			if (result) {
+				scaleNetwork();
+			}
+		});
 	});
 
 	d3.select('#action-reset').on('click', function () {
-		alert('reset')
+		var prompt = "Reset network?"
+		doConfirm(prompt, function (result) {
+			if (result) {
+				resetNetwork();
+			}
+		});
+	});
+
+	d3.select('#action-kill').on('click', function () {
+		var prompt = "Kill ONOS node?";
+		var options = model.activeControllers;
+		doConfirm(prompt, function (result) {
+			controllerDown(result);
+		}, options);
 	});
 
 	createTopologyView(cb);
diff --git a/web/ons-demo/js/iperf.js b/web/ons-demo/js/iperf.js
new file mode 100644
index 0000000..b1f964c
--- /dev/null
+++ b/web/ons-demo/js/iperf.js
@@ -0,0 +1,190 @@
+var enableIPerfLog = false;
+
+function iperfLog(message, flow) {
+	if (enableIPerfLog) {
+		console.log('flow: ' + flow.flowId + '===>' + message);
+	}
+}
+
+function hasIPerf(flow) {
+	return flow && flow.iperfFetchTimeout;
+}
+
+function clearIPerf(flow) {
+	iperfLog('clearing iperf interval for: ' + flow.flowId, flow);
+	clearTimeout(flow.iperfFetchTimeout);
+	delete flow.iperfFetchTimeout;
+	clearInterval(flow.iperfDisplayInterval);
+	delete flow.iperfDisplayInterval;
+	clearTimeout(flow.animationTimeout);
+	delete flow.animationTimeout;
+	stopFlowAnimation(flow);
+	delete flow.iperfData.timestamp;
+
+	delete flow.iperfData;
+}
+
+function startIPerfForFlow(flow) {
+	var duration = 10000; // seconds
+	var interval = 100; // ms. this is defined by the server
+	var updateRate = 3000; // ms
+	var pointsToDisplay = 1000;
+
+	function makeGraph(iperfData) {
+		var d = 'M0,0';
+
+		var now = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
+
+		if (iperfData.samples && iperfData.samples.length) {
+
+			var lastX;
+			var i = iperfData.samples.length - 1;
+			while (i) {
+				var sample = iperfData.samples[i];
+
+				var x = (1000 - (now - sample.time)*10);
+				// workaround for discontinuity in iperf data
+				if (x < 0) {
+					i -= 1;
+					continue;
+				}
+
+				var y = 28 * sample.value/1000000;
+				if (y > 28) {
+					y = 28;
+				}
+				if (i == iperfData.samples.length - 1) {
+					d = 'M' + x + ',30';
+				}
+
+				// handle gaps
+				// 1.5 for rounding error
+				if (lastX && lastX - x > 1.5) {
+					d += 'L' + lastX + ',30';
+					d += 'M' + x + ',30'
+				}
+				lastX = x;
+
+				d += 'L' + x + ',' + (30-y);
+
+				i -= 1;
+			}
+			d += 'L' + lastX + ',30';
+		}
+		return d;
+	}
+
+	if (flow.flowId) {
+		iperfLog('starting iperf', flow);
+		startIPerf(flow, duration, updateRate/interval);
+
+		flow.iperfDisplayInterval = setInterval(function () {
+			if (flow.iperfData) {
+				var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
+				flow.iperfData.samples.sort(function (a, b) {
+					return a.time - b.time;
+				});
+				iperfPath.attr('d', makeGraph(flow.iperfData));
+			}
+
+
+		}, interval);
+
+		var animationTimeout;
+		flow.iperfData = {
+			samples: []
+		}
+
+		function resetFlowAnimationTimeout() {
+			clearTimeout(flow.animationTimeout);
+			// kill the animation if iperfdata stops flowing
+			flow.animationTimeout = setTimeout(function () {
+				stopFlowAnimation(flow);
+			}, updateRate*1.5);
+		}
+
+		var lastTime;
+		function fetchData() {
+			iperfLog('Requesting iperf data', flow);
+			var fetchTime = Date.now();
+			getIPerfData(flow, function (data) {
+				var requestTime = Date.now() - fetchTime;
+				var requestTimeMessage = 'iperf request completed in: ' + requestTime + 'ms';
+				if (requestTime > 1000) {
+					requestTimeMessage = requestTimeMessage.toUpperCase();
+				}
+				iperfLog(requestTimeMessage, flow);
+
+				if (!flow.iperfData) {
+					iperfLog('iperf session closed', flow);
+					return;
+				}
+
+				try {
+					var iperfData = JSON.parse(data);
+
+//				iperfLog(iperfData.timestamp, flow);
+
+					// if the data is fresh
+					if (!(flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp)) {
+						if (!flow.iperfData.timestamp) {
+							iperfLog('received first iperf buffer', flow);
+						} else {
+							iperfLog('received duplicate iperf buffer with timestamp: ' + iperfData.timestamp, flow);
+						}
+					} else {
+						iperfLog('received new iperf buffer with timstamp: ' + iperfData.timestamp, flow);
+						startFlowAnimation(flow);
+						resetFlowAnimationTimeout();
+
+						var endTime = Math.floor(iperfData['end-time']*10)/10;
+
+						var startTime = endTime - (iperfData.samples.length * interval/1000);
+						// set now on the first buffer
+						if (!flow.iperfData.startTime) {
+							flow.iperfData.startTime = startTime;
+							flow.iperfData.localNow = Date.now();
+						}
+
+						iperfLog('iperf buffer start time: ' + startTime, flow);
+						if (lastTime && (startTime - lastTime) > updateRate/1000) {
+							iperfLog('iperf buffer gap: ' + (startTime - lastTime), flow);
+						}
+						lastTime = startTime;
+
+						// clear out the old data
+						while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
+							flow.iperfData.samples.shift();
+						}
+
+						// if the client gets too out of sync, resynchronize
+						var clientNow = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
+						if (Math.abs(clientNow - startTime) > (updateRate/1000) * 2) {
+							iperfLog('resynchronizing now: ' + clientNow + ' => ' + startTime, flow);
+							flow.iperfData.startTime = startTime;
+							flow.iperfData.localNow = Date.now();
+						}
+
+						var time = startTime;
+						iperfData.samples.forEach(function (s) {
+							var sample = {
+								time: time,
+								value: s
+							};
+							flow.iperfData.samples.push(sample);
+							time += interval/1000;
+						});
+					}
+					flow.iperfData.timestamp = iperfData.timestamp;
+				} catch (e) {
+					iperfLog('bad iperf data: ' + data, flow);
+				}
+				flow.iperfFetchTimeout = setTimeout(fetchData, updateRate*.25); // over sample to avoid gaps
+//				iperfLog(data, flow);
+			});
+		}
+
+		// wait a buffer to make sure the old iperf session gets cleared out
+		flow.iperfFetchTimeout = setTimeout(fetchData, updateRate);
+	}
+}
\ No newline at end of file
diff --git a/web/ons-demo/js/map.js b/web/ons-demo/js/map.js
index ad590cb..256162d 100644
--- a/web/ons-demo/js/map.js
+++ b/web/ons-demo/js/map.js
@@ -344,10 +344,7 @@
 }
 
 function switchesEnter(switches) {
-	switchLayer.selectAll('g').data(switches, function (d) {
-		return d.dpid;
-	})
-		.enter()
+	switches.enter()
 			.append('svg:g')
 				.attr("id", function (d) {
 					return d.dpid;
@@ -363,9 +360,7 @@
 
 
 function switchesUpdate(switches) {
-	switchLayer.selectAll('g').data(switches, function (d) {
-		return d.dpid;
-	}).each(function (d) {
+	switches.each(function (d) {
 			// if there's a pending state changed and then the state changes, clear the pending class
 			var circle = d3.select(this);
 			if (d.state === 'ACTIVE' && circle.classed('inactive') ||
@@ -392,8 +387,10 @@
 	var aggregationSwitches = makeSwitchesModel(model.aggregationSwitches, 'aggregation');
 	var edgeSwitches = makeSwitchesModel(model.edgeSwitches, 'edge');
 
-	var switches = coreSwitches.concat(aggregationSwitches).concat(edgeSwitches);
-
+	var switches = 	switchLayer.selectAll('g')
+		.data(coreSwitches.concat(aggregationSwitches).concat(edgeSwitches), function (d) {
+			return d.dpid;
+		});
 	switchesEnter(switches)
 	switchesUpdate(switches);
 
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index 265c661..1b4c38c 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -204,10 +204,24 @@
 /***************************************************************************************************
 
 ***************************************************************************************************/
-function doConfirm(prompt, cb) {
+function doConfirm(prompt, cb, options) {
 	var confirm = d3.select('#confirm');
 	confirm.select('#confirm-prompt').text(prompt);
 
+	var select = d3.select(document.getElementById('confirm-select'));
+	if (options) {
+		select.style('display', 'block');
+		select.text('');
+		select.selectAll('option').
+			data(options)
+			.enter()
+				.append('option')
+					.attr('value', function (d) {return d})
+					.text(function (d) {return d});
+	} else {
+		select.style('display', 'none');
+	}
+
 	function show() {
 		confirm.style('display', '-webkit-box');
 		confirm.style('opacity', 0);
@@ -227,7 +241,11 @@
 	confirm.select('#confirm-ok').on('click', function () {
 		d3.select(this).on('click', null);
 		dismiss();
-		cb(true);
+		if (options) {
+			cb(select[0][0].value);
+		} else {
+			cb(true);
+		}
 	});
 
 	confirm.select('#confirm-cancel').on('click', function () {
diff --git a/web/topology_rest.py b/web/topology_rest.py
index 43865f1..9bb8b9e 100755
--- a/web/topology_rest.py
+++ b/web/topology_rest.py
@@ -229,8 +229,35 @@
   resp = Response(result, status=200, mimetype='application/json')
   return resp
 
+@app.route("/proxy/gui/reset")
+def proxy_gui_reset():
+  result = ""
+  try:
+    command = "curl -m 300 -s %s/gui/reset" % (ONOS_GUI3_CONTROL_HOST)
+    print command
+    result = os.popen(command).read()
+  except:
+    print "REST IF has issue"
+    exit
 
-###### ONOS RESET API ##############################
+  resp = Response(result, status=200, mimetype='application/json')
+  return resp
+
+@app.route("/proxy/gui/scale")
+def proxy_gui_scale():
+  result = ""
+  try:
+    command = "curl -m 300 -s %s/gui/scale" % (ONOS_GUI3_CONTROL_HOST)
+    print command
+    result = os.popen(command).read()
+  except:
+    print "REST IF has issue"
+    exit
+
+  resp = Response(result, status=200, mimetype='application/json')
+  return resp
+
+###### ONOS REST API ##############################
 ## Worker Func ###
 def get_json(url):
   code = 200
@@ -692,17 +719,20 @@
 def controller_status_change(cmd, controller_name):
   if (TESTBED == "hw"):
     start_onos="/home/admin/bin/onos start %s" % (controller_name[-1:])
+#    start_onos="/home/admin/bin/onos start %s > /tmp/debug " % (controller_name[-1:])
     stop_onos="/home/admin/bin/onos stop %s" % (controller_name[-1:])
+#    stop_onos="/home/admin/bin/onos stop %s > /tmp/debug " % (controller_name[-1:])
+#    print "Debug: Controller command %s called %s" % (cmd, controller_name)
   else:
     start_onos="ssh -i ~/.ssh/onlabkey.pem %s ONOS/start-onos.sh start" % (controller_name)
     stop_onos="ssh -i ~/.ssh/onlabkey.pem %s ONOS/start-onos.sh stop" % (controller_name)
 
   if cmd == "up":
     result=os.popen(start_onos).read()
-    ret = "controller %s is up" % (controller_name)
+    ret = "controller %s is up: %s" % (controller_name, result)
   elif cmd == "down":
     result=os.popen(stop_onos).read()
-    ret = "controller %s is down" % (controller_name)
+    ret = "controller %s is down: %s" % (controller_name, result)
 
   return ret
 
@@ -716,7 +746,7 @@
         cmd_string="ssh -i ~/.ssh/onlabkey.pem %s 'cd ONOS/scripts; ./ctrl-local.sh'" % (controllers[i])
         result += os.popen(cmd_string).read()
     else:
-      cmd_string="cd; switch local"
+      cmd_string="cd; switch local > /tmp/watch"
       result += os.popen(cmd_string).read()
   elif cmd =="all":
     print "All aggr switches connects to all controllers except for core controller"
@@ -724,14 +754,31 @@
     if (TESTBED == "sw"):
       for i in range(0, len(controllers)):
         cmd_string="ssh -i ~/.ssh/onlabkey.pem %s 'cd ONOS/scripts; ./ctrl-add-ext.sh'" % (controllers[i])
+        print "cmd is: "+cmd_string
         result += os.popen(cmd_string).read()
     else:
-      cmd_string="cd; switch all"
+      cmd_string="/home/admin/bin/switch all > /tmp/watch"
       result += os.popen(cmd_string).read()
 
   return result
 
+@app.route("/gui/reset")
+def reset_demo():
+  if (TESTBED == "hw"):
+    cmd_string="cd ~/bin; ./demo-reset-hw.sh > /tmp/watch &"
+  else:
+    cmd_string="cd ~/ONOS/scripts; ./demo-reset-sw.sh > /tmp/watch &"
+  os.popen(cmd_string)
+  return "Reset" 
 
+@app.route("/gui/scale")
+def scale_demo():
+  if (TESTBED == "hw"):
+    cmd_string="cd ~/bin;  ~/bin/demo-scale-out-hw.sh > /tmp/watch &"
+  else:
+    cmd_string="cd ~/ONOS/scripts; ./demo-scale-out-sw.sh > /tmp/watch &"
+  os.popen(cmd_string)
+  return "scale"
 
 @app.route("/gui/switch/<cmd>/<dpid>")
 def switch_status_change(cmd, dpid):
@@ -1061,7 +1108,8 @@
 #    iperf_start(1,10,15)
 #    iperf_rate(1)
 #    switches()
-    add_flow(1,2,3,4,5,6)
+#    add_flow(1,2,3,4,5,6)
+    reset_demo()
   else:
     app.debug = True
     app.run(threaded=True, host="0.0.0.0", port=9000)