From 94a6caa2a0859b8d4ffa45d9f5d6b5eb0ffa91ed Mon Sep 17 00:00:00 2001
From: Ryan Izard <rizard@g.clemson.edu>
Date: Sun, 21 Dec 2014 12:58:34 -0500
Subject: [PATCH] Updated circuitpusher with new SFP syntax. Updated
 Forwarding(Base) to use a default priority of 1, since in OF1.3 we have a
 priority=0 table-miss flow that will also match. Trying to update the Web UI
 to display flows correctly.

---
 apps/circuitpusher/circuitpusher.py           |  18 +-
 .../forwarding/Forwarding.java                |  32 +-
 .../routing/ForwardingBase.java               |   9 +-
 .../resources/web/js/models/switchmodel.js    | 484 +++++++++---------
 4 files changed, 277 insertions(+), 266 deletions(-)

diff --git a/apps/circuitpusher/circuitpusher.py b/apps/circuitpusher/circuitpusher.py
index 49bbcfacc..4fa66904a 100755
--- a/apps/circuitpusher/circuitpusher.py
+++ b/apps/circuitpusher/circuitpusher.py
@@ -143,26 +143,26 @@ if args.action=='add':
             # send one flow mod per pair of APs in route
             # using StaticFlowPusher rest API
 
-            # IMPORTANT NOTE: current Floodlight StaticflowEntryPusher
+            # IMPORTANT NOTE: current Floodlight StaticflowPusher
             # assumes all flow entries to have unique name across all switches
             # this will most possibly be relaxed later, but for now we
             # encode each flow entry's name with both switch dpid, user
             # specified name, and flow type (f: forward, r: reverse, farp/rarp: arp)
 
-            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"nw_src\":\"%s\", \"nw_dst\":\"%s\", \"dl_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".f", args.srcAddress, args.dstAddress, "0x800", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp)
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ipv4_src\":\"%s\", \"ipv4_dst\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".f", args.srcAddress, args.dstAddress, "0x800", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp)
             result = os.popen(command).read()
             print command
 
-            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_dpa\":\"%s\", \"dl_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".farp", args.srcAddress, args.dstAddress, "0x806", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp)
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_tpa\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".farp", args.srcAddress, args.dstAddress, "0x806", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp)
             result = os.popen(command).read()
             print command
 
 
-            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"nw_src\":\"%s\", \"nw_dst\":\"%s\", \"dl_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".r", args.dstAddress, args.srcAddress, "0x800", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp)
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ipv4_src\":\"%s\", \"ipv4_dst\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".r", args.dstAddress, args.srcAddress, "0x800", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp)
             result = os.popen(command).read()
             print command
 
-            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_dpa\":\"%s\", \"dl_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".rarp",args.dstAddress, args.srcAddress, "0x806", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp)
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_tpa\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".rarp",args.dstAddress, args.srcAddress, "0x806", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp)
             result = os.popen(command).read()
             print command
 
@@ -198,19 +198,19 @@ elif args.action=='delete':
             sw = data['Dpid']
             print data, sw
 
-            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp)
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp)
             result = os.popen(command).read()
             print command, result
 
-            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp)
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp)
             result = os.popen(command).read()
             print command, result
 
-            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp)
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp)
             result = os.popen(command).read()
             print command, result
 
-            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp)
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp)
             result = os.popen(command).read()
             print command, result            
             
diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
index 48643d6e2..107ee8016 100644
--- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
+++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
@@ -75,9 +75,6 @@ import org.slf4j.LoggerFactory;
 public class Forwarding extends ForwardingBase implements IFloodlightModule {
 	protected static Logger log = LoggerFactory.getLogger(Forwarding.class);
 
-	protected static int DEFAULT_HARD_TIMEOUT = 0; // not final b/c could be configured from config file
-	protected static int DEFAULT_IDLE_TIMEOUT = 5;
-
 	@Override
 	@LogMessageDoc(level="ERROR",
 	message="Unexpected decision made for this packet-in={}",
@@ -155,11 +152,12 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule {
 		U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
 
 		fmb.setCookie(cookie)
-		.setHardTimeout(DEFAULT_HARD_TIMEOUT)
-		.setIdleTimeout(DEFAULT_IDLE_TIMEOUT)
+		.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
+		.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
 		.setBufferId(OFBufferId.NO_BUFFER)
 		.setMatch(mb.build())
-		.setActions(actions); // empty list
+		.setActions(actions) // empty list
+		.setPriority(FLOWMOD_DEFAULT_PRIORITY);
 
 		try {
 			if (log.isDebugEnabled()) {
@@ -449,21 +447,25 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule {
 		Map<String, String> configParameters = context.getConfigParams(this);
 		String tmp = configParameters.get("hard-timeout");
 		if (tmp != null) {
-			DEFAULT_HARD_TIMEOUT = Integer.parseInt(tmp);
-			log.info("Default hard timeout set to {}.", DEFAULT_HARD_TIMEOUT);
+			FLOWMOD_DEFAULT_HARD_TIMEOUT = Integer.parseInt(tmp);
+			log.info("Default hard timeout set to {}.", FLOWMOD_DEFAULT_HARD_TIMEOUT);
 		} else {
-			log.info("Default hard timeout not configured. Using {}.", DEFAULT_HARD_TIMEOUT);
+			log.info("Default hard timeout not configured. Using {}.", FLOWMOD_DEFAULT_HARD_TIMEOUT);
 		}
 		tmp = configParameters.get("idle-timeout");
 		if (tmp != null) {
-			DEFAULT_IDLE_TIMEOUT = Integer.parseInt(tmp);
-			log.info("Default idle timeout set to {}.", DEFAULT_IDLE_TIMEOUT);
+			FLOWMOD_DEFAULT_IDLE_TIMEOUT = Integer.parseInt(tmp);
+			log.info("Default idle timeout set to {}.", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
 		} else {
-			log.info("Default idle timeout not configured. Using {}.", DEFAULT_IDLE_TIMEOUT);
+			log.info("Default idle timeout not configured. Using {}.", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
+		}
+		tmp = configParameters.get("priority");
+		if (tmp != null) {
+			FLOWMOD_DEFAULT_PRIORITY = Integer.parseInt(tmp);
+			log.info("Default priority set to {}.", FLOWMOD_DEFAULT_PRIORITY);
+		} else {
+			log.info("Default priority not configured. Using {}.", FLOWMOD_DEFAULT_PRIORITY);
 		}
-
-
-
 	}
 
 	@Override
diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
index 634757b52..1235cf522 100644
--- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
+++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
@@ -80,8 +80,9 @@ public abstract class ForwardingBase implements IOFMessageListener {
 	protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // TODO: find sweet spot
 	protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms
 
-	public static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
-	public static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
+	public static int FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
+	public static int FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
+	public static int FLOWMOD_DEFAULT_PRIORITY = 1; // 0 is the default table-miss flow in OF1.3+, so we need to use 1
 
 	public static final short FLOWMOD_DEFAULT_IDLE_TIMEOUT_CONSTANT = 5;
 	public static final short FLOWMOD_DEFAULT_HARD_TIMEOUT_CONSTANT = 0;
@@ -266,7 +267,8 @@ public abstract class ForwardingBase implements IOFMessageListener {
 			.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
 			.setBufferId(OFBufferId.NO_BUFFER)
 			.setCookie(cookie)
-			.setOutPort(outPort);
+			.setOutPort(outPort)
+			.setPriority(FLOWMOD_DEFAULT_PRIORITY);
 
 			try {
 				if (log.isTraceEnabled()) {
@@ -487,6 +489,7 @@ public abstract class ForwardingBase implements IOFMessageListener {
 		fmb.setCookie(cookie)
 		.setHardTimeout(hardTimeout)
 		.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
+		.setPriority(FLOWMOD_DEFAULT_PRIORITY)
 		.setBufferId(OFBufferId.NO_BUFFER)
 		.setMatch(mb.build())
 		.setActions(actions);
diff --git a/src/main/resources/web/js/models/switchmodel.js b/src/main/resources/web/js/models/switchmodel.js
index fc47aea60..5a9403fed 100644
--- a/src/main/resources/web/js/models/switchmodel.js
+++ b/src/main/resources/web/js/models/switchmodel.js
@@ -1,244 +1,250 @@
 /*
-   Copyright 2012 IBM
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
+ Copyright 2012 IBM
+ 
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
 
 window.Switch = Backbone.Model.extend({
-
-    urlRoot:"/wm/core/switch/",
-    
-    defaults: {
-        datapathDescription: '',
-        hardwareDescription: '',
-        manufacturerDescription: '',
-        serialNumber: '',
-        softwareDescription: '',
-        flowCount: ' ',
-        packetCount: ' ',
-        byteCount: ' ',
-    },
-
-    initialize:function () {
-        var self = this;
-
-        //console.log("fetching switch " + this.id + " desc")
-        $.ajax({
-            url:hackBase + "/wm/core/switch/" + self.id + '/desc/json',
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch " + self.id + " desc");
-                //console.log(data['desc']);
-                self.set(data['desc']);
-            }
-        });
-
-        //console.log("fetching switch " + this.id + " aggregate")
-        $.ajax({
-            url:hackBase + "/wm/core/switch/" + self.id + '/aggregate/json',
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch " + self.id + " aggregate");
-                //console.log(data['aggregate']);
-                self.set(data['aggregate']);
-            }
-        });
-        self.trigger('add');
-        this.ports = new PortCollection();
-        this.flows = new FlowCollection();
-        //this.loadPorts();
-        //this.loadFlows();
-    },
-
-    fetch:function () {
-        this.initialize()
-    },
-
-    loadPorts:function () {
-        var self = this;
-        //console.log("fetching switch " + this.id + " ports")
-        //console.log("fetching switch " + this.id + " features")
-        $.when($.ajax({
-            url:hackBase + "/wm/core/switch/" + self.id + '/port/json',
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch " + self.id + " ports");
-                var old_ids = self.ports.pluck('id');
-                //console.log("old_ids" + old_ids);
-
-                // create port models
-                _.each(data['port'], function(p) {
-                    // workaround for REST serialization signed/unsigned bug
-                    if(p.portNumber < 0) {p.portNumber = 65536 + p.portNumber};
-                    
-                    p.id = self.id+'-'+p.portNumber;
-                    old_ids = _.without(old_ids, p.id);
-                    p.dropped = p.receiveDropped + p.transmitDropped;
-                    p.errors = p.receiveCRCErrors + p.receiveFrameErrors + p.receiveOverrunErrors +
-                        p.receiveFrameErrors + p.transmitErrors;
-                    // this is a knda kludgy way to merge models
-                    var m = self.ports.get(p.id);
-                    if(m) {
-                        m.set(p, {silent: true});
-                    } else {
-                        self.ports.add(p, {silent: true});
-                    }
-                    //console.log(p);
-                });
-                
-                // old_ids now holds ports that no longer exist; remove them
-                //console.log("old_ids" + old_ids);
-                _.each(old_ids, function(p) {
-                    console.log("removing port " + p);
-                    self.remove({id:p});
-                });
-            }
-        }),
-        $.ajax({
-            url:hackBase + "/wm/core/switch/" + self.id + '/port-desc/json',
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch " + self.id + " features");
-                //console.log(data['portDesc']);
-                // update port models
-                _.each(data['portDesc'], function(p) {
-                    p.id = self.id+'-'+p.portNumber;
-                    if(p.name != p.portNumber) {
-                        p.name = p.portNumber + ' (' + p.name + ')';
-                    }
-                    p.status = '';
-                    p.status += (p.state & 1) ? 'DOWN' : 'UP';
-                    switch(p.currentFeatures & 0x7f) {
-                    case 1:
-                        p.status += ' 10 Mbps';
-                        break;
-                    case 2:
-                        p.status += ' 10 Mbps FDX';
-                        break;
-                    case 4:
-                        p.status += ' 100 Mbps';
-                        break;
-                    case 8:
-                        p.status += ' 100 Mbps FDX';
-                        break;
-                    case 16:
-                        p.status += ' 1 Gbps'; // RLY?
-                        break;
-                    case 32:
-                        p.status += ' 1 Gbps FDX';
-                        break;
-                    case 64:
-                        p.status += ' 10 Gbps FDX';
-                        break;
-                    }
-                    // TODO parse copper/fiber, autoneg, pause
-                    
-                    // this is a knda kludgy way to merge models
-                    var m = self.ports.get(p.id);
-                    if(m) {
-                        m.set(p, {silent: true});
-                    } else {
-                        self.ports.add(p, {silent: true});
-                    }
-                    //console.log(p);
-                });
-            }
-        })).done(function() {
-            self.ports.trigger('add'); // batch redraws
-        });
-    },
-    
-    loadFlows:function () {
-        var self = this;
-        //console.log("fetching switch " + this.id + " flows")
-        $.ajax({
-            url:hackBase + "/wm/core/switch/" + self.id + '/flow/json',
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch " + self.id + " flows");
-                var flows = data['flows'];
-                //console.log(flows);
-
-                // create flow models
-                var i = 0;
-                _.each(flows, function(f) {
-                    f.id = self.id + '-' + i++;
-
-                    // build human-readable match
-                    f.matchHTML = '';
-                    if(f.hasOwnProperty('match')) {
-                        _.each(f.match, function(value , key) {
-                            f.matchHTML += key + "=" + value +" ";
-                        },f);
-                    }
-                    f.matchHTML = f.matchHTML.substr(0, f.matchHTML.length - 2);
-                    
-                    f.applyActionText = '';
-                    f.writeActionText = '';
-                    if(f.hasOwnProperty('instructions')) {
-                        if(f.instructions.hasOwnProperty('instruction_apply_actions')) {
-                            _.each(f.instructions.instruction_apply_actions, function(value, key) {
-                                f.applyActionText  += key + ":" + value +" ";
-                            },f);
-                        }
-                        if(f.instructions.hasOwnProperty('instruction_write_actions')) {
-                            _.each(f.instructions.instruction_write_actions, function(value, key) {
-                                f.writeActionText  += key + ":" + value +" ";
-                            },f);
-                        }
-
-                    }
-                    // build human-readable action list
-                    f.applyActionText = f.applyActionText.substr(0, f.applyActionText.length - 2);
-                    f.writeActionText = f.writeActionText.substr(0, f.writeActionText.length - 2);
-                    //console.log(f);
-                    self.flows.add(f, {silent: true});
-                });
-                self.flows.trigger('add');
-            }
-        });
-    },
-});
+                                      
+                                      urlRoot:"/wm/core/switch/",
+                                      
+                                      defaults: {
+                                      datapathDescription: '',
+                                      hardwareDescription: '',
+                                      manufacturerDescription: '',
+                                      serialNumber: '',
+                                      softwareDescription: '',
+                                      flowCount: ' ',
+                                      packetCount: ' ',
+                                      byteCount: ' ',
+                                      },
+                                      
+                                      initialize:function () {
+                                      var self = this;
+                                      
+                                      //console.log("fetching switch " + this.id + " desc")
+                                      $.ajax({
+                                             url:hackBase + "/wm/core/switch/" + self.id + '/desc/json',
+                                             dataType:"json",
+                                             success:function (data) {
+                                             //console.log("fetched  switch " + self.id + " desc");
+                                             //console.log(data['desc']);
+                                             self.set(data['desc']);
+                                             }
+                                             });
+                                      
+                                      //console.log("fetching switch " + this.id + " aggregate")
+                                      $.ajax({
+                                             url:hackBase + "/wm/core/switch/" + self.id + '/aggregate/json',
+                                             dataType:"json",
+                                             success:function (data) {
+                                             //console.log("fetched  switch " + self.id + " aggregate");
+                                             //console.log(data['aggregate']);
+                                             self.set(data['aggregate']);
+                                             }
+                                             });
+                                      self.trigger('add');
+                                      this.ports = new PortCollection();
+                                      this.flows = new FlowCollection();
+                                      //this.loadPorts();
+                                      //this.loadFlows();
+                                      },
+                                      
+                                      fetch:function () {
+                                      this.initialize()
+                                      },
+                                      
+                                      loadPorts:function () {
+                                      var self = this;
+                                      //console.log("fetching switch " + this.id + " ports")
+                                      //console.log("fetching switch " + this.id + " features")
+                                      $.when($.ajax({
+                                                    url:hackBase + "/wm/core/switch/" + self.id + '/port/json',
+                                                    dataType:"json",
+                                                    success:function (data) {
+                                                    //console.log("fetched  switch " + self.id + " ports");
+                                                    var old_ids = self.ports.pluck('id');
+                                                    //console.log("old_ids" + old_ids);
+                                                    
+                                                    // create port models
+                                                    _.each(data['port'], function(p) {
+                                                           // workaround for REST serialization signed/unsigned bug
+                                                           if(p.portNumber < 0) {p.portNumber = 65536 + p.portNumber};
+                                                           
+                                                           p.id = self.id+'-'+p.portNumber;
+                                                           old_ids = _.without(old_ids, p.id);
+                                                           p.dropped = p.receiveDropped + p.transmitDropped;
+                                                           p.errors = p.receiveCRCErrors + p.receiveFrameErrors + p.receiveOverrunErrors +
+                                                           p.receiveFrameErrors + p.transmitErrors;
+                                                           // this is a knda kludgy way to merge models
+                                                           var m = self.ports.get(p.id);
+                                                           if(m) {
+                                                           m.set(p, {silent: true});
+                                                           } else {
+                                                           self.ports.add(p, {silent: true});
+                                                           }
+                                                           //console.log(p);
+                                                           });
+                                                    
+                                                    // old_ids now holds ports that no longer exist; remove them
+                                                    //console.log("old_ids" + old_ids);
+                                                    _.each(old_ids, function(p) {
+                                                           console.log("removing port " + p);
+                                                           self.remove({id:p});
+                                                           });
+                                                    }
+                                                    }),
+                                             $.ajax({
+                                                    url:hackBase + "/wm/core/switch/" + self.id + '/port-desc/json',
+                                                    dataType:"json",
+                                                    success:function (data) {
+                                                    //console.log("fetched  switch " + self.id + " features");
+                                                    //console.log(data['portDesc']);
+                                                    // update port models
+                                                    _.each(data['portDesc'], function(p) {
+                                                           p.id = self.id+'-'+p.portNumber;
+                                                           if(p.name != p.portNumber) {
+                                                           p.name = p.portNumber + ' (' + p.name + ')';
+                                                           }
+                                                           p.status = '';
+                                                           p.status += (p.state & 1) ? 'DOWN' : 'UP';
+                                                           switch(p.currentFeatures & 0x7f) {
+                                                           case 1:
+                                                           p.status += ' 10 Mbps';
+                                                           break;
+                                                           case 2:
+                                                           p.status += ' 10 Mbps FDX';
+                                                           break;
+                                                           case 4:
+                                                           p.status += ' 100 Mbps';
+                                                           break;
+                                                           case 8:
+                                                           p.status += ' 100 Mbps FDX';
+                                                           break;
+                                                           case 16:
+                                                           p.status += ' 1 Gbps'; // RLY?
+                                                           break;
+                                                           case 32:
+                                                           p.status += ' 1 Gbps FDX';
+                                                           break;
+                                                           case 64:
+                                                           p.status += ' 10 Gbps FDX';
+                                                           break;
+                                                           }
+                                                           // TODO parse copper/fiber, autoneg, pause
+                                                           
+                                                           // this is a knda kludgy way to merge models
+                                                           var m = self.ports.get(p.id);
+                                                           if(m) {
+                                                           m.set(p, {silent: true});
+                                                           } else {
+                                                           self.ports.add(p, {silent: true});
+                                                           }
+                                                           //console.log(p);
+                                                           });
+                                                    }
+                                                    })).done(function() {
+                                                             self.ports.trigger('add'); // batch redraws
+                                                             });
+                                      },
+                                      
+                                      loadFlows:function () {
+                                      var self = this;
+                                      //console.log("fetching switch " + this.id + " flows")
+                                      $.ajax({
+                                             url:hackBase + "/wm/core/switch/" + self.id + '/flow/json',
+                                             dataType:"json",
+                                             success:function (data) {
+                                             console.log("fetched  switch " + self.id + " flows");
+                                             var flows = data['flows'];
+                                             console.log(flows);
+                                             
+                                             // create flow models
+                                             var i = 0;
+                                             _.each(flows, function(f) {
+                                                    f.id = self.id + '-' + i++;
+                                                    
+                                                    // build human-readable match
+                                                    f.matchHTML = '';
+                                                    if(f.hasOwnProperty('match')) {
+                                                    _.each(f.match, function(value , key) {
+                                                           f.matchHTML += key + "=" + value +" ";
+                                                           },f);
+                                                    }
+                                                    f.matchHTML = f.matchHTML.substr(0, f.matchHTML.length - 1);
+                                                    
+                                                    f.applyActionText = '';
+                                                    f.writeActionText = '';
+                                                    if(f.hasOwnProperty('instructions')) {
+                                                    if(f.instructions.hasOwnProperty('instruction_apply_actions')) {
+                                                    _.each(f.instructions.instruction_apply_actions, function(value, key) {
+                                                           f.applyActionText  += key + ":" + value +" ";
+                                                           },f);
+                                                    }
+                                                    if(f.instructions.hasOwnProperty('instruction_write_actions')) {
+                                                    _.each(f.instructions.instruction_write_actions, function(value, key) {
+                                                           f.writeActionText  += key + ":" + value +" ";
+                                                           },f);
+                                                    }
+                                                    
+                                                    }
+                                                    if(f.hasOwnProperty('actions')) {
+                                                    _.each(f.actions, function(value, key) {
+                                                           f.applyActionText += key + ":" + value +" ";
+                                                           },f);
+                                                    }
+                                                                                                        }
+                                                    // build human-readable action list
+                                                    f.applyActionText = f.applyActionText.substr(0, f.applyActionText.length - 1);
+                                                    f.writeActionText = f.writeActionText.substr(0, f.writeActionText.length - 1);
+                                                    console.log(f);
+                                                    self.flows.add(f, {silent: true});
+                                                    });
+                                             self.flows.trigger('add');
+                                             }
+                                             });
+                                      },
+                                      });
 
 window.SwitchCollection = Backbone.Collection.extend({
-
-    model:Switch,
-    
-    fetch:function () {
-        var self = this;
-        //console.log("fetching switch list")
-        $.ajax({
-            url:hackBase + "/wm/core/controller/switches/json",
-            dataType:"json",
-            success:function (data) {
-                //console.log("fetched  switch list: " + data.length);
-                //console.log(data);
-                var old_ids = self.pluck('id');
-                //console.log("old_ids" + old_ids);
-                
-                _.each(data, function(sw) {
-                    old_ids = _.without(old_ids, sw['switchDPID']);
-                    self.add({id: sw['switchDPID'], inetAddress: sw.inetAddress,
-                              connectedSince: new Date(sw.connectedSince).toLocaleString()})});
-                
-                // old_ids now holds switches that no longer exist; remove them
-                //console.log("old_ids" + old_ids);
-                _.each(old_ids, function(sw) {
-                    console.log("removing switch " + sw);
-                    self.remove({id:sw});
-                });
-            },
-        });
-    },
-
-});
+                                                     
+                                                     model:Switch,
+                                                     
+                                                     fetch:function () {
+                                                     var self = this;
+                                                     //console.log("fetching switch list")
+                                                     $.ajax({
+                                                            url:hackBase + "/wm/core/controller/switches/json",
+                                                            dataType:"json",
+                                                            success:function (data) {
+                                                            //console.log("fetched  switch list: " + data.length);
+                                                            //console.log(data);
+                                                            var old_ids = self.pluck('id');
+                                                            //console.log("old_ids" + old_ids);
+                                                            
+                                                            _.each(data, function(sw) {
+                                                                   old_ids = _.without(old_ids, sw['switchDPID']);
+                                                                   self.add({id: sw['switchDPID'], inetAddress: sw.inetAddress,
+                                                                            connectedSince: new Date(sw.connectedSince).toLocaleString()})});
+                                                            
+                                                            // old_ids now holds switches that no longer exist; remove them
+                                                            //console.log("old_ids" + old_ids);
+                                                            _.each(old_ids, function(sw) {
+                                                                   console.log("removing switch " + sw);
+                                                                   self.remove({id:sw});
+                                                                   });
+                                                            },
+                                                            });
+                                                     },
+                                                     
+                                                     });
-- 
GitLab