Merge branch 'tyabe'

Conflicts:
	tests/pktact.py
diff --git a/src/python/oftest/parse.py b/src/python/oftest/parse.py
index 54b7226..ed05aa1 100644
--- a/src/python/oftest/parse.py
+++ b/src/python/oftest/parse.py
@@ -310,6 +310,14 @@
         match.wildcards &= ~OFPFW_NW_TOS
 
     if tcp:
+        match.nw_proto = 6
+        match.wildcards &= ~OFPFW_NW_PROTO
+    elif not tcp and udp:
+        tcp = udp
+        match.nw_proto = 17
+        match.wildcards &= ~OFPFW_NW_PROTO
+
+    if tcp:
         match.tp_src = tcp.sport
         match.wildcards &= ~OFPFW_TP_SRC
         match.tp_dst = tcp.dport
diff --git a/tests/basic.py b/tests/basic.py
index b464b5e..0e91ad5 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -68,7 +68,7 @@
 
     def setUp(self):
         signal.signal(signal.SIGINT, self.sig_handler)
-        basic_logger.info("Setup for " + str(self))
+        basic_logger.info("** START TEST CASE " + str(self))
         self.controller = controller.Controller(
             host=basic_config["controller_host"],
             port=basic_config["controller_port"])
@@ -84,7 +84,7 @@
         basic_logger.info("Connected " + str(self.controller.switch_addr))
 
     def tearDown(self):
-        basic_logger.info("Teardown for simple proto test")
+        basic_logger.info("** END TEST CASE " + str(self))
         self.controller.shutdown()
         #@todo Review if join should be done on clean_shutdown
         if self.clean_shutdown:
@@ -96,6 +96,11 @@
         self.assertTrue(self.controller.switch_socket is not None,
                         str(self) + 'No connection to switch')
 
+    def assertTrue(self, cond, msg):
+        if not cond:
+            basic_logger.error("** FAILED ASSERTION: " + msg)
+        unittest.TestCase.assertTrue(self, cond, msg)
+
 class SimpleDataPlane(SimpleProtocol):
     """
     Root class that sets up the controller and dataplane
@@ -245,6 +250,27 @@
         self.assertTrue(response is not None, "Did not get response")
         basic_logger.debug(response.show())
 
+class TableStatsGet(SimpleProtocol):
+    """
+    Get table stats 
+
+    Simply verify table stats get transaction
+    """
+    def runTest(self):
+        basic_logger.info("Running TableStatsGet")
+        basic_logger.info("Inserting trial flow")
+        request = message.flow_mod()
+        request.match.wildcards = ofp.OFPFW_ALL
+        request.buffer_id = 0xffffffff
+        rv = self.controller.message_send(request)
+        self.assertTrue(rv != -1, "Failed to insert test flow")
+        
+        basic_logger.info("Sending table stats request")
+        request = message.table_stats_request()
+        response, pkt = self.controller.transact(request, timeout=2)
+        self.assertTrue(response is not None, "Did not get response")
+        basic_logger.debug(response.show())
+
 class FlowMod(SimpleProtocol):
     """
     Insert a flow
@@ -271,39 +297,34 @@
 
     def runTest(self):
         basic_logger.info("Running " + str(self))
-        request = message.features_request()
-        reply, pkt = self.controller.transact(request, timeout=2)
-        self.assertTrue(reply is not None, "Did not get response to ftr req")
-        basic_logger.info("Reply has " + str(len(reply.ports)) + " ports")
-        #basic_logger.debug(reply.show())
-        tport = reply.ports[0]
-        basic_logger.info("No flood bit port 0 is now " + 
-                          str(reply.ports[0].config ^ ofp.OFPPC_NO_FLOOD))
+        for of_port, ifname in basic_port_map.items(): # Grab first port
+            break
 
