Added L2 modify + MC tests and support
Added support to flow_match_test (and flow_match_test_port_pair)
to send generate flows/packets for multiple output port actions.
Added test cases for L2SrcModMC, L2DstModMC, L2SrcDstModMC which
modify and send to multiple output ports
Use egr_count to modify the number of output ports via test params.
Added warning to comments that test params need to be proper
Python identifiers.
diff --git a/tests/oft b/tests/oft
index bbbb0fa..48816a1 100755
--- a/tests/oft
+++ b/tests/oft
@@ -221,8 +221,8 @@
parser.add_option("-p", "--port", dest="controller_port",
type="int", help="Port number of the test controller")
test_list_help = """Indicate tests to run. Valid entries are "all" (the
- default) or a comma separated list of:
- module Run all tests in the named module
+ default) or a comma separated list of:\n
+ module Run all tests in the named module\n
testcase Run tests in all modules with the name testcase
module.testcase Run the specific test case
"""
@@ -246,7 +246,9 @@
parser.add_option("--param", type="int",
help="Parameter sent to test (for debugging)")
parser.add_option("-t", "--test-params",
- help="Set test parameters: key=val;... See --list")
+ help="""Set test parameters: key=val;...
+ NOTE: key MUST be a valid Python identifier, egr_count not egr-count
+ See --list""")
# Might need this if other parsers want command line
# parser.allow_interspersed_args = False
(options, args) = parser.parse_args()
diff --git a/tests/pktact.py b/tests/pktact.py
index 1861c89..86fc44e 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -955,6 +955,62 @@
flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
action_list=acts, max_test=2)
+class ModifyL2DstMC(BaseMatchCase):
+ """
+ Modify the L2 dest and send to 2 ports
+
+ Uses egr_count test parameter; defaults to 2
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+ skip_message_emit(self, "ModifyL2dst test")
+ return
+
+ egr_count = test_param_get(self.config, 'egr_count', default=2)
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=egr_count)
+
+class ModifyL2SrcMC(BaseMatchCase):
+ """
+ Modify the source MAC address (TP1) and send to multiple
+
+ Uses egr_count test parameter; defaults to 2
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_SRC):
+ skip_message_emit(self, "ModifyL2Src test")
+ return
+
+ egr_count = test_param_get(self.config, 'egr_count', default=2)
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_src'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=egr_count)
+
+class ModifyL2SrcDstMC(BaseMatchCase):
+ """
+ Modify the L2 source and dest and send to 2 ports
+
+ Uses egr_count test parameter; defaults to 2
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+ skip_message_emit(self, "ModifyL2dst test")
+ return
+
+ egr_count = test_param_get(self.config, 'egr_count', default=2)
+ mod_fields = ['dl_dst', 'dl_src']
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=mod_fields,
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=egr_count)
+
+
#@todo Need to implement tagged versions of the above tests
#
#@todo Implement a test case that strips tag 2, adds tag 3
diff --git a/tests/testutils.py b/tests/testutils.py
index 887a1b2..4c7420c 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -246,9 +246,10 @@
"Unexpected pkt on port " + str(ofport))
-def receive_pkt_verify(parent, egr_port, exp_pkt):
+def receive_pkt_verify(parent, egr_ports, exp_pkt):
"""
Receive a packet and verify it matches an expected value
+ @param egr_port A single port or list of ports
parent must implement dataplane, assertTrue and assertEqual
"""
@@ -256,27 +257,33 @@
if parent.config["relax"]:
exp_pkt_arg = exp_pkt
- (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(port_number=egr_port,
- timeout=1,
- exp_pkt=exp_pkt_arg)
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
- if rcv_pkt is None:
- parent.logger.error("ERROR: No packet received from " + str(egr_port))
+ # Expect a packet from each port on egr port list
+ for egr_port in egr_port_list:
+ (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
+ port_number=egr_port, timeout=1, exp_pkt=exp_pkt_arg)
- 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 rcv_pkt is None:
+ parent.logger.error("ERROR: No packet received from " + str(egr_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))
-
+ 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
@@ -377,7 +384,7 @@
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, in_band=False):
+ egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
"""
Create a flow message
@@ -385,6 +392,7 @@
See flow_match_test for other parameter descriptoins
@param egr_queue if not None, make the output an enqueue action
@param in_band if True, do not wildcard ingress port
+ @param egr_ports None (drop), single port or list of ports
"""
match = parse.packet_to_flow_match(pkt)
parent.assertTrue(match is not None, "Flow match from pkt failed")
@@ -393,6 +401,11 @@
match.wildcards = wildcards
match.in_port = ing_port
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
+
request = message.flow_mod()
request.match = match
request.buffer_id = 0xffffffff
@@ -408,18 +421,21 @@
# Set up output/enqueue action if directed
if egr_queue is not None:
- parent.assertTrue(egr_port is not None, "Egress port not set")
+ parent.assertTrue(egr_ports 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))
+ for egr_port in egr_port_list:
+ 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_ports is not None:
+ for egr_port in egr_port_list:
+ 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())
@@ -444,42 +460,66 @@
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,
+def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
dl_vlan=-1, pkt=None, exp_pkt=None,
action_list=None, check_expire=False):
"""
Flow match test on single TCP packet
+ @param egr_ports A single port or list of ports
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.info("Pkt match test: " + str(ing_port) + " to " +
+ str(egr_ports))
parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
" expire: " + 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,
+ wildcards=wildcards, egr_ports=egr_ports,
action_list=action_list)
flow_msg_install(parent, request)
- parent.logger.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+ parent.logger.debug("Send packet: " + str(ing_port) + " to " +
+ str(egr_ports))
parent.dataplane.send(ing_port, str(pkt))
if exp_pkt is None:
exp_pkt = pkt
- receive_pkt_verify(parent, egr_port, exp_pkt)
+ receive_pkt_verify(parent, egr_ports, 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 get_egr_list(parent, of_ports, how_many, exclude_list=[]):
+ """
+ Generate a list of ports avoiding those in the exclude list
+ @param parent Supplies logger
+ @param of_ports List of OF port numbers
+ @param how_many Number of ports to be added to the list
+ @param exclude_list List of ports not to be used
+ @returns An empty list if unable to find enough ports
+ """
+
+ count = 0
+ egr_ports = []
+ for egr_idx in range(len(of_ports)):
+ if of_ports[egr_idx] not in exclude_list:
+ egr_ports.append(of_ports[egr_idx])
+ count += 1
+ if count >= how_many:
+ return egr_ports
+ parent.logger.debug("Could not generate enough egress ports for test")
+ return []
+
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):
+ max_test=0, egr_count=1):
"""
Run flow_match_test_port_pair on all port pairs
@@ -500,19 +540,20 @@
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,
- wildcards=wildcards, 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
+ egr_ports = get_egr_list(parent, of_ports, egr_count,
+ exclude_list=[ingress_port])
+ if len(egr_ports) == 0:
+ parent.assertTrue(0, "Failed to generate egress port list")
+
+ flow_match_test_port_pair(parent, ingress_port, egr_ports,
+ wildcards=wildcards, 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
def test_param_get(config, key, default=None):
"""
@@ -525,6 +566,9 @@
If the pair 'key=val' appeared in the string passed to --test-params
on the command line, return val (as interpreted by exec). Otherwise
return default value.
+
+ WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
+ eg egr_count, not egr-count.
"""
try:
exec config["test_params"]