Some major restructuring
Updated README with some warnings.
Added data-plane-only class to allow controlling the dataplane
ports without needing a controller connection.
Subclassed this to allow sending only a packet without doing
any flow mods; both tagged or untagged.
Added the ability to pass a parameter to a test through th
config structure. Use --param=N.
Used the above to set the VLAN id in a tagged pkt in the new test.
Break up description/name in --list when name is long
Restructured pktact.py into different routines, moving a lot of
the base functionality into testutils.py. This allows packet
modification tests to be done independently.
In the process, added support for using tagged and untagged
packets in the tests. Several tests remain to be implemented.
diff --git a/README b/README
index 596d91a..371bbd6 100644
--- a/README
+++ b/README
@@ -4,43 +4,6 @@
Copyright (c) 2010 The Board of Trustees of The Leland Stanford
Junior University
-
-
-=Warning=
-
-==Risks==
-This is still '''experimental''' and it requires root privilege to
-control the dataplane ports. As a consequence, there may be
-'''risks''' to the machine on which this is running.
-Use caution.
-
-==Recovering From Problems==
-
-If the test script, oft, becomes unresponsive, you may find that ^C does not
-break out of the script. In this case you have two options:
-
-* Use ^Z to interrupt the script and return to the shell prompt.
-* Start another terminal window to the same machine.
-
-In either case, you then need to kill the process that is hung. Use the
-following commands:
-
- me@host> ps aux | grep oft
- root 4 0.0 0.0 0 0 ? S< Jul07 0:00 [ksoftirqd/0]
- ...
- root 14066 3.2 0.6 149888 25344 pts/4 Tl 09:27 0:00 python ./oft ...
- me 14074 0.0 0.0 7452 876 pts/4 R+ 09:28 0:00 grep oft
-
- me@host> sudo kill -9 14066
-
-where 14066 is the process ID of the hung process. (Replace it with the PID for your process.)
-
-==Preliminary Work==
-
-This is still preliminary work and there are bugs in the
-framework that need to be ironed out. Please report any issues
-to dtalayco@stanford.edu.
-
Warning
+++++++
@@ -48,8 +11,22 @@
control the dataplane ports. As a consequence, there may be
risks to the machine on which this is running. Use caution.
-Recovering From Problems
-++++++++++++++++++++++++
+Rebuilding
+++++++++++
+
+If you ever make a change to the code in src/oftest/python...
+you must rebuild and reinstall the source code. See Step (2)
+in the Longer Start below.
+
+If you see
+
+ WARNING:..:Could not import file ...
+
+There is likely a Python error in the file. Try invoking the
+Python cli and importing the file directly to get more information.
+
+Recovering From Crash
++++++++++++++++++++++
If the test script, oft, becomes unresponsive, you may find that ^C
does not break out of the script. In this case you have two options:
@@ -75,6 +52,7 @@
framework that need to be ironed out. Please report any issues
to dtalayco@stanford.edu.
+
Introduction
++++++++++++
diff --git a/tests/basic.py b/tests/basic.py
index 465a3c9..5a2e26f 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -69,6 +69,7 @@
sys.exit(1)
def setUp(self):
+ self.logger = basic_logger
signal.signal(signal.SIGINT, self.sig_handler)
basic_logger.info("** START TEST CASE " + str(self))
self.controller = controller.Controller(
@@ -127,6 +128,36 @@
# self.dataplane.show()
# Would like an assert that checks the data plane
+class DataPlaneOnly(unittest.TestCase):
+ """
+ Root class that sets up only the dataplane
+ """
+
+ def sig_handler(self, v1, v2):
+ basic_logger.critical("Received interrupt signal; exiting")
+ print "Received interrupt signal; exiting"
+ self.clean_shutdown = False
+ self.tearDown()
+ sys.exit(1)
+
+ def setUp(self):
+ self.clean_shutdown = False
+ self.logger = basic_logger
+ signal.signal(signal.SIGINT, self.sig_handler)
+ basic_logger.info("** START DataPlaneOnly CASE " + str(self))
+ self.dataplane = dataplane.DataPlane()
+ for of_port, ifname in basic_port_map.items():
+ self.dataplane.port_add(ifname, of_port)
+
+ def tearDown(self):
+ basic_logger.info("Teardown for simple dataplane test")
+ self.dataplane.kill(join_threads=self.clean_shutdown)
+ basic_logger.info("Teardown done")
+
+ def runTest(self):
+ self.dataplane.show()
+ # Would like an assert that checks the data plane
+
class Echo(SimpleProtocol):
"""
Test echo response with no data
diff --git a/tests/oft b/tests/oft
index b4314fe..9bd9e40 100755
--- a/tests/oft
+++ b/tests/oft
@@ -150,6 +150,7 @@
##@var config_default
# The default configuration dictionary for OFT
config_default = {
+ "param" : None,
"platform" : "local",
"controller_host" : "127.0.0.1",
"controller_port" : 6633,
@@ -236,6 +237,8 @@
help="List all tests and exit")
parser.add_option("--verbose", action="store_true",
help="Short cut for --debug=verbose")
+ parser.add_option("--param", type="int",
+ help="Parameter sent to test (for debugging)")
# Might need this if other parsers want command line
# parser.allow_interspersed_args = False
(options, args) = parser.parse_args()
@@ -377,12 +380,17 @@
start_str = " Module " + mod.__name__ + ": "
print start_str + _space_to(22, start_str) + desc
for test in config["all_tests"][mod]:
- desc = eval('mod.' + test + '.__doc__.strip()')
- desc = desc.split('\n')[0]
+ try:
+ desc = eval('mod.' + test + '.__doc__.strip()')
+ desc = desc.split('\n')[0]
+ except:
+ desc = "No description"
if test_prio_get(mod, test) < 0:
start_str = " * " + test + ":"
else:
start_str = " " + test + ":"
+ if len(start_str) > 22:
+ desc = "\n" + _space_to(22, "") + desc
print start_str + _space_to(22, start_str) + desc
print
if not did_print:
diff --git a/tests/pktact.py b/tests/pktact.py
index 7088350..a3280a2 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -39,6 +39,37 @@
#@var pa_config Local copy of global configuration data
pa_config = None
+# For test priority
+#@var test_prio Set test priority for local tests
+test_prio = {}
+
+WILDCARD_VALUES = [ofp.OFPFW_IN_PORT,
+ ofp.OFPFW_DL_VLAN,
+ ofp.OFPFW_DL_SRC,
+ ofp.OFPFW_DL_DST,
+ ofp.OFPFW_DL_TYPE,
+ ofp.OFPFW_NW_PROTO,
+ ofp.OFPFW_TP_SRC,
+ ofp.OFPFW_TP_DST,
+ 0x3F << ofp.OFPFW_NW_SRC_SHIFT,
+ 0x3F << ofp.OFPFW_NW_DST_SHIFT,
+ ofp.OFPFW_DL_VLAN_PCP,
+ ofp.OFPFW_NW_TOS]
+
+MODIFY_ACTION_VALUES = [ofp.OFPAT_SET_VLAN_VID,
+ ofp.OFPAT_SET_VLAN_PCP,
+ ofp.OFPAT_STRIP_VLAN,
+ ofp.OFPAT_SET_DL_SRC,
+ ofp.OFPAT_SET_DL_DST,
+ ofp.OFPAT_SET_NW_SRC,
+ ofp.OFPAT_SET_NW_DST,
+ ofp.OFPAT_SET_NW_TOS,
+ ofp.OFPAT_SET_TP_SRC,
+ ofp.OFPAT_SET_TP_DST]
+
+# Cache supported features to avoid transaction overhead
+cached_supported_actions = None
+
def test_set_init(config):
"""
Set up function for packet action test classes
@@ -69,8 +100,6 @@
self.handleFlow()
def handleFlow(self, pkttype='TCP'):
-
- global pa_port_map
of_ports = pa_port_map.keys()
of_ports.sort()
self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
@@ -92,7 +121,7 @@
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))
+ " to egress " + str(egress_port))
match.in_port = ingress_port
@@ -143,7 +172,6 @@
Verify the packet is received at the two egress ports
"""
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")
@@ -204,7 +232,6 @@
Does not use the flood action
"""
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")
@@ -260,7 +287,6 @@
Does not use the flood action
"""
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")
@@ -312,7 +338,6 @@
Verify the packet is received at all other ports
"""
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")
@@ -362,7 +387,6 @@
Verify the packet is received at all other ports
"""
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")
@@ -413,7 +437,6 @@
Verify the packet is received at all other ports
"""
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")
@@ -463,7 +486,6 @@
Verify the packet is received at all other ports
"""
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")
@@ -516,7 +538,6 @@
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")
@@ -570,151 +591,54 @@
#@todo Should check no other packets received
-class SimpleExactMatch(basic.SimpleDataPlane):
+################################################################
+
+class BaseMatchCase(basic.SimpleDataPlane):
+ def setUp(self):
+ basic.SimpleDataPlane.setUp(self)
+ self.logger = pa_logger
+ def runTest(self):
+ self.logger.info("BaseMatchCase")
+
+class ExactMatch(BaseMatchCase):
"""
- Exercise exact matching for all ports
+ Exercise exact matching for all port pairs
Generate a packet
Generate and install a matching flow without wildcard mask
Add action to forward to a port
Send the packet to the port
Verify the packet is received at all other ports (one port at a time)
- Verify flow_expiration message is correct when command option is set
"""
- IP_ETHTYPE = 0x800
- TCP_PROTOCOL = 0x6
- UDP_PROTOCOL = 0x11
def runTest(self):
- self.flowMatchTest()
+ flow_match_test(self, pa_port_map)
- def flowMatchTest(self, wildcards=0, check_expire=False):
- global pa_port_map
- of_ports = pa_port_map.keys()
- of_ports.sort()
- self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+class ExactMatchTagged(BaseMatchCase):
+ """
+ Exact match for all port pairs with tagged pkts
+ """
- pkt = simple_tcp_packet()
- match = parse.packet_to_flow_match(pkt)
- self.assertTrue(match is not None,
- "Could not generate flow match from pkt")
- match.dl_vlan = ofp.OFP_VLAN_NONE
- match.nw_proto = self.TCP_PROTOCOL
- match.wildcards = wildcards
+ def runTest(self):
+ flow_match_test(self, pa_port_map, dl_vlan=1)
- for idx in range(len(of_ports)):
- ingress_port = of_ports[idx]
- pa_logger.info("Ingress " + str(ingress_port) + " to all the other ports")
- match.in_port = ingress_port
+class ExactMatchTaggedMany(BaseMatchCase):
+ """
+ ExactMatchTagged with many VLANS
+ """
- for egr_idx in range(len(of_ports)):
- if egr_idx == idx:
- continue
+ def runTest(self):
+ for vid in range(1,100,10):
+ flow_match_test(self, pa_port_map, dl_vlan=vid, max_test=5)
+ for vid in range(100,4000,389):
+ flow_match_test(self, pa_port_map, dl_vlan=vid, max_test=5)
+ flow_match_test(self, pa_port_map, dl_vlan=4094, max_test=5)
- rc = delete_all_flows(self.controller, pa_logger)
- self.assertEqual(rc, 0, "Failed to delete all flows")
- do_barrier(self.controller)
+# Don't run by default
+test_prio["ExactMatchTaggedMany"] = -1
- request = message.flow_mod()
- request.match = match
- request.buffer_id = 0xffffffff
- #@todo Need UI to setup FLAGS parameter for flow_mod
- if(check_expire):
- request.flags |= ofp.OFPFF_SEND_FLOW_REM
- request.hard_timeout = 1
- act = action.action_output()
- act.port = of_ports[egr_idx]
- self.assertTrue(request.actions.add(act),
- "Could not add output 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))
- self.dataplane.send(ingress_port, str(pkt))
-
- ofport = of_ports[egr_idx]
- self.verifPkt(ofport, pkt)
-
- #@todo Need UI for enabling response-verification
- if(check_expire):
- self.verifFlowRemoved(request)
-
- def verifPkt(self, ofport, exp_pkt):
- (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(exp_pkt), str(rcv_pkt),
- 'Response packet does not match send packet ' +
- "on port " + str(ofport))
-
- def verifFlowRemoved(self, request):
- (response, raw) = self.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
- self.assertTrue(response is not None,
- 'Flow removed message not received')
-
- req_match = request.match
- res_match = response.match
- if(req_match != res_match):
- self.verifMatchField(req_match, res_match)
-
- self.assertEqual(request.cookie, response.cookie,
- self.matchErrStr('cookie'))
- if (req_match.wildcards != 0):
- self.assertEqual(request.priority, response.priority,
- self.matchErrStr('priority'))
- self.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
- 'Reason is not HARD TIMEOUT')
- self.assertEqual(response.packet_count, 1,
- 'Packet count is not correct')
- self.assertEqual(response.byte_count, len(pkt),
- 'Packet length is not correct')
-
- def verifMatchField(self, req_match, res_match):
- self.assertEqual(str(req_match.wildcards), str(res_match.wildcards),
- self.matchErrStr('wildcards'))
- self.assertEqual(str(req_match.in_port), str(res_match.in_port),
- self.matchErrStr('in_port'))
- self.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
- self.matchErrStr('dl_src'))
- self.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
- self.matchErrStr('dl_dst'))
- self.assertEqual(str(req_match.dl_vlan), str(res_match.dl_vlan),
- self.matchErrStr('dl_vlan'))
- self.assertEqual(str(req_match.dl_vlan_pcp), str(res_match.dl_vlan_pcp),
- self.matchErrStr('dl_vlan_pcp'))
- self.assertEqual(str(req_match.dl_type), str(res_match.dl_type),
- self.matchErrStr('dl_type'))
- if(not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
- and (req_match.dl_type == self.IP_ETHERTYPE)):
- self.assertEqual(str(req_match.nw_tos), str(res_match.nw_tos),
- self.matchErrStr('nw_tos'))
- self.assertEqual(str(req_match.nw_proto), str(res_match.nw_proto),
- self.matchErrStr('nw_proto'))
- self.assertEqual(str(req_match.nw_src), str(res_match.nw_src),
- self.matchErrStr('nw_src'))
- self.assertEqual(str(req_match.nw_dst), str(res_match.nw_dst),
- self.matchErrStr('nw_dst'))
- if(not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
- and ((req_match.nw_proto == self.TCP_PROTOCOL)
- or (req_match.nw_proto == self.UDP_PROTOCOL))):
- self.assertEqual(str(req_match.tp_src), str(res_match.tp_src),
- self.matchErrStr('tp_src'))
- self.assertEqual(str(req_match.tp_dst), str(res_match.tp_dst),
- self.matchErrStr('tp_dst'))
-
- def matchErrStr(self, field):
- return ('Response Match_' + field + ' does not match send message')
-
-class SingleWildcardMatch(SimpleExactMatch):
+class SingleWildcardMatch(BaseMatchCase):
"""
Exercise wildcard matching for all ports
@@ -725,26 +649,20 @@
Verify the packet is received at all other ports (one port at a time)
Verify flow_expiration message is correct when command option is set
"""
- def __init__(self):
- SimpleExactMatch.__init__(self)
- self.wildcards = [ofp.OFPFW_IN_PORT,
- ofp.OFPFW_DL_VLAN,
- ofp.OFPFW_DL_SRC,
- ofp.OFPFW_DL_DST,
- ofp.OFPFW_DL_TYPE,
- ofp.OFPFW_NW_PROTO,
- ofp.OFPFW_TP_SRC,
- ofp.OFPFW_TP_DST,
- 0x3F << ofp.OFPFW_NW_SRC_SHIFT,
- 0x3F << ofp.OFPFW_NW_DST_SHIFT,
- ofp.OFPFW_DL_VLAN_PCP,
- ofp.OFPFW_NW_TOS]
-
def runTest(self):
- for exec_wildcard in range(len(self.wildcards)):
- self.flowMatchTest(exec_wildcard)
+ for wc in WILDCARD_VALUES:
+ flow_match_test(self, pa_port_map, wildcard=wc, max_test=10)
-class AllExceptOneWildcardMatch(SingleWildcardMatch):
+class SingleWildcardMatchTagged(BaseMatchCase):
+ """
+ SingleWildcardMatch with tagged packets
+ """
+ def runTest(self):
+ for wc in WILDCARD_VALUES:
+ flow_match_test(self, pa_port_map, wildcard=wc, dl_vlan=1,
+ max_test=10)
+
+class AllExceptOneWildcardMatch(BaseMatchCase):
"""
Match exactly one field
@@ -756,11 +674,21 @@
Verify flow_expiration message is correct when command option is set
"""
def runTest(self):
- for exec_wildcard in range(len(self.wildcards)):
- all_exp_one_wildcard = ofp.OFPFW_ALL ^ self.wildcards[exec_wildcard]
- self.flowMatchTest(all_exp_one_wildcard)
+ for wc in WILDCARD_VALUES:
+ all_exp_one_wildcard = ofp.OFPFW_ALL ^ wc
+ flow_match_test(self, pa_port_map, wildcard=all_exp_one_wildcard)
-class AllWildcardMatch(SingleWildcardMatch):
+class AllExceptOneWildcardMatchTagged(BaseMatchCase):
+ """
+ Match one field with tagged packets
+ """
+ def runTest(self):
+ for wc in WILDCARD_VALUES:
+ all_exp_one_wildcard = ofp.OFPFW_ALL ^ wc
+ flow_match_test(self, pa_port_map, wildcard=all_exp_one_wildcard,
+ dl_vlan=1)
+
+class AllWildcardMatch(BaseMatchCase):
"""
Create Wildcard-all flow and exercise for all ports
@@ -772,248 +700,155 @@
Verify flow_expiration message is correct when command option is set
"""
def runTest(self):
- self.flowMatchTest(ofp.OFPFW_ALL)
+ flow_match_test(self, pa_port_map, wildcard=ofp.OFPFW_ALL)
-class ExactModifyAction(SimpleExactMatch):
+class AllWildcardMatchTagged(BaseMatchCase):
"""
- Perform Modify action with exact matching for all ports
-
- Generate a packet for transmit
- Generate the expected packet
- Generate and install a matching flow with a modify action and
- an output action without wildcard mask
- Send the packet to the port
- Verify the expected packet is received at all other ports
- (one port at a time)
- Verify flow_expiration message is correct when command option is set
+ AllWildcardMatch with tagged packets
"""
- def __init__(self):
- SimpleExactMatch.__init__(self)
- self.modify_act = [ofp.OFPAT_SET_VLAN_VID,
- ofp.OFPAT_SET_VLAN_PCP,
- ofp.OFPAT_STRIP_VLAN,
- ofp.OFPAT_SET_DL_SRC,
- ofp.OFPAT_SET_DL_DST,
- ofp.OFPAT_SET_NW_SRC,
- ofp.OFPAT_SET_NW_DST,
- ofp.OFPAT_SET_NW_TOS,
- ofp.OFPAT_SET_TP_SRC,
- ofp.OFPAT_SET_TP_DST]
- self.act_bitmap = 0
- for val in self.modify_act:
- self.act_bitmap = self.act_bitmap | (1 << val)
-
def runTest(self):
- self.flowMatchModTest()
+ flow_match_test(self, pa_port_map, wildcard=ofp.OFPFW_ALL, dl_vlan=1)
- def flowMatchModTest(self, wildcards=0, check_expire=False):
- #@todo Refactor this routine to be separate functions for each action
- global pa_port_map
- of_ports = pa_port_map.keys()
- of_ports.sort()
- self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
-
- mod_dl_dst = '43:21:0F:ED:CB:A9'
- mod_dl_src = '7F:ED:CB:A9:87:65'
- mod_dl_vlan = 4094
- mod_dl_vlan_pcp = 7
- mod_ip_src = '10.20.30.40'
- mod_ip_dst = '50.60.70.80'
- mod_ip_tos = 0xf0
- mod_tcp_sport = 4321
- mod_tcp_dport = 8765
-
- 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")
- supported_act = reply.actions
- pa_logger.info("Supported actions: " + hex(supported_act))
- if (supported_act & self.act_bitmap) == 0:
- pa_logger.info("No modification actions supported");
+class AddVLANTag(BaseMatchCase):
+ """
+ Add a VLAN tag to an untagged packet
+ """
+ def runTest(self):
+ new_vid = 2
+ sup_acts = supported_actions_get(self)
+ if not(sup_acts & 1<<ofp.OFPAT_SET_VLAN_VID):
+ pa_logger.info("Skipping add VLAN tag test")
return
- for idx in range(len(of_ports)):
- ingress_port = of_ports[idx]
- pa_logger.info("Ingress " + str(ingress_port) +
- " to all the other ports")
+ len = 100
+ len_w_vid = 104
+ pkt = simple_tcp_packet(pktlen=len)
+ exp_pkt = simple_tcp_packet(pktlen=len_w_vid, dl_vlan_enable=True,
+ dl_vlan=new_vid)
+ vid_act = action.action_set_vlan_vid()
+ vid_act.vlan_vid = new_vid
- for egr_idx in range(len(of_ports)):
- if egr_idx == idx:
- continue
+ flow_match_test(self, pa_port_map, pkt=pkt,
+ exp_pkt=exp_pkt, action_list=[vid_act])
- for exec_mod in range(len(self.modify_act)):
- pkt_len = 100
- dl_dst = '0C:DE:F0:12:34:56'
- dl_src = '01:23:45:67:89:AB'
- dl_vlan_enable = False
- dl_vlan = 0
- dl_vlan_pcp = 0
- ip_src = '192.168.0.1'
- ip_dst = '192.168.0.2'
- ip_tos = 0
- tcp_sport = 1234
- tcp_dport = 80
+class PacketOnly(basic.DataPlaneOnly):
+ """
+ Just send a packet thru the switch
+ """
+ def runTest(self):
+ pkt = simple_tcp_packet()
+ of_ports = pa_port_map.keys()
+ of_ports.sort()
+ ing_port = of_ports[0]
+ pa_logger.info("Sending packet to " + str(ing_port))
+ pa_logger.debug("Data: " + str(pkt).encode('hex'))
+ self.dataplane.send(ing_port, str(pkt))
- pkt = simple_tcp_packet(pktlen=pkt_len,
- dl_dst=dl_dst,
- dl_src=dl_src,
- dl_vlan_enable=dl_vlan_enable,
- dl_vlan=dl_vlan,
- dl_vlan_pcp=dl_vlan_pcp,
- ip_src=ip_src,
- ip_dst=ip_dst,
- ip_tos=ip_tos,
- tcp_sport=tcp_sport,
- tcp_dport=tcp_dport)
+class PacketOnlyTagged(basic.DataPlaneOnly):
+ """
+ Just send a packet thru the switch
+ """
+ def runTest(self):
+ vid = 2
+ if pa_config["param"] is not None:
+ vid = pa_config["param"]
+ print "Param is " + str(pa_config["param"])
+ pkt = simple_tcp_packet(dl_vlan_enable=True, dl_vlan=vid)
+ of_ports = pa_port_map.keys()
+ of_ports.sort()
+ ing_port = of_ports[0]
+ pa_logger.info("Sending packet to " + str(ing_port))
+ pa_logger.debug("Data: " + str(pkt).encode('hex'))
+ self.dataplane.send(ing_port, str(pkt))
- match = parse.packet_to_flow_match(pkt)
- self.assertTrue(match is not None,
- "Could not generate flow match from pkt")
- match.in_port = ingress_port
- match.dl_vlan = ofp.OFP_VLAN_NONE
- match.nw_proto = self.TCP_PROTOCOL
- match.wildcards = wildcards
+test_prio["PacketOnly"] = -1
+test_prio["PacketOnlyTagged"] = -1
- request = message.flow_mod()
- request.match = match
- request.buffer_id = 0xffffffff
- #@todo Need UI to setup FLAGS parameter for flow_mod
- if (check_expire):
- request.flags |= ofp.OFPFF_SEND_FLOW_REM
- request.hard_timeout = 1
+class ModifyVID(BaseMatchCase):
+ def runTest(self):
+ old_vid = 2
+ new_vid = 3
+ sup_acts = supported_actions_get(self)
+ if not(sup_acts & 1<<ofp.OFPAT_SET_VLAN_VID):
+ pa_logger.info("Skipping modify VLAN tag test")
+ return
- exec_act = self.modify_act[exec_mod]
- if exec_act == ofp.OFPAT_SET_VLAN_VID:
- if not(supported_act & 1<<ofp.OFPAT_SET_VLAN_VID):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- pkt_len = pkt_len + 4
- dl_vlan_enable = True
- dl_vlan = mod_dl_vlan
- mod_act = action.action_set_vlan_vid()
- mod_act.vlan_vid = mod_dl_vlan
- elif exec_act == ofp.OFPAT_SET_VLAN_PCP:
- if not(supported_act & 1<<ofp.OFPAT_SET_VLAN_PCP):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- pkt_len = pkt_len + 4
- dl_vlan_enable = True
- dl_vlan_pcp = mod_dl_vlan_pcp
- mod_act = action.action_set_vlan_pcp()
- mod_act.vlan_pcp = mod_dl_vlan_pcp
- elif exec_act == ofp.OFPAT_STRIP_VLAN:
- if not(supported_act & 1<<ofp.OFPAT_STRIP_VLAN):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- dl_vlan_enable = False
- mod_act = action.action_strip_vlan()
- elif exec_act == ofp.OFPAT_SET_DL_SRC:
- if not(supported_act & 1<<ofp.OFPAT_SET_DL_SRC):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- dl_src = mod_dl_src
- mod_act = action.action_set_dl_src()
- mod_act.dl_addr = parse.parse_mac(mod_dl_src)
- elif exec_act == ofp.OFPAT_SET_DL_DST:
- if not(supported_act & 1<<ofp.OFPAT_SET_DL_DST):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- dl_dst = mod_dl_dst
- mod_act = action.action_set_dl_dst()
- mod_act.dl_addr = parse.parse_mac(mod_dl_dst)
- elif exec_act == ofp.OFPAT_SET_NW_SRC:
- if not(supported_act & 1<<ofp.OFPAT_SET_NW_SRC):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- ip_src = mod_ip_src
- mod_act = action.action_set_nw_src()
- mod_act.nw_addr = parse.parse_ip(mod_ip_src)
- elif exec_act == ofp.OFPAT_SET_NW_DST:
- if not(supported_act & 1<<ofp.OFPAT_SET_NW_DST):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- ip_dst = mod_ip_dst
- mod_act = action.action_set_nw_dst()
- mod_act.nw_addr = parse.parse_ip(mod_ip_dst)
- elif exec_act == ofp.OFPAT_SET_NW_TOS:
- if not(supported_act & 1<<ofp.OFPAT_SET_NW_TOS):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- ip_tos = mod_ip_tos
- mod_act = action.action_set_nw_tos()
- mod_act.nw_tos = mod_ip_tos
- elif exec_act == ofp.OFPAT_SET_TP_SRC:
- if not(supported_act & 1<<ofp.OFPAT_SET_TP_SRC):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- tcp_sport = mod_tcp_sport
- mod_act = action.action_set_tp_src()
- mod_act.tp_port = mod_tcp_sport
- elif exec_act == ofp.OFPAT_SET_TP_DST:
- if not(supported_act & 1<<ofp.OFPAT_SET_TP_DST):
- pa_logger.debug("Skipping action " +
- ofp.ofp_action_type_map[exec_act])
- continue
- tcp_dport = mod_tcp_dport
- mod_act = action.action_set_tp_dst()
- mod_act.tp_port = mod_tcp_dport
- else:
- pa_logger.debug("Unknown action " + str(exec_act))
- continue
+ pkt = simple_tcp_packet(dl_vlan_enable=True, dl_vlan=old_vid)
+ exp_pkt = simple_tcp_packet(dl_vlan_enable=True, dl_vlan=new_vid)
+ vid_act = action.action_set_vlan_vid()
+ vid_act.vlan_vid = new_vid
- self.assertTrue(request.actions.add(mod_act),
- "Could not add output action: " +
- ofp.ofp_action_type_map[exec_act])
- pa_logger.info("Testing action " +
- ofp.ofp_action_type_map[exec_act])
- pa_logger.info(request.show())
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=[vid_act])
- rc = delete_all_flows(self.controller, pa_logger)
- self.assertEqual(rc, 0, "Failed to delete all flows")
- do_barrier(self.controller)
+class StripVLANTag(BaseMatchCase):
+ def runTest(self):
+ old_vid = 2
+ sup_acts = supported_actions_get(self)
+ if not(sup_acts & 1<<ofp.OFPAT_STRIP_VLAN):
+ pa_logger.info("Skipping strip VLAN tag test")
+ return
- exp_pkt = simple_tcp_packet(pktlen=pkt_len,
- dl_dst=dl_dst,
- dl_src=dl_src,
- dl_vlan_enable=dl_vlan_enable,
- dl_vlan=dl_vlan,
- dl_vlan_pcp=dl_vlan_pcp,
- ip_src=ip_src,
- ip_dst=ip_dst,
- ip_tos=ip_tos,
- tcp_sport=tcp_sport,
- tcp_dport=tcp_dport)
+ len_w_vid = 104
+ len = 100
+ pkt = simple_tcp_packet(pktlen=len_w_vid, dl_vlan_enable=True,
+ dl_vlan=old_vid)
+ exp_pkt = simple_tcp_packet(pktlen=len)
+ vid_act = action.action_strip_vlan()
- act = action.action_output()
- act.port = of_ports[egr_idx]
- self.assertTrue(request.actions.add(act),
- "Could not add output action for port "
- + str(act.port))
- pa_logger.info(request.show())
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=[vid_act])
- pa_logger.info("Inserting flow")
- rv = self.controller.message_send(request)
- self.assertTrue(rv != -1, "Error installing flow mod")
- do_barrier(self.controller)
+class ModifyL2Src(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
- pa_logger.info("Sending packet to dp port " +str(ingress_port))
- self.dataplane.send(ingress_port, str(pkt))
+class ModifyL2Dst(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
- ofport = of_ports[egr_idx]
- self.verifPkt(ofport, exp_pkt)
+class ModifyL3Src(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
- #@todo Need UI for enabling response-verification
- if(check_expire):
- self.verifFlowRemoved(request)
+class ModifyL3Dst(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
+
+class ModifyL4Src(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
+
+class ModifyL4Dst(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
+
+class ModifyTOS(BaseMatchCase):
+ def runTest(self):
+ pa_logger("To be implemented")
+
+test_prio["ModifyL2Src"] = -1
+test_prio["ModifyL2Dst"] = -1
+test_prio["ModifyL3Src"] = -1
+test_prio["ModifyL3Dst"] = -1
+test_prio["ModifyL4Src"] = -1
+test_prio["ModifyL4Dst"] = -1
+test_prio["ModifyTOS"] = -1
+
+def supported_actions_get(parent, use_cache=True):
+ """
+ Get the bitmap of supported actions from the switch
+ If use_cache is false, the cached value will be updated
+ """
+ global cached_supported_actions
+ if cached_supported_actions is None or not use_cache:
+ request = message.features_request()
+ (reply, pkt) = parent.controller.transact(request, timeout=2)
+ parent.assertTrue(reply is not None, "Did not get response to ftr req")
+ cached_supported_actions = reply.actions
+ pa_logger.info("Supported actions: " + hex(cached_supported_actions))
+
+ return cached_supported_actions
if __name__ == "__main__":
print "Please run through oft script: ./oft --test_spec=basic"
diff --git a/tests/testutils.py b/tests/testutils.py
index ea82ecc..044e5c8 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -18,6 +18,11 @@
import oftest.parse as parse
import logging
+# Some useful defines
+IP_ETHERTYPE = 0x800
+TCP_PROTOCOL = 0x6
+UDP_PROTOCOL = 0x11
+
def delete_all_flows(ctrl, logger):
"""
Delete all flows on the switch
@@ -39,6 +44,7 @@
dl_vlan_enable=False,
dl_vlan=0,
dl_vlan_pcp=0,
+ dl_vlan_cfi=0,
ip_src='192.168.0.1',
ip_dst='192.168.0.2',
ip_tos=0,
@@ -65,9 +71,10 @@
shouldn't assume anything about this packet other than that
it is a valid ethernet/IP/TCP frame.
"""
+ # Note Dot1Q.id is really CFI
if (dl_vlan_enable):
pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
- scapy.Dot1Q(prio=dl_vlan_pcp, id=0, vlan=dl_vlan)/ \
+ scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
scapy.TCP(sport=tcp_sport, dport=tcp_dport)
else:
@@ -206,3 +213,263 @@
port_number=ofport, timeout=1)
assert_if.assertTrue(rcv_pkt is None,
"Unexpected pkt on port " + str(ofport))
+
+
+def receive_pkt_verify(parent, egr_port, exp_pkt):
+ """
+ Receive a packet and verify it matches an expected value
+
+ parent must implement dataplane, assertTrue and assertEqual
+ """
+ (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(port_number=egr_port,
+ timeout=1)
+ if rcv_pkt is None:
+ parent.logger.error("ERROR: No packet received from " + str(egr_port))
+
+ parent.assertTrue(rcv_pkt is not None,
+ "Did not receive packet port " + str(egr_port))
+ parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
+ str(rcv_port))
+
+ if str(exp_pkt) != str(rcv_pkt):
+ parent.logger.error("ERROR: Packet match failed.")
+ parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
+ + str(exp_pkt).encode('hex'))
+ parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
+ + str(rcv_pkt).encode('hex'))
+ parent.assertEqual(str(exp_pkt), str(rcv_pkt),
+ "Packet match error on port " + str(egr_port))
+
+def match_verify(parent, req_match, res_match):
+ """
+ Verify flow matches agree; if they disagree, report where
+
+ parent must implement assertEqual
+ Use str() to ensure content is compared and not pointers
+ """
+
+ parent.assertEqual(req_match.wildcards, res_match.wildcards,
+ 'Match failed: wildcards: ' + hex(req_match.wildcards) +
+ " != " + hex(res_match.wildcards))
+ parent.assertEqual(req_match.in_port, res_match.in_port,
+ 'Match failed: in_port: ' + str(req_match.in_port) +
+ " != " + str(res_match.in_port))
+ parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
+ 'Match failed: dl_src: ' + str(req_match.dl_src) +
+ " != " + str(res_match.dl_src))
+ parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
+ 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
+ " != " + str(res_match.dl_dst))
+ parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
+ 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
+ " != " + str(res_match.dl_vlan))
+ parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
+ 'Match failed: dl_vlan_pcp: ' +
+ str(req_match.dl_vlan_pcp) + " != " +
+ str(res_match.dl_vlan_pcp))
+ parent.assertEqual(req_match.dl_type, res_match.dl_type,
+ 'Match failed: dl_type: ' + str(req_match.dl_type) +
+ " != " + str(res_match.dl_type))
+
+ if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
+ and (req_match.dl_type == IP_ETHERTYPE)):
+ parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
+ 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
+ " != " + str(res_match.nw_tos))
+ parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
+ 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
+ " != " + str(res_match.nw_proto))
+ parent.assertEqual(req_match.nw_src, res_match.nw_src,
+ 'Match failed: nw_src: ' + str(req_match.nw_src) +
+ " != " + str(res_match.nw_src))
+ parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
+ 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
+ " != " + str(res_match.nw_dst))
+
+ if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
+ and ((req_match.nw_proto == TCP_PROTOCOL)
+ or (req_match.nw_proto == UDP_PROTOCOL))):
+ parent.assertEqual(req_match.tp_src, res_match.tp_src,
+ 'Match failed: tp_src: ' +
+ str(req_match.tp_src) +
+ " != " + str(res_match.tp_src))
+ parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
+ 'Match failed: tp_dst: ' +
+ str(req_match.tp_dst) +
+ " != " + str(res_match.tp_dst))
+
+def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
+ """
+ Receive a flow removed msg and verify it matches expected
+
+ @params parent Must implement controller, assertEqual
+ @param pkt_count If >= 0, verify packet count
+ @param byte_count If >= 0, verify byte count
+ """
+ (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
+ parent.assertTrue(response is not None, 'No flow removed message received')
+
+ if request is None:
+ return
+
+ parent.assertEqual(request.cookie, response.cookie,
+ "Flow removed cookie error: " +
+ hex(request.cookie) + " != " + hex(response.cookie))
+
+ req_match = request.match
+ res_match = response.match
+ verifyMatchField(req_match, res_match)
+
+ if (req_match.wildcards != 0):
+ parent.assertEqual(request.priority, response.priority,
+ 'Flow remove prio mismatch: ' +
+ str(request,priority) + " != " +
+ str(response.priority))
+ parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
+ 'Flow remove reason is not HARD TIMEOUT:' +
+ str(response.reason))
+ if pkt_count >= 0:
+ parent.assertEqual(response.packet_count, pkt_count,
+ 'Flow removed failed, packet count: ' +
+ str(response.packet_count) + " != " +
+ str(pkt_count))
+ if byte_count >= 0:
+ parent.assertEqual(response.byte_count, byte_count,
+ 'Flow removed failed, byte count: ' +
+ str(response.byte_count) + " != " +
+ str(byte_count))
+
+def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
+ egr_port=None, egr_queue=None, check_expire=False):
+ """
+ Create a flow message
+
+ Match on packet with given wildcards.
+ See flow_match_test for other parameter descriptoins
+ @param egr_queue if not None, make the output an enqueue action
+ """
+ match = parse.packet_to_flow_match(pkt)
+ parent.assertTrue(match is not None, "Flow match from pkt failed")
+ match.wildcards = wildcards
+ match.in_port = ing_port
+
+ request = message.flow_mod()
+ request.match = match
+ request.buffer_id = 0xffffffff
+ if check_expire:
+ request.flags |= ofp.OFPFF_SEND_FLOW_REM
+ request.hard_timeout = 1
+
+ if action_list is not None:
+ for act in action_list:
+ parent.logger.debug("Adding action " + act.show())
+ rv = request.actions.add(act)
+ parent.assertTrue(rv, "Could not add action" + act.show())
+
+ # Set up output/enqueue action if directed
+ if egr_queue is not None:
+ parent.assertTrue(egr_port is not None, "Egress port not set")
+ act = action.action_enqueue()
+ act.port = egr_port
+ act.queue_id = egr_queue
+ rv = request.actions.add(act)
+ parent.assertTrue(rv, "Could not add enqueue action " +
+ str(egr_port) + " Q: " + str(egr_queue))
+ elif egr_port is not None:
+ act = action.action_output()
+ act.port = egr_port
+ rv = request.actions.add(act)
+ parent.assertTrue(rv, "Could not add output action " + str(egr_port))
+
+ parent.logger.debug(request.show())
+
+ return request
+
+def flow_msg_install(parent, request, clear_table=True):
+ """
+ Install a flow mod message in the switch
+
+ @param parent Must implement controller, assertEqual, assertTrue
+ @param request The request, all set to go
+ @param clear_table If true, clear the flow table before installing
+ """
+ if clear_table:
+ parent.logger.debug("Clear flow table")
+ rc = delete_all_flows(parent.controller, parent.logger)
+ parent.assertEqual(rc, 0, "Failed to delete all flows")
+ do_barrier(parent.controller)
+
+ parent.logger.debug("Insert flow")
+ rv = parent.controller.message_send(request)
+ parent.assertTrue(rv != -1, "Error installing flow mod")
+ do_barrier(parent.controller)
+
+def flow_match_test_port_pair(parent, ing_port, egr_port, wildcards=0,
+ dl_vlan=-1, pkt=None, exp_pkt=None,
+ action_list=None, check_expire=False):
+ """
+ Flow match test on single TCP packet
+
+ Run test with packet through switch from ing_port to egr_port
+ See flow_match_test for parameter descriptions
+ """
+
+ parent.logger.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
+ parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
+ " exp: " + str(check_expire))
+ if pkt is None:
+ pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
+
+ request = flow_msg_create(parent, pkt, ing_port=ing_port,
+ wildcards=wildcards, egr_port=egr_port,
+ action_list=action_list)
+
+ flow_msg_install(parent, request)
+
+ parent.logger.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+ parent.dataplane.send(ing_port, str(pkt))
+
+ if exp_pkt is None:
+ exp_pkt = pkt
+ receive_pkt_verify(parent, egr_port, exp_pkt)
+
+ if check_expire:
+ #@todo Not all HW supports both pkt and byte counters
+ flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
+
+def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
+ exp_pkt=None, action_list=None, check_expire=False,
+ max_test=0):
+ """
+ Run flow_match_test_port_pair on all port pairs
+
+ @param max_test If > 0 no more than this number of tests are executed.
+ @param parent Must implement controller, dataplane, assertTrue, assertEqual
+ and logger
+ @param pkt If not None, use this packet for ingress
+ @param wildcards For flow match entry
+ @param dl_vlan If not -1, and pkt is not None, create a pkt w/ VLAN tag
+ @param exp_pkt If not None, use this as the expected output pkt; els use pkt
+ @param action_list Additional actions to add to flow mod
+ @param check_expire Check for flow expiration message
+ """
+ of_ports = port_map.keys()
+ of_ports.sort()
+ parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+ test_count = 0
+
+ for ing_idx in range(len(of_ports)):
+ ingress_port = of_ports[ing_idx]
+ for egr_idx in range(len(of_ports)):
+ if egr_idx == ing_idx:
+ continue
+ egress_port = of_ports[egr_idx]
+ flow_match_test_port_pair(parent, ingress_port, egress_port,
+ dl_vlan=dl_vlan, pkt=pkt,
+ exp_pkt=exp_pkt, action_list=action_list,
+ check_expire=check_expire)
+ test_count += 1
+ if (max_test > 0) and (test_count > max_test):
+ parent.logger.info("Ran " + str(test_count) + " tests; exiting")
+ return
+