-        mod = message.port_mod()
-        mod.port_no = tport.port_no
-        mod.hw_addr = tport.hw_addr
-        mod.config = tport.config ^ ofp.OFPPC_NO_FLOOD
-        mod.mask = ofp.OFPPC_NO_FLOOD
-        mod.advertise = tport.advertised
-        #basic_logger.debug(mod.show())
-        rv = self.controller.message_send(mod)
+        (hw_addr, config, advert) = \
+            port_config_get(self.controller, of_port, basic_logger)
+        self.assertTrue(config is not None, "Did not get port config")
+
+        basic_logger.debug("No flood bit port " + str(of_port) + " is now " + 
+                           str(config & ofp.OFPPC_NO_FLOOD))
+
+        rv = port_config_set(self.controller, of_port,
+                             config ^ ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD,
+                             basic_logger)
         self.assertTrue(rv != -1, "Error sending port mod")
 
         # Verify change took place with same feature request
-        request.header.xid = 0 # Force new XID
-        reply2, pkt = self.controller.transact(request, timeout=2)
-        self.assertTrue(reply2 is not None, "Did not get response ftr req2")
-        #basic_logger.debug(reply2.show())
-        self.assertTrue(reply2.ports[0].port_no == tport.port_no,
-                   "Feature reply port order changed; unhandled")
-        self.assertTrue(reply2.ports[0].config & ofp.OFPPC_NO_FLOOD !=
-                   tport.config & ofp.OFPPC_NO_FLOOD, 
-                   "Bit change did not take")
+        (hw_addr, config2, advert) = \
+            port_config_get(self.controller, of_port, basic_logger)
+        basic_logger.debug("No flood bit port " + str(of_port) + " is now " + 
+                           str(config2 & ofp.OFPPC_NO_FLOOD))
+        self.assertTrue(config2 is not None, "Did not get port config2")
+        self.assertTrue(config2 & ofp.OFPPC_NO_FLOOD !=
+                        config & ofp.OFPPC_NO_FLOOD,
+                        "Bit change did not take")
         # Set it back
-        mod.config ^= ofp.OFPPC_NO_FLOOD
-        rv = self.controller.message_send(mod)
-        self.assertTrue(rv != -1, "Error sending port mod2")
+        rv = port_config_set(self.controller, of_port, config, 
+                             ofp.OFPPC_NO_FLOOD, basic_logger)
+        self.assertTrue(rv != -1, "Error sending port mod")
 
 if __name__ == "__main__":
     print "Please run through oft script:  ./oft --test_spec=basic"
