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 48834fb..8649e9b 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,4 +1,16 @@
 ** March 28, 2013 **
+- add and delete flow implemented
+- to add flow
+	- mouse down in src edge switch
+	- drag to dst edge switch
+	- release/confirm to create flow
+- to delete flow
+	- select flow in flow chooser
+	- click "close" button in flow table
+	- confirm
+- same "pending" style as other actions
+
+** 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)
diff --git a/web/ons-demo/assets/delete.svg b/web/ons-demo/assets/delete.svg
new file mode 100644
index 0000000..a32a278
--- /dev/null
+++ b/web/ons-demo/assets/delete.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<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"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="283.465px"
+   height="283.465px"
+   viewBox="0 0 283.465 283.465"
+   enable-background="new 0 0 283.465 283.465"
+   xml:space="preserve"
+   inkscape:version="0.48.0 r9654"
+   sodipodi:docname="delete.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="721"
+   inkscape:window-height="480"
+   id="namedview5"
+   showgrid="false"
+   inkscape:zoom="0.92427638"
+   inkscape:cx="141.7325"
+   inkscape:cy="141.7325"
+   inkscape:window-x="0"
+   inkscape:window-y="0"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1" />
+<path
+   fill="#772953"
+   d="M141.733,198.425c-15.143,0-29.38-5.897-40.088-16.605c-10.708-10.708-16.605-24.945-16.605-40.088  c0-15.142,5.897-29.378,16.605-40.086c10.708-10.708,24.945-16.605,40.088-16.605c15.142,0,29.379,5.896,40.087,16.604  c10.708,10.708,16.605,24.946,16.605,40.089c0,15.143-5.897,29.381-16.605,40.088C171.112,192.528,156.875,198.425,141.733,198.425  L141.733,198.425z M141.733,99.213c-11.357,0-22.035,4.423-30.067,12.454c-8.031,8.031-12.454,18.708-12.454,30.064  c0,11.357,4.423,22.035,12.454,30.065c8.032,8.031,18.709,12.455,30.067,12.455c11.356,0,22.034-4.423,30.064-12.453  c8.031-8.03,12.455-18.709,12.455-30.066s-4.424-22.036-12.455-30.067C163.768,103.636,153.09,99.213,141.733,99.213L141.733,99.213  z M151.703,141.79l10.021-10.022c2.768-2.768,2.768-7.254,0-10.021c-2.767-2.767-7.255-2.767-10.021,0l-10.022,10.021  l-10.021-10.021c-2.767-2.767-7.255-2.767-10.022,0c-2.768,2.768-2.768,7.254,0,10.021l10.022,10.022l-10.023,10.023  c-2.768,2.768-2.768,7.254,0,10.021c1.383,1.384,3.197,2.076,5.011,2.076c1.813,0,3.627-0.692,5.011-2.076l10.023-10.023  l10.022,10.021c1.383,1.384,3.197,2.076,5.011,2.076s3.627-0.692,5.011-2.076c2.768-2.768,2.768-7.254,0-10.021L151.703,141.79z"
+   id="path3"
+   style="fill:#ffffff;fill-opacity:1" />
+</svg>
\ No newline at end of file
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index aebf68b..d88814e 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -61,6 +61,10 @@
 	border-bottom: 1px solid #AAA;
 }
 
+.selectedFlow:hover {
+	background-color: #333;
+}
+
 .selectedFlow:last-child {
 	border-bottom: none;
 }
@@ -132,6 +136,11 @@
 	stroke: rgba(255, 255, 255, .35);
 }
 
+path.flow.highlight {
+	stroke-width: 6px;
+	stroke: rgba(255, 255, 255, .75);
+}
+
 #selectedFlowsHeader {
 	border-top: 1px solid #AAA;
 }
@@ -142,7 +151,7 @@
 }
 
 
-.flowId, .srcDPID, .dstDPID, .iperf {
+.flowId, .srcDPID, .dstDPID, .iperf, #deleteFlow, .deleteFlow {
 	display: -webkit-box;
 	-webkit-box-pack: center;
 	-webkit-box-align: center;
@@ -155,10 +164,14 @@
 }
 
 
-.flowId, .srcDPID, .dstDPID {
+.srcDPID, .dstDPID, .deleteFlow, #deleteFlow {
 	border-right: 1px solid #AAA;
 }
 
+.srcDPID {
+	border-left: 1px solid #AAA;
+}
+
 
 #controllers {
 	border-right: 1px solid #AAA;
