Merge branch 'master' into kenc
Conflicts:
tests/cxn.py - manually resolved
diff --git a/src/python/oftest/__init__.py b/src/python/oftest/__init__.py
index 802dc75..c1cbb78 100644
--- a/src/python/oftest/__init__.py
+++ b/src/python/oftest/__init__.py
@@ -1 +1,5 @@
'''Docstring to silence pylint; ignores --ignore option for __init__.py'''
+
+# Global config dictionary
+# Populated by oft.
+config = {}
diff --git a/src/python/oftest/base_tests.py b/src/python/oftest/base_tests.py
new file mode 100644
index 0000000..3664d9f
--- /dev/null
+++ b/src/python/oftest/base_tests.py
@@ -0,0 +1,142 @@
+"""
+Base classes for test cases
+
+Tests will usually inherit from one of these classes to have the controller
+and/or dataplane automatically set up.
+"""
+
+import logging
+import unittest
+
+from oftest import config
+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
+
+from oftest.testutils import *
+
+class SimpleProtocol(unittest.TestCase):
+ """
+ Root class for setting up the controller
+ """
+
+ def setUp(self):
+ logging.info("** START TEST CASE " + str(self))
+ self.controller = controller.Controller(
+ host=config["controller_host"],
+ port=config["controller_port"])
+ # clean_shutdown should be set to False to force quit app
+ self.clean_shutdown = True
+ self.controller.start()
+ #@todo Add an option to wait for a pkt transaction to ensure version
+ # compatibilty?
+ self.controller.connect(timeout=20)
+
+ # By default, respond to echo requests
+ self.controller.keep_alive = True
+
+ if not self.controller.active:
+ raise Exception("Controller startup failed")
+ if self.controller.switch_addr is None:
+ raise Exception("Controller startup failed (no switch addr)")
+ logging.info("Connected " + str(self.controller.switch_addr))
+ request = message.features_request()
+ reply, pkt = self.controller.transact(request)
+ self.assertTrue(reply is not None,
+ "Did not complete features_request for handshake")
+ self.supported_actions = reply.actions
+ logging.info("Supported actions: " + hex(self.supported_actions))
+
+ def inheritSetup(self, parent):
+ """
+ Inherit the setup of a parent
+
+ This allows running at test from within another test. Do the
+ following:
+
+ sub_test = SomeTestClass() # Create an instance of the test class
+ sub_test.inheritSetup(self) # Inherit setup of parent
+ sub_test.runTest() # Run the test
+
+ Normally, only the parent's setUp and tearDown are called and
+ the state after the sub_test is run must be taken into account
+ by subsequent operations.
+ """
+ logging.info("** Setup " + str(self) + " inheriting from "
+ + str(parent))
+ self.controller = parent.controller
+ self.supported_actions = parent.supported_actions
+
+ def tearDown(self):
+ logging.info("** END TEST CASE " + str(self))
+ self.controller.shutdown()
+ #@todo Review if join should be done on clean_shutdown
+ if self.clean_shutdown:
+ self.controller.join()
+
+ def runTest(self):
+ # Just a simple sanity check as illustration
+ logging.info("Running simple proto test")
+ self.assertTrue(self.controller.switch_socket is not None,
+ str(self) + 'No connection to switch')
+
+ def assertTrue(self, cond, msg):
+ if not cond:
+ logging.error("** FAILED ASSERTION: " + msg)
+ unittest.TestCase.assertTrue(self, cond, msg)
+
+class SimpleDataPlane(SimpleProtocol):
+ """
+ Root class that sets up the controller and dataplane
+ """
+ def setUp(self):
+ SimpleProtocol.setUp(self)
+ self.dataplane = dataplane.DataPlane(config)
+ for of_port, ifname in config["port_map"].items():
+ self.dataplane.port_add(ifname, of_port)
+
+ def inheritSetup(self, parent):
+ """
+ Inherit the setup of a parent
+
+ See SimpleProtocol.inheritSetup
+ """
+ SimpleProtocol.inheritSetup(self, parent)
+ self.dataplane = parent.dataplane
+
+ def tearDown(self):
+ logging.info("Teardown for simple dataplane test")
+ SimpleProtocol.tearDown(self)
+ if hasattr(self, 'dataplane'):
+ self.dataplane.kill(join_threads=self.clean_shutdown)
+ logging.info("Teardown done")
+
+ def runTest(self):
+ self.assertTrue(self.controller.switch_socket is not None,
+ str(self) + 'No connection to switch')
+ # 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 setUp(self):
+ self.clean_shutdown = True
+ logging.info("** START DataPlaneOnly CASE " + str(self))
+ self.dataplane = dataplane.DataPlane(config)
+ for of_port, ifname in config["port_map"].items():
+ self.dataplane.port_add(ifname, of_port)
+
+ def tearDown(self):
+ logging.info("Teardown for simple dataplane test")
+ self.dataplane.kill(join_threads=self.clean_shutdown)
+ logging.info("Teardown done")
+
+ def runTest(self):
+ logging.info("DataPlaneOnly")
+ # self.dataplane.show()
+ # Would like an assert that checks the data plane
diff --git a/src/python/oftest/controller.py b/src/python/oftest/controller.py
index 0769437..cfe2eda 100644
--- a/src/python/oftest/controller.py
+++ b/src/python/oftest/controller.py
@@ -104,7 +104,6 @@
self.socs = []
self.connect_cv = Condition()
self.message_cv = Condition()
- self.disconnect_cv = Condition()
# Counters
self.socket_errors = 0
@@ -290,17 +289,19 @@
sock.close()
return 0
- (sock, addr) = self.listen_socket.accept()
+ try:
+ (sock, addr) = self.listen_socket.accept()
+ except:
+ self.logger.warning("Error on listen socket accept")
+ return -1
self.socs.append(sock)
self.logger.info("Incoming connection from %s" % str(addr))
with self.connect_cv:
(self.switch_socket, self.switch_addr) = (sock, addr)
+ if self.initial_hello:
+ self.message_send(hello())
self.connect_cv.notify() # Notify anyone waiting
-
- if self.initial_hello:
- self.message_send(hello())
- ## @fixme Check return code
elif s and s == self.switch_socket:
for idx in range(3): # debug: try a couple of times
try:
@@ -403,8 +404,8 @@
self.switch_socket.close()
self.switch_socket = None
self.switch_addr = None
- with self.disconnect_cv:
- self.disconnect_cv.notifyAll()
+ with self.connect_cv:
+ self.connect_cv.notifyAll()
def wait_disconnected(self, timeout=-1):
"""
@@ -412,8 +413,8 @@
@return Boolean, True if disconnected
"""
- with self.disconnect_cv:
- timed_wait(self.disconnect_cv,
+ with self.connect_cv:
+ timed_wait(self.connect_cv,
lambda: True if not self.switch_socket else None,
timeout=timeout)
return self.switch_socket is None
diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py
index aa434a7..8d9b1a2 100644
--- a/src/python/oftest/dataplane.py
+++ b/src/python/oftest/dataplane.py
@@ -299,12 +299,6 @@
", port %d, length mismatch %d != %d" %
(port_number, bytes, len(packet)))
- def _oldest_packet_find(self):
- # Find port with oldest packet
- oft_assert(min_port != -1, "Could not find port when pkts pending")
-
- return min_port
-
# Returns the port with the oldest packet, or None if no packets are queued.
def oldest_port(self):
min_port = None
diff --git a/src/python/oftest/netutils.py b/src/python/oftest/netutils.py
index 613ac66..c7c8051 100644
--- a/src/python/oftest/netutils.py
+++ b/src/python/oftest/netutils.py
@@ -36,6 +36,7 @@
# From netpacket/packet.h
PACKET_ADD_MEMBERSHIP = 1
+PACKET_DROP_MEMBERSHIP = 2
PACKET_MR_PROMISC = 1
# From bits/socket.h
@@ -47,13 +48,6 @@
s.close()
return ifreq
-def get_if_hwaddr(iff):
- addrfamily, mac = struct.unpack("16xh6s8x",get_if(iff,SIOCGIFHWADDR))
- if addrfamily in [ARPHDR_ETHER,ARPHDR_LOOPBACK]:
- return str2mac(mac)
- else:
- raise Exception("Unsupported address family (%i)"%addrfamily)
-
def get_if_index(iff):
return int(struct.unpack("I",get_if(iff, SIOCGIFINDEX)[16:20])[0])
diff --git a/src/python/oftest/parse.py b/src/python/oftest/parse.py
index 11d6983..8826c0c 100644
--- a/src/python/oftest/parse.py
+++ b/src/python/oftest/parse.py
@@ -201,7 +201,7 @@
@param mac_str The string to convert
@return Array of 6 integer values
"""
- return map(lambda val:eval("0x" + val), mac_str.split(":"))
+ return map(lambda val: int(val, 16), mac_str.split(":"))
def parse_ip(ip_str):
"""
@@ -212,7 +212,7 @@
@param ip_str The string to convert
@return Integer value
"""
- array = map(lambda val:eval(val),ip_str.split("."))
+ array = map(lambda val: int(val), ip_str.split("."))
val = 0
for a in array:
val <<= 8
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 0d2dd09..3be9c32 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -10,6 +10,7 @@
except:
sys.exit("Need to install scapy for packet parsing")
+from oftest import config
import oftest.controller as controller
import oftest.cstruct as ofp
import oftest.message as message
@@ -30,25 +31,13 @@
MINSIZE = 0
-def clear_switch(parent, port_list, logger):
- """
- Clear the switch configuration
-
- @param parent Object implementing controller and assert equal
- @param logger Logging object
- """
- for port in port_list:
- clear_port_config(parent, port, logger)
- delete_all_flows(parent.controller, logger)
-
-def delete_all_flows(ctrl, logger):
+def delete_all_flows(ctrl):
"""
Delete all flows on the switch
@param ctrl The controller object for the test
- @param logger Logging object
"""
- logger.info("Deleting all flows")
+ logging.info("Deleting all flows")
msg = message.flow_mod()
msg.match.wildcards = ofp.OFPFW_ALL
msg.out_port = ofp.OFPP_NONE
@@ -56,19 +45,8 @@
msg.buffer_id = 0xffffffff
return ctrl.message_send(msg)
-def clear_port_config(parent, port, logger):
- """
- Clear the port configuration (currently only no flood setting)
-
- @param parent Object implementing controller and assert equal
- @param logger Logging object
- """
- rv = port_config_set(parent.controller, port,
- 0, ofp.OFPPC_NO_FLOOD, logger)
- self.assertEqual(rv, 0, "Failed to reset port config")
-
def required_wildcards(parent):
- w = test_param_get(parent.config, 'required_wildcards', default='default')
+ w = test_param_get('required_wildcards', default='default')
if w == 'l3-l4':
return (ofp.OFPFW_NW_SRC_ALL | ofp.OFPFW_NW_DST_ALL | ofp.OFPFW_NW_TOS
| ofp.OFPFW_NW_PROTO | ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST)
@@ -203,7 +181,7 @@
return -1
return 0
-def port_config_get(controller, port_no, logger):
+def port_config_get(controller, port_no):
"""
Get a port's configuration
@@ -215,31 +193,31 @@
"""
request = message.features_request()
reply, pkt = controller.transact(request)
- logger.debug(reply.show())
+ logging.debug(reply.show())
if reply is None:
- logger.warn("Get feature request failed")
+ logging.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")
+ logging.warn("Did not find port number for port config")
return None, None, None
-def port_config_set(controller, port_no, config, mask, logger):
+def port_config_set(controller, port_no, config, mask):
"""
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))
+ logging.info("Setting port " + str(port_no) + " to config " + str(config))
request = message.features_request()
reply, pkt = controller.transact(request)
if reply is None:
return -1
- logger.debug(reply.show())
+ logging.debug(reply.show())
for idx in range(len(reply.ports)):
if reply.ports[idx].port_no == port_no:
break
@@ -254,8 +232,7 @@
rv = controller.message_send(mod)
return rv
-def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if, logger,
- config):
+def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if):
"""
Check for proper receive packets across all ports
@param dp The dataplane object
@@ -265,25 +242,25 @@
@param assert_if Object that implements assertXXX
"""
exp_pkt_arg = None
- if config and config["relax"]:
+ if config["relax"]:
exp_pkt_arg = pkt
for ofport in yes_ports:
- logger.debug("Checking for pkt on port " + str(ofport))
+ logging.debug("Checking for pkt on port " + str(ofport))
(rcv_port, rcv_pkt, pkt_time) = dp.poll(
port_number=ofport, exp_pkt=exp_pkt_arg)
assert_if.assertTrue(rcv_pkt is not None,
"Did not receive pkt on " + str(ofport))
if not dataplane.match_exp_pkt(pkt, rcv_pkt):
- logger.debug("Sent %s" % format_packet(pkt))
- logger.debug("Resp %s" % format_packet(rcv_pkt))
+ logging.debug("Sent %s" % format_packet(pkt))
+ logging.debug("Resp %s" % format_packet(rcv_pkt))
assert_if.assertTrue(dataplane.match_exp_pkt(pkt, rcv_pkt),
"Response packet does not match send packet " +
"on port " + str(ofport))
if len(no_ports) > 0:
time.sleep(1)
for ofport in no_ports:
- logger.debug("Negative check for pkt on port " + str(ofport))
+ logging.debug("Negative check for pkt on port " + str(ofport))
(rcv_port, rcv_pkt, pkt_time) = dp.poll(
port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
assert_if.assertTrue(rcv_pkt is None,
@@ -298,7 +275,7 @@
parent must implement dataplane, assertTrue and assertEqual
"""
exp_pkt_arg = None
- if parent.config["relax"]:
+ if config["relax"]:
exp_pkt_arg = exp_pkt
if type(egr_ports) == type([]):
@@ -315,19 +292,19 @@
port_number=check_port, exp_pkt=exp_pkt_arg)
if rcv_pkt is None:
- parent.logger.error("ERROR: No packet received from " +
+ logging.error("ERROR: No packet received from " +
str(check_port))
parent.assertTrue(rcv_pkt is not None,
"Did not receive packet port " + str(check_port))
- parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
+ logging.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)) + ": "
+ logging.error("ERROR: Packet match failed.")
+ logging.debug("Expected len " + str(len(exp_pkt)) + ": "
+ str(exp_pkt).encode('hex'))
- parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
+ logging.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(check_port))
@@ -390,47 +367,6 @@
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 packet_to_flow_match(parent, packet):
match = parse.packet_to_flow_match(packet)
match.wildcards |= required_wildcards(parent)
@@ -470,7 +406,7 @@
if action_list is not None:
for act in action_list:
- parent.logger.debug("Adding action " + act.show())
+ logging.debug("Adding action " + act.show())
rv = request.actions.add(act)
parent.assertTrue(rv, "Could not add action" + act.show())
@@ -492,7 +428,7 @@
parent.assertTrue(rv, "Could not add output action " +
str(egr_port))
- parent.logger.debug(request.show())
+ logging.debug(request.show())
return request
@@ -505,24 +441,24 @@
@param clear_table If true, clear the flow table before installing
"""
- clear_table = test_param_get(parent.config, 'clear_table', default=True)
+ clear_table = test_param_get('clear_table', default=True)
if(clear_table_override != None):
clear_table = clear_table_override
if clear_table:
- parent.logger.debug("Clear flow table")
- rc = delete_all_flows(parent.controller, parent.logger)
+ logging.debug("Clear flow table")
+ rc = delete_all_flows(parent.controller)
parent.assertEqual(rc, 0, "Failed to delete all flows")
parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
- parent.logger.debug("Insert flow")
+ logging.debug("Insert flow")
rv = parent.controller.message_send(request)
parent.assertTrue(rv != -1, "Error installing flow mod")
parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=None,
dl_vlan=-1, pkt=None, exp_pkt=None,
- action_list=None, check_expire=False):
+ action_list=None):
"""
Flow match test on single TCP packet
@param egr_ports A single port or list of ports
@@ -533,10 +469,9 @@
if wildcards is None:
wildcards = required_wildcards(parent)
- parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
+ logging.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))
+ logging.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan))
if pkt is None:
pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
@@ -546,7 +481,7 @@
flow_msg_install(parent, request)
- parent.logger.debug("Send packet: " + str(ing_port) + " to " +
+ logging.debug("Send packet: " + str(ing_port) + " to " +
str(egr_ports))
parent.dataplane.send(ing_port, str(pkt))
@@ -554,14 +489,10 @@
exp_pkt = pkt
receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
- 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 parent Supplies logging
@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
@@ -579,24 +510,23 @@
count += 1
if count >= how_many:
return egr_ports
- parent.logger.debug("Could not generate enough egress ports for test")
+ logging.debug("Could not generate enough egress ports for test")
return []
def flow_match_test(parent, port_map, wildcards=None, dl_vlan=-1, pkt=None,
- exp_pkt=None, action_list=None, check_expire=False,
+ exp_pkt=None, action_list=None,
max_test=0, egr_count=1, ing_port=False):
"""
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
+ and logging
@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 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
@param egr_count Number of egress ports; -1 means get from config w/ dflt 2
"""
if wildcards is None:
@@ -607,7 +537,7 @@
test_count = 0
if egr_count == -1:
- egr_count = test_param_get(parent.config, 'egr_count', default=2)
+ egr_count = test_param_get('egr_count', default=2)
for ing_idx in range(len(of_ports)):
ingress_port = of_ports[ing_idx]
@@ -621,18 +551,16 @@
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)
+ action_list=action_list)
test_count += 1
if (max_test > 0) and (test_count > max_test):
- parent.logger.info("Ran " + str(test_count) + " tests; exiting")
+ logging.info("Ran " + str(test_count) + " tests; exiting")
return
-def test_param_get(config, key, default=None):
+def test_param_get(key, default=None):
"""
Return value passed via test-params if present
- @param config The configuration structure for OFTest
@param key The lookup key
@param default Default value to use if not found
@@ -707,11 +635,11 @@
return act
def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
- mod_fields={}, check_test_params=False):
+ mod_fields=[], check_test_params=False):
"""
Set up the ingress and expected packet and action list for a test
- @param parent Must implement, assertTrue, config hash and logger
+ @param parent Must implement assertTrue
@param start_field_values Field values to use for ingress packet (optional)
@param mod_field_values Field values to use for modified packet (optional)
@param mod_fields The list of fields to be modified by the switch in the test.
@@ -756,9 +684,9 @@
# Check for test param modifications
strip = False
if check_test_params:
- add_vlan = test_param_get(parent.config, 'add_vlan')
- strip_vlan = test_param_get(parent.config, 'strip_vlan')
- vid = test_param_get(parent.config, 'vid')
+ add_vlan = test_param_get('add_vlan')
+ strip_vlan = test_param_get('strip_vlan')
+ vid = test_param_get('vid')
if add_vlan and strip_vlan:
parent.assertTrue(0, "Add and strip VLAN both specified")
@@ -816,13 +744,13 @@
Print out a 'skipped' message to stderr
@param s The string to print out to the log file
- @param parent Must implement config and logger objects
+ @param parent Must implement config object
"""
global skipped_test_count
skipped_test_count += 1
- parent.logger.info("Skipping: " + s)
- if parent.config["dbg_level"] < logging.WARNING:
+ logging.info("Skipping: " + s)
+ if config["dbg_level"] < logging.WARNING:
sys.stderr.write("(skipped) ")
else:
sys.stderr.write("(S)")