diff --git a/tests/caps.py b/tests/caps.py
new file mode 100644
index 0000000..1146cf1
--- /dev/null
+++ b/tests/caps.py
@@ -0,0 +1,158 @@
+"""
+Basic capabilities and capacities tests
+
+"""
+
+import logging
+
+import unittest
+
+import oftest.controller as controller
+import oftest.cstruct as ofp
+import oftest.message as message
+import oftest.dataplane as dataplane
+import oftest.action as action
+import oftest.parse as parse
+import basic
+
+from testutils import *
+
+#@var caps_port_map Local copy of the configuration map from OF port
+# numbers to OS interfaces
+caps_port_map = None
+#@var caps_logger Local logger object
+caps_logger = None
+#@var caps_config Local copy of global configuration data
+caps_config = None
+
+def test_set_init(config):
+    """
+    Set up function for caps test classes
+
+    @param config The configuration dictionary; see oft
+    """
+
+    global caps_port_map
+    global caps_logger
+    global caps_config
+
+    caps_logger = logging.getLogger("caps")
+    caps_logger.info("Initializing caps test set")
+    caps_port_map = config["port_map"]
+    caps_config = config
+
+
+def flow_caps_common(obj, is_exact=True):
+    """
+    The common function for 
+
+    @param obj The calling object
+    @param is_exact If True, checking exact match; else wildcard
+    """
+
+    global caps_port_map
+    of_ports = caps_port_map.keys()
+    of_ports.sort()
+
+    rv = delete_all_flows(obj.controller, caps_logger)
+    obj.assertEqual(rv, 0, "Failed to delete all flows")
+
+    pkt = simple_tcp_packet()
+    match = parse.packet_to_flow_match(pkt)
+    obj.assertTrue(match is not None, "Could not generate flow match from pkt")
+    for port in of_ports:
+        break;
+    match.in_port = port
+    match.nw_src = 1
+    request = message.flow_mod()
+    count_check = 101  # fixme:  better way to determine this.
+    if is_exact:
+        match.wildcards = 0
+    else:
+        match.wildcards |= ofp.OFPFW_DL_SRC
+
+    request.match = match
+    caps_logger.info(request.show())
+
+    tstats = message.table_stats_request()
+    try:  # Determine the table index to check (or "all")
+        table_idx = caps_config["caps_table_idx"]
+    except:
+        table_idx = -1  # Accumulate all table counts
+
+    # Make sure we can install at least one flow
+    caps_logger.info("Inserting initial flow")
+    rv = obj.controller.message_send(request)
+    obj.assertTrue(rv != -1, "Error installing flow mod")
+    do_barrier(obj.controller)
+    flow_count = 1
+
+    caps_logger.info("Table idx: " + str(table_idx))
+    caps_logger.info("Check every " + str(count_check) + " inserts")
+
+    while True:
+        request.match.nw_src += 1
+        rv = obj.controller.message_send(request)
+        do_barrier(obj.controller)
+        flow_count += 1
+        if flow_count % count_check == 0:
+            response, pkt = obj.controller.transact(tstats, timeout=2)
+            obj.assertTrue(response is not None, "Get tab stats failed")
+            caps_logger.info(response.show())
+            if table_idx == -1:  # Accumulate for all tables
+                active_flows = 0
+                for stats in response.stats:
+                    active_flows += stats.active_count
+            else: # Table index to use specified in config
+                active_flows = response.stats[table_idx].active_count
+            if active_flows != flow_count:
+                break
+
+    caps_logger.error("RESULT: " + str(flow_count) + " flows inserted")
+    caps_logger.error("RESULT: " + str(active_flows) + " flows reported")
+
+
+class FillTableExact(basic.SimpleProtocol):
+    """
+    Fill the flow table with exact matches
+
+    Fill table until no more flows can be added.  Report result.
+    Increment the source IP address.  Assume the flow table will
+    fill in less than 4 billion inserts
+
+    To check the number of flows in the tables is expensive, so
+    it's only done periodically.  This is controlled by the
+    count_check variable.
+
+    A switch may have multiple tables.  The default behaviour
+    is to count all the flows in all the tables.  By setting 
+    the parameter "caps_table_idx" in the configuration array,
+    you can control which table to check.
+    """
+    def runTest(self):
+        caps_logger.info("Running " + str(self))
+        flow_caps_common(self)
+
+
+class FillTableWC(basic.SimpleProtocol):
+    """
+    Fill the flow table with wildcard matches
+
+    Fill table using wildcard entries until no more flows can be
+    added.  Report result.
+    Increment the source IP address.  Assume the flow table will
+    fill in less than 4 billion inserts
+
+    To check the number of flows in the tables is expensive, so
+    it's only done periodically.  This is controlled by the
+    count_check variable.
+
+    A switch may have multiple tables.  The default behaviour
+    is to count all the flows in all the tables.  By setting 
+    the parameter "caps_table_idx" in the configuration array,
+    you can control which table to check.
+
+    """
+    def runTest(self):
+        caps_logger.info("Running " + str(self))
+        flow_caps_common(self, is_exact=False)
diff --git a/tests/flow_expire.py b/tests/flow_expire.py
new file mode 100644
index 0000000..cc1fee9
--- /dev/null
+++ b/tests/flow_expire.py
@@ -0,0 +1,106 @@
+"""
+Flow expire test case.
+Similar to Flow expire test case in the perl test harness.
+
+"""
+
+import logging
+
+import unittest
+import random
+
+import oftest.controller as controller
+import oftest.cstruct as ofp
+import oftest.message as message
+import oftest.dataplane as dataplane
+import oftest.action as action
+import oftest.parse as parse
+import basic
+
+from testutils import *
+from time import sleep
+
+#@var port_map Local copy of the configuration map from OF port
+# numbers to OS interfaces
+pa_port_map = None
+#@var pa_logger Local logger object
+pa_logger = None
+#@var pa_config Local copy of global configuration data
+pa_config = None
+
+def test_set_init(config):
+    """
+    Set up function for packet action test classes
+
+    @param config The configuration dictionary; see oft
+    """
+
+    global pa_port_map
+    global pa_logger
+    global pa_config
+
+    pa_logger = logging.getLogger("pkt_act")
+    pa_logger.info("Initializing test set")
+    pa_port_map = config["port_map"]
+    pa_config = config
+
+class FlowExpire(basic.SimpleDataPlane):
+    """
+    Verify flow expire messages are properly generated.
+
+    Generate a packet
+    Generate and install a matching flow with idle timeout = 1 sec
+    Verify the flow expiration message is received
+    """
+    def runTest(self):
+        global pa_port_map
+
+        of_ports = pa_port_map.keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        pkt = simple_tcp_packet()
+        match = parse.packet_to_flow_match(pkt)
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        act = action.action_output()
+
+        ingress_port = pa_config["base_of_port"]
+        egress_port  = (pa_config["base_of_port"] + 1) % len(of_ports)
+        pa_logger.info("Ingress " + str(ingress_port) + 
+                       " to egress " + str(egress_port))
+        
+        match.in_port = ingress_port
+        
+        request = message.flow_mod()
+        request.match = match
+        request.cookie = random.randint(0,9007199254740992)
+        request.buffer_id = 0xffffffff
+        request.idle_timeout = 1
+        request.flags |= ofp.OFPFF_SEND_FLOW_REM
+        act.port = egress_port
+        self.assertTrue(request.actions.add(act), "Could not add action")
+        
+        pa_logger.info("Inserting flow")
+        rv = self.controller.message_send(request)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+        do_barrier(self.controller)
+
+        (response, raw) = self.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
+        
+        self.assertTrue(response is not None, 
+                        'Did not receive flow removed message ')
+
+        self.assertEqual(request.cookie, response.cookie,
+                         'Cookies do not match')
+
+        self.assertEqual(ofp.OFPRR_IDLE_TIMEOUT, response.reason,
+                         'Flow table entry removal reason is not idle_timeout')
+
+        self.assertEqual(match, response.match,
+                         'Flow table entry does not match')
+        
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
new file mode 100644
index 0000000..48d6b6b
--- /dev/null
+++ b/tests/flow_stats.py
@@ -0,0 +1,123 @@
+"""
+Flow stats test case.
+Similar to Flow stats test case in the perl test harness.
+
+"""
+
+import logging
+
+import unittest
+import random
+
+import oftest.controller as controller
+import oftest.cstruct as ofp
+import oftest.message as message
+import oftest.dataplane as dataplane
+import oftest.action as action
+import oftest.parse as parse
+import basic
+
+from testutils import *
+from time import sleep
+
+#@var port_map Local copy of the configuration map from OF port
+# numbers to OS interfaces
+pa_port_map = None
+#@var pa_logger Local logger object
+pa_logger = None
+#@var pa_config Local copy of global configuration data
+pa_config = None
+
+def test_set_init(config):
+    """
+    Set up function for packet action test classes
+
+    @param config The configuration dictionary; see oft
+    """
+
+    global pa_port_map
+    global pa_logger
+    global pa_config
+
+    pa_logger = logging.getLogger("pkt_act")
+    pa_logger.info("Initializing test set")
+    pa_port_map = config["port_map"]
+    pa_config = config
+
+class FlowStats(basic.SimpleDataPlane):
+    """
+    Verify flow stats are properly retrieved.
+
+    Generate a packet
+    Generate and install a matching flow with idle timeout = 1 sec
+    Verify the flow expiration message is received
+    """
+    def runTest(self):
+        global pa_port_map
+
+        of_ports = pa_port_map.keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        pkt = simple_tcp_packet()
+        match = parse.packet_to_flow_match(pkt)
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        act = action.action_output()
+
+        ingress_port = pa_config["base_of_port"]
+        egress_port  = (pa_config["base_of_port"] + 1) % len(of_ports)
+        pa_logger.info("Ingress " + str(ingress_port) + 
+                       " to egress " + str(egress_port))
+        
+        match.in_port = ingress_port
+        
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.match = match
+        flow_mod_msg.cookie = random.randint(0,9007199254740992)
+        flow_mod_msg.buffer_id = 0xffffffff
+        flow_mod_msg.idle_timeout = 1
+        act.port = egress_port
+        self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
+        
+        stat_req = message.flow_stats_request()
+        stat_req.match = match
+        stat_req.table_id = 0xff
+        stat_req.out_port = ofp.OFPP_NONE;
+
+        do_barrier(self.controller)
+        pa_logger.info("Sending stats request")
+        rv = self.controller.message_send(stat_req)
+        self.assertTrue(rv != -1, "Error sending flow stat req")
+        do_barrier(self.controller)
+
+        (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
+        
+        pa_logger.info("Inserting flow")
+        rv = self.controller.message_send(flow_mod_msg)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+        do_barrier(self.controller)
+
+        pa_logger.info("Sending packet to dp port " + 
+                       str(ingress_port))
+        self.dataplane.send(ingress_port, str(pkt))
+        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=2)
+        self.assertTrue(rcv_pkt is not None, "Did not receive packet")
+        pa_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
+                        str(rcv_port))
+        self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
+        self.assertEqual(str(pkt), str(rcv_pkt),
+                         'Response packet does not match send packet')
+            
+        pa_logger.info("Sending stats request")
+        rv = self.controller.message_send(stat_req)
+        self.assertTrue(rv != -1, "Error sending flow stat req")
+        do_barrier(self.controller)
+
+        (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
+        #print "YYY: Stats reply is \n%s" % (response.show())
+        self.assertTrue(len(response.stats) == 1, "Did not receive flow stats reply")
diff --git a/tests/pktact.py b/tests/pktact.py
index dd8c509..8d886b9 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -15,6 +15,8 @@
 
 """
 
+import copy
+
 import logging
 
 import unittest
@@ -84,8 +86,8 @@
         act = action.action_output()
 
         for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
             ingress_port = of_ports[idx]
             egress_port = of_ports[(idx + 1) % len(of_ports)]
@@ -130,7 +132,6 @@
     def runTest(self):
         self.handleFlow(pkttype='ICMP')
 
-
 class DirectTwoPorts(basic.SimpleDataPlane):
     """
     Send packet to two egress ports
@@ -155,8 +156,8 @@
         act = action.action_output()
 
         for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
             ingress_port = of_ports[idx]
             egress_port1 = of_ports[(idx + 1) % len(of_ports)]
@@ -184,27 +185,11 @@
             pa_logger.info("Sending packet to dp port " + 
                            str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            (rcv_port1, rcv_pkt1, pkt_time1) = self.dataplane.poll(timeout=1)
-            (rcv_port2, rcv_pkt2, pkt_time2) = self.dataplane.poll(timeout=1)
-            self.assertTrue(rcv_pkt1 is not None, "Did not receive packet 1")
-            self.assertTrue(rcv_pkt2 is not None, "Did not receive packet 2")
-            pa_logger.debug("Packet len " + str(len(rcv_pkt1)) + " in on " + 
-                         str(rcv_port1))
-            pa_logger.debug("Packet len " + str(len(rcv_pkt2)) + " in on " + 
-                         str(rcv_port2))
+            yes_ports = set([egress_port1, egress_port2])
+            no_ports = set(of_ports).difference(yes_ports)
 
-            # Check if ports swapped
-            if (rcv_port1 == egress_port2 and rcv_port2 == egress_port1):
-                (rcv_port2, rcv_port1) = (rcv_port1, rcv_port2)
-                (rcv_pkt2, rcv_pkt1) = (rcv_pkt1, rcv_pkt2)
-            self.assertEqual(rcv_port1, egress_port1,
-                             "Unexpected receive port 1")
-            self.assertEqual(rcv_port2, egress_port2,
-                             "Unexpected receive port 2")
-            self.assertEqual(str(pkt), str(rcv_pkt1),
-                             'Response packet does not match send packet 1')
-            self.assertEqual(str(pkt), str(rcv_pkt2),
-                             'Response packet does not match send packet 2')
+            receive_pkt_check(self.dataplane, pkt, yes_ports, no_ports,
+                              self, pa_logger)
 
 class DirectMCNonIngress(basic.SimpleDataPlane):
     """
@@ -231,50 +216,36 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + 
                            " all non-ingress ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
             request.match = match
             request.buffer_id = 0xffffffff
-            for egr_idx in range(len(of_ports)):
-                if egr_idx == idx:
+            for egress_port in of_ports:
+                if egress_port == ingress_port:
                     continue
-                egress_port = of_ports[egr_idx]
                 act.port = egress_port
                 self.assertTrue(request.actions.add(act), 
                                 "Could not add output to " + str(egress_port))
-            pa_logger.info(request.show())
+            pa_logger.debug(request.show())
 
             pa_logger.info("Inserting flow")
             rv = self.controller.message_send(request)
             self.assertTrue(rv != -1, "Error installing flow mod")
             do_barrier(self.controller)
 
-            pa_logger.info("Sending packet to dp port " + 
-                           str(ingress_port))
+            pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                if egr_idx == idx:
-                    continue
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
+            yes_ports = set(of_ports).difference([ingress_port])
+            receive_pkt_check(self.dataplane, pkt, yes_ports, [ingress_port],
+                              self, pa_logger)
 
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
 
 class DirectMC(basic.SimpleDataPlane):
     """
@@ -301,21 +272,18 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + " to all ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
             request.match = match
             request.buffer_id = 0xffffffff
-            for egr_idx in range(len(of_ports)):
-                egress_port = of_ports[egr_idx]
-                if egr_idx == idx:
+            for egress_port in of_ports:
+                if egress_port == ingress_port:
                     act.port = ofp.OFPP_IN_PORT
                 else:
                     act.port = egress_port
@@ -330,18 +298,8 @@
 
             pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
-
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
+            receive_pkt_check(self.dataplane, pkt, of_ports, [], self,
+                              pa_logger)
 
 class Flood(basic.SimpleDataPlane):
     """
@@ -366,13 +324,11 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + " to all ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
@@ -390,21 +346,9 @@
 
             pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                if egr_idx == idx:
-                    continue
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
-
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
-
+            yes_ports = set(of_ports).difference([ingress_port])
+            receive_pkt_check(self.dataplane, pkt, yes_ports, [ingress_port],
+                              self, pa_logger)
 
 class FloodPlusIngress(basic.SimpleDataPlane):
     """
@@ -430,13 +374,11 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + " to all ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
@@ -457,18 +399,8 @@
 
             pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
-
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
+            receive_pkt_check(self.dataplane, pkt, of_ports, [], self,
+                              pa_logger)
 
 class All(basic.SimpleDataPlane):
     """
@@ -493,13 +425,11 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + " to all ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
@@ -517,20 +447,9 @@
 
             pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                if egr_idx == idx:
-                    continue
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
-
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
+            yes_ports = set(of_ports).difference([ingress_port])
+            receive_pkt_check(self.dataplane, pkt, yes_ports, [ingress_port],
+                              self, pa_logger)
 
 class AllPlusIngress(basic.SimpleDataPlane):
     """
@@ -556,13 +475,11 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
-        for idx in range(len(of_ports)):
-            rc = delete_all_flows(self.controller, pa_logger)
-            self.assertEqual(rc, 0, "Failed to delete all flows")
+        for ingress_port in of_ports:
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
 
-            ingress_port = of_ports[idx]
             pa_logger.info("Ingress " + str(ingress_port) + " to all ports")
-
             match.in_port = ingress_port
 
             request = message.flow_mod()
@@ -583,18 +500,75 @@
 
             pa_logger.info("Sending packet to dp port " + str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            for egr_idx in range(len(of_ports)):
-                ofport = of_ports[egr_idx]
-                (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(
-                    port_number=ofport, timeout=1)
-                self.assertTrue(rcv_pkt is not None, 
-                                "Did not receive packet port " + str(ofport))
-                pa_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on "
-                                + str(rcv_port))
+            receive_pkt_check(self.dataplane, pkt, of_ports, [], self,
+                              pa_logger)
+            
+class FloodMinusPort(basic.SimpleDataPlane):
+    """
+    Config port with No_Flood and test Flood action
 
-                self.assertEqual(str(pkt), str(rcv_pkt),
-                             'Response packet does not match send packet ' +
-                                 "on port " + str(ofport))
+    Generate a packet
+    Generate a matching flow
+    Add action to forward to OFPP_ALL
+    Set port to no-flood
+    Send the packet to ingress dataplane port
+    Verify the packet is received at all other ports except
+    the ingress port and the no_flood port
+    """
+    def runTest(self):
+        global pa_port_map
+        of_ports = pa_port_map.keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) > 2, "Not enough ports for test")
+
+        pkt = simple_tcp_packet()
+        match = parse.packet_to_flow_match(pkt)
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        act = action.action_output()
+
+        for idx in range(len(of_ports)):
+            rv = delete_all_flows(self.controller, pa_logger)
+            self.assertEqual(rv, 0, "Failed to delete all flows")
+
+            ingress_port = of_ports[idx]
+            no_flood_idx = (idx + 1) % len(of_ports)
+            no_flood_port = of_ports[no_flood_idx]
+            rv = port_config_set(self.controller, no_flood_port,
+                                 ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD,
+                                 pa_logger)
+            self.assertEqual(rv, 0, "Failed to set port config")
+
+            match.in_port = ingress_port
+
+            request = message.flow_mod()
+            request.match = match
+            request.buffer_id = 0xffffffff
+            act.port = ofp.OFPP_FLOOD
+            self.assertTrue(request.actions.add(act), 
+                            "Could not add flood port action")
+            pa_logger.info(request.show())
+
+            pa_logger.info("Inserting flow")
+            rv = self.controller.message_send(request)
+            self.assertTrue(rv != -1, "Error installing flow mod")
+            do_barrier(self.controller)
+
+            pa_logger.info("Sending packet to dp port " + str(ingress_port))
+            pa_logger.info("No flood port is " + str(no_flood_port))
+            self.dataplane.send(ingress_port, str(pkt))
+            no_ports = set([ingress_port, no_flood_port])
+            yes_ports = set(of_ports).difference(no_ports)
+            receive_pkt_check(self.dataplane, pkt, yes_ports, no_ports, self,
+                              pa_logger)
+
+            # Turn no flood off again
+            rv = port_config_set(self.controller, no_flood_port,
+                                 0, ofp.OFPPC_NO_FLOOD, pa_logger)
+            self.assertEqual(rv, 0, "Failed to reset port config")
+
+            #@todo Should check no other packets received
 
 class SimpleExactMatch(basic.SimpleDataPlane):
     """
@@ -1006,3 +980,6 @@
                     #@todo Need UI for enabling response-verification
                     if(check_expire):
                         self.verifFlowRemoved(request)
+
+if __name__ == "__main__":
+    print "Please run through oft script:  ./oft --test_spec=basic"
diff --git a/tests/remote.py b/tests/remote.py
index 07779f9..5931153 100644
--- a/tests/remote.py
+++ b/tests/remote.py
@@ -20,3 +20,4 @@
 
     global remote_port_map
     config["port_map"] = remote_port_map.copy()
+    config["caps_table_idx"] = 0
diff --git a/tests/testutils.py b/tests/testutils.py
index 5a0be14..ea82ecc 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -1,5 +1,6 @@
 
 import sys
+import copy
 
 try:
     import scapy.all as scapy
@@ -127,3 +128,81 @@
 def do_barrier(ctrl):
     b = message.barrier_request()
     ctrl.transact(b)
+
+
+def port_config_get(controller, port_no, logger):
+    """
+    Get a port's configuration
+
+    Gets the switch feature configuration and grabs one port's
+    configuration
+
+    @returns (hwaddr, config, advert) The hwaddress, configuration and
+    advertised values
+    """
+    request = message.features_request()
+    reply, pkt = controller.transact(request, timeout=2)
+    logger.debug(reply.show())
+    if reply is None:
+        logger.warn("Get feature request failed")
+        return None, None, None
+    for idx in range(len(reply.ports)):
+        if reply.ports[idx].port_no == port_no:
+            return (reply.ports[idx].hw_addr, reply.ports[idx].config,
+                    reply.ports[idx].advertised)
+    
+    logger.warn("Did not find port number for port config")
+    return None, None, None
+
+def port_config_set(controller, port_no, config, mask, logger):
+    """
+    Set the port configuration according the given parameters
+
+    Gets the switch feature configuration and updates one port's
+    configuration value according to config and mask
+    """
+    logger.info("Setting port " + str(port_no) + " to config " + str(config))
+    request = message.features_request()
+    reply, pkt = controller.transact(request, timeout=2)
+    if reply is None:
+        return -1
+    logger.debug(reply.show())
+    for idx in range(len(reply.ports)):
+        if reply.ports[idx].port_no == port_no:
+            break
+    if idx >= len(reply.ports):
+        return -1
+    mod = message.port_mod()
+    mod.port_no = port_no
+    mod.hw_addr = reply.ports[idx].hw_addr
+    mod.config = config
+    mod.mask = mask
+    mod.advertise = reply.ports[idx].advertised
+    rv = controller.message_send(mod)
+    return rv
+
+def receive_pkt_check(dataplane, pkt, yes_ports, no_ports, assert_if, logger):
+    """
+    Check for proper receive packets across all ports
+    @param dataplane The dataplane object
+    @param pkt Expected packet; may be None if yes_ports is empty
+    @param yes_ports Set or list of ports that should recieve packet
+    @param no_ports Set or list of ports that should not receive packet
+    @param assert_if Object that implements assertXXX
+    """
+    for ofport in yes_ports:
+        logger.debug("Checking for pkt on port " + str(ofport))
+        (rcv_port, rcv_pkt, pkt_time) = dataplane.poll(
+            port_number=ofport, timeout=1)
+        assert_if.assertTrue(rcv_pkt is not None, 
+                             "Did not receive pkt on " + str(ofport))
+        assert_if.assertEqual(str(pkt), str(rcv_pkt),
+                              "Response packet does not match send packet " +
+                              "on port " + str(ofport))
+
+    for ofport in no_ports:
+        logger.debug("Negative check for pkt on port " + str(ofport))
+        (rcv_port, rcv_pkt, pkt_time) = dataplane.poll(
+            port_number=ofport, timeout=1)
+        assert_if.assertTrue(rcv_pkt is None, 
+                             "Unexpected pkt on port " + str(ofport))
diff --git a/tools/pylibopenflow/pylib/cpythonize.py b/tools/pylibopenflow/pylib/cpythonize.py
index 89c9a84..22b5214 100644
--- a/tools/pylibopenflow/pylib/cpythonize.py
+++ b/tools/pylibopenflow/pylib/cpythonize.py
@@ -470,7 +470,7 @@
             elif (isinstance(member, cheader.cstruct)):
                 code.append(self.tab*2 + "outstr += prefix + '" + 
                             member.name + ": \\n' ")
-                code.append(self.tab*2 + "self." + member.name + 
+                code.append(self.tab*2 + "outstr += self." + member.name + 
                             ".show(prefix + '  ')")
             elif (isinstance(member, cheader.carray) and
                   not isinstance(member.object, cheader.cprimitive)):