@@ -191,6 +204,14 @@
 	background-position: .25em center;
 }
 
+.deleteFlow {
+	height: 100%;
+	background-image: url('../assets/delete.svg');
+	background-size: auto 150%;
+	background-repeat: no-repeat;
+	background-position: center;
+}
+
 #logo {
 	height: 50px;
 }
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index d157ecc..55237ac 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -38,6 +38,7 @@
 	</div>
 </div>
 <div id='selectedFlowsHeader'>
+	<div id='deleteFlow'></div>
 	<div id='showFlowChooser' class='flowId'><div class='white-eye'></div></div>
 	<div class='srcDPID'>src</div>
 	<div class='dstDPID'>dst</div>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index 0fa5fa3..e342213 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -18,6 +18,9 @@
 var svg;
 var updateTopology;
 var pendingLinks = {};
+var selectedFlows = [];
+
+var pendingTimeout = 10000;
 
 var colors = [
 	'color1',
@@ -65,13 +68,9 @@
 			attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
 }
 
-var selectedFlows = [null, null, null];
-
-function drawFlows() {
+function updateSelectedFlowsTopology() {
 	// DRAW THE FLOWS
-	var flows = d3.select('svg').selectAll('.flow').data(selectedFlows, function (d) {
-		return d ? d.flowId.value : null;
-	});
+	var flows = d3.select('svg').selectAll('.flow').data(selectedFlows);
 
 	flows.enter().append("svg:path").attr('class', 'flow')
 		.attr('stroke-dasharray', '4, 10')
@@ -85,27 +84,212 @@
 
 
 	flows.attr('d', function (d) {
-		if (!d) {
-			return;
-		}
-		var pts = [];
-		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');
-			pt.y = s.attr('y');
-			pt = pt.matrixTransform(s[0][0].getCTM());
-			pts.push(pt);
+			if (!d) {
+				return;
+			}
+			var pts = [];
+			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));
+
+				var pt1 = document.querySelector('svg').createSVGPoint();
+				pt1.x = s1.attr('x');
+				pt1.y = s1.attr('y');
+				pt1 = pt1.matrixTransform(s1[0][0].getCTM());
+				pts.push(pt1);
+
+				var pt2 = document.querySelector('svg').createSVGPoint();
+				pt2.x = s2.attr('x');
+				pt2.y = s2.attr('y');
+				pt2 = pt2.matrixTransform(s2[0][0].getCTM());
+				pts.push(pt2);
+
+			} else {
+				d.dataPath.flowEntries.forEach(function (flowEntry) {
+					var s = d3.select(document.getElementById(flowEntry.dpid.value));
+					// s[0] is null if the flow entry refers to a non-existent switch
+					if (s[0][0]) {
+						var pt = document.querySelector('svg').createSVGPoint();
+						pt.x = s.attr('x');
+						pt.y = s.attr('y');
+						pt = pt.matrixTransform(s[0][0].getCTM());
+						pts.push(pt);
+					} else {
+						console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
+					}
+				});
+			}
+			return line(pts);
+		})
+		.attr('id', function (d) {
+			if (d) {
+				return makeFlowKey(d);
+			}
+		})
+		.classed('pending', function (d) {
+			return d && (d.createPending || d.deletePending);
 		});
-		return line(pts);
-	})
 
 	// "marching ants"
 	flows.select('animate').attr('from', 500);
 
