Initial addition of packet action test module
diff --git a/tests/pktact.py b/tests/pktact.py
new file mode 100644
index 0000000..eb34e7e
--- /dev/null
+++ b/tests/pktact.py
@@ -0,0 +1,111 @@
+"""
+Test cases for testing actions taken on packets
+
+See basic.py for other info.
+
+It is recommended that these definitions be kept in their own
+namespace as different groups of tests will likely define 
+similar identifiers.
+
+  The function test_set_init is called with a complete configuration
+dictionary prior to the invocation of any tests from this file.
+
+  The switch is actively attempting to contact the controller at the address
+indicated oin oft_config
+
+"""
+
+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 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 DirectPacket(basic.SimpleDataPlane):
+    """
+    Test case that checks single port direction action
+
+    Generate a packet
+    Generate and install a matching flow
+    Add action to direct the packet to an egress port
+    Send the packet to ingress dataplane port
+    Verify the packet is received at the egress port only
+    """
+    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)
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        act = action.action_output()
+
+        for idx in range(len(of_ports)):
+            ingress_port = of_ports[idx]
+            egress_port = of_ports[(idx + 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.buffer_id = 0xffffffff
+            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)
+
+            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=1)
+            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')
+            
+