+}
+
+function updateSelectedFlowsTable() {
+	function rowEnter(d) {
+		var row = d3.select(this);
+		row.append('div').classed('deleteFlow', true);
+		row.append('div').classed('flowId', true);
+		row.append('div').classed('srcDPID', true);
+		row.append('div').classed('dstDPID', true);
+		row.append('div').classed('iperf', true);
+
+		row.on('mouseover', function (d) {
+			if (d) {
+				var path = document.getElementById(makeFlowKey(d));
+				d3.select(path).classed('highlight', true);
+			}
+		});
+		row.on('mouseout', function (d) {
+			if (d) {
+				var path = document.getElementById(makeFlowKey(d));
+				d3.select(path).classed('highlight', false);
+			}
+		})
+	}
+
+	function rowUpdate(d) {
+		var row = d3.select(this);
+		row.select('.deleteFlow').on('click', function () {
+			if (d) {
+				var prompt = 'Delete flow ' + d.flowId.value + '?';
+				if (confirm(prompt)) {
+					deleteFlow(d);
+					d.deletePending = true;
+					updateSelectedFlows();
+
+					setTimeout(function () {
+						d.deletePending = false;
+						updateSelectedFlows();
+					}, pendingTimeout)
+				};
+			}
+		});
+
+		row.select('.flowId')
+			.text(function (d) {
+				if (d) {
+					if (d.flowId) {
+						return d.flowId.value;
+					} else {
+						return '0x--';
+					}
+				}
+			})
+			.classed('pending', d && (d.deletePending || d.createPending));
+
+		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();
 }
 
+function updateSelectedFlows() {
+	// make sure that all of the selected flows are either
+	// 1) valid (meaning they are in the latest list of flows)
+	// 2) pending
+	if (model) {
+		var flowMap = {};
+		model.flows.forEach(function (flow) {
+			flowMap[makeFlowKey(flow)] = flow;
+		});
+
+		var newSelectedFlows = [];
+		selectedFlows.forEach(function (flow) {
+			if (flow) {
+				var liveFlow = flowMap[makeFlowKey(flow)];
+				if (liveFlow) {
+					newSelectedFlows.push(liveFlow);
+					liveFlow.deletePending = flow.deletePending;
+				} else if (flow.createPending) {
+					newSelectedFlows.push(flow);
+				}
+			} else {
+				newSelectedFlows.push(null);
+			}
+		});
+		selectedFlows = newSelectedFlows;
+	}
+	while (selectedFlows.length < 3) {
+		selectedFlows.push(null);
+	}
+
+	updateSelectedFlowsTable();
+	updateSelectedFlowsTopology();
+}
+
+function selectFlow(flow) {
+	var flowKey = makeFlowKey(flow);
+	var alreadySelected = false;
+	selectedFlows.forEach(function (f) {
+		if (f && makeFlowKey(f) === flowKey) {
+			alreadySelected = true;
+		}
+	});
+
+	if (!alreadySelected) {
+		selectedFlows.unshift(flow);
+		selectedFlows = selectedFlows.slice(0, 3);
+		updateSelectedFlows();
+	}
+}
+
+function deselectFlow(flow, ifCreatePending) {
+	var flowKey = makeFlowKey(flow);
+	var newSelectedFlows = [];
+	selectedFlows.forEach(function (flow) {
+		if (!flow ||
+				flowKey !== makeFlowKey(flow) ||
+				flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
+			newSelectedFlows.push(flow);
+		}
+	});
+	selectedFlows = newSelectedFlows;
+	while (selectedFlows.length < 3) {
+		selectedFlows.push(null);
+	}
+
+	updateSelectedFlows();
+}
+
+function deselectFlowIfCreatePending(flow) {
+	deselectFlow(flow, true);
+}
+
 function showFlowChooser() {
 	function rowEnter(d) {
 		var row = d3.select(this);
@@ -113,11 +297,7 @@
 		row.append('div')
 			.classed('black-eye', true).
 			on('click', function () {
-				selectedFlows.unshift(d);
-				selectedFlows = selectedFlows.slice(0, 3);
-
-				updateSelectedFlows();
-				updateTopology();
+				selectFlow(d);
 			});
 
 		row.append('div')
@@ -159,54 +339,7 @@
 	}, 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;
-}
 
 function updateHeader(model) {
 	d3.select('#lastUpdate').text(new Date());
@@ -325,6 +458,10 @@
 	return link['src-switch'] + '=>' + link['dst-switch'];
 }
 
+function makeFlowKey(flow) {
+	return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
+}
+
 function createLinkMap(links) {
 	var linkMap = {};
 	links.forEach(function (link) {
@@ -361,7 +498,6 @@
 
 	var links = reconcilePendingLinks(model);
 	var linkMap = createLinkMap(links);
-//	var flowMap = createFlowMap(model);
 
 	function mouseOverSwitch(data) {
 
@@ -650,9 +786,29 @@
 			if (s1Data.className == 'edge' && s2Data.className == 'edge') {
 				var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
 				if (confirm(prompt)) {
-					alert('do create flow');
-				} else {
-					alert('do not create flow');
+					addFlow(srcData, dstData);
+
+					var flow = {
+						dataPath: {
+							srcPort: {
+								dpid: {
+									value: srcData.dpid
+								}
+							},
+							dstPort: {
+								dpid: {
+									value: dstData.dpid
+								}
+							}
+						},
+						createPending: true
+					};
+
+					selectFlow(flow);
+
+					setTimeout(function () {
+						deselectFlowIfCreatePending(flow);
+					}, pendingTimeout);
 				}
 			} else {
 				var map = linkMap[srcData.dpid];
@@ -697,7 +853,7 @@
 								delete pendingLinks[makeLinkKey(link2)];
 
 								updateTopology();
-							}, 10000);
+							}, pendingTimeout);
 						}
 					}
 				}
@@ -830,9 +986,6 @@
 
 	// remove old links
 	links.exit().remove();
-
-
-	drawFlows();
 }
 
 function updateControllers() {
diff --git a/web/ons-demo/js/controller.js b/web/ons-demo/js/controller.js
index a36ae1c..fd3f6ae 100644
--- a/web/ons-demo/js/controller.js
+++ b/web/ons-demo/js/controller.js
@@ -10,57 +10,64 @@
 	});
 }
 
+function MAC(dpid) {
+	var cmps = dpid.split(':');
+	var MAC = '00:00:c0:a8:' + [cmps[6], cmps[7]].join(':');
+	return MAC;
+}
 
 var controllerFunctions = {
-	l: function (cmd, link) {
+	linkCmd: function (cmd, link) {
 		var url = '/proxy/gui/link/' + [cmd, link['src-switch'], link['src-port'], link['dst-switch'], link['dst-port']].join('/');
 		callURL(url);
 
 	},
-	s: function (cmd, s) {
+	switchCmd: function (cmd, s) {
 		var url = '/proxy/gui/switch/' + [cmd, s.dpid].join('/');
 		callURL(url);
 	},
-	c: function (cmd, c) {
+	ctrlCmd: function (cmd, c) {
 		var url = '/proxy/gui/controller/' + [cmd, c].join('/');
 		callURL(url);
+	},
+	addFlowCmd: function (src, dst) {
+		var url = '/proxy/gui/addflow/' + [src.dpid, 1, dst.dpid, 1, MAC(src.dpid), MAC(dst.dpid)].join('/');
+		callURL(url);
+	},
+	delFlowCmd: function (flow) {
+		var url = '/proxy/gui/delflow/' + flow.flowId.value;
+		callURL(url);
 	}
 };
 
-
-// if (parseURLParameters().mock) {
-// 	urls = mockURLs;
-// }
-
-
 function linkUp(link) {
-	controllerFunctions.l('up', link);
+	controllerFunctions.linkCmd('up', link);
 }
 
 function linkDown(link) {
-	controllerFunctions.l('down', link);
+	controllerFunctions.linkCmd('down', link);
 }
 
 function switchUp(s) {
-	controllerFunctions.s('up', s);
+	controllerFunctions.switchCmd('up', s);
 }
 
 function switchDown(s) {
-	controllerFunctions.s('down', s);
+	controllerFunctions.switchCmd('down', s);
 }
 
 function controllerUp(c) {
-	controllerFunctions.c('up', c);
+	controllerFunctions.ctrlCmd('up', c);
 }
 
 function controllerDown(c) {
-	controllerFunctions.c('down', c);
+	controllerFunctions.ctrlCmd('down', c);
 }
 
-function createFlow(src, dst) {
-
+function addFlow(src, dst) {
+	controllerFunctions.addFlowCmd(src, dst);
 }
 
-function deleteFlow(src, dst) {
-
+function deleteFlow(flow) {
+	controllerFunctions.delFlowCmd(flow);
 }
\ No newline at end of file
diff --git a/web/topology_rest.py b/web/topology_rest.py
index f3a4a3b..0306de9 100755
--- a/web/topology_rest.py
+++ b/web/topology_rest.py
@@ -127,6 +127,31 @@
   resp = Response(result, status=200, mimetype='application/json')
   return resp
 
+@app.route("/proxy/gui/addflow/<src_dpid>/<src_port>/<dst_dpid>/<dst_port>/<srcMAC>/<dstMAC>")
+def proxy_add_flow(src_dpid, src_port, dst_dpid, dst_port, srcMAC, dstMAC):
+  try:
+    command = "curl -s %s/gui/addflow/%s/%s/%s/%s/%s/%s" % (ONOS_GUI3_CONTROL_HOST, src_dpid, src_port, dst_dpid, dst_port, srcMAC, dstMAC)
+    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/delflow/<flow_id>")
+def proxy_del_flow(flow_id):
+  try:
+    command = "curl -s %s/gui/delflow/%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:
@@ -729,7 +754,7 @@
   cmd = 'up'
   result=""
 
-  for dpid in (src_dpid, dst_dpid): 
+  for dpid in (src_dpid, dst_dpid):
     if dpid in core_switches:
       host = controllers[0]
       src_ports = [1, 2, 3, 4, 5]