Merge pull request #20 from InCNTRE/master
Add conformance tests "actions"
diff --git a/STYLE b/STYLE
new file mode 100644
index 0000000..358805a
--- /dev/null
+++ b/STYLE
@@ -0,0 +1,10 @@
+OFTest Coding Style
+-------------------
+
+See the Python standard style guide, PEP-8: http://www.python.org/dev/peps/pep-0008/.
+
+Some important style points:
+ - Test file names should be lowercase with underscores and short. Test case
+ class names are CamelCased.
+ - Any additional documentation added to the repository should be in plain text
+ or Markdown.
diff --git a/platforms/local.py b/platforms/local.py
new file mode 100644
index 0000000..77e15d9
--- /dev/null
+++ b/platforms/local.py
@@ -0,0 +1,22 @@
+"""
+Local platform
+
+This platform uses veth pairs to send packets to and from a userspace
+switch. The switch should be connected to veth0, veth2, veth4, and veth6.
+"""
+
+def platform_config_update(config):
+ """
+ Update configuration for the local platform
+
+ @param config The configuration dictionary to use/update
+ """
+ base_of_port = 1
+ base_if_index = 1
+ port_count = 4
+
+ port_map = {}
+ # Use every other veth interface (veth1, veth3, ...)
+ for idx in range(port_count):
+ port_map[base_of_port + idx] = "veth%d" % (base_if_index + 2 * idx)
+ config['port_map'] = port_map
diff --git a/tests/remote.py b/platforms/remote.py
similarity index 75%
rename from tests/remote.py
rename to platforms/remote.py
index 5931153..3666bb4 100644
--- a/tests/remote.py
+++ b/platforms/remote.py
@@ -1,23 +1,23 @@
"""
-Platform configuration file
-platform == remote
+Remote platform
+
+This platform uses physical ethernet interfaces.
"""
+# Update this dictionary to suit your environment.
remote_port_map = {
23 : "eth2",
24 : "eth3",
25 : "eth4",
26 : "eth5"
- }
+}
def platform_config_update(config):
"""
Update configuration for the remote platform
@param config The configuration dictionary to use/update
- This routine defines the port map used for this configuration
"""
-
global remote_port_map
config["port_map"] = remote_port_map.copy()
config["caps_table_idx"] = 0
diff --git a/profiles/default.py b/profiles/default.py
new file mode 100644
index 0000000..8313eb7
--- /dev/null
+++ b/profiles/default.py
@@ -0,0 +1,9 @@
+"""
+Default profile
+
+No tests skipped.
+"""
+
+#@var skip_test_list The list of tests to skip for this run
+skip_test_list = [
+]
diff --git a/tests/profiles/example.py b/profiles/example.py
similarity index 89%
rename from tests/profiles/example.py
rename to profiles/example.py
index 938a6f2..6e23b92 100644
--- a/tests/profiles/example.py
+++ b/profiles/example.py
@@ -1,7 +1,7 @@
"""
Sample profile
-A profile determines run specific behavior. It is meant to capture
+A profile determines run specific behavior. It is meant to capture
variations between different switch targets.
A profile defines two target specific variables.
@@ -13,9 +13,6 @@
2. A set of tests to run (overriding the default "skip" priority)
optionally specifying a test parameters specific to the test run
-This file should be imported "as profile" so references to the
-module will properly map.
-
@todo Allow a test to be run multiple times with different params
"""
diff --git a/tests/profiles/noing.py b/profiles/noing.py
similarity index 100%
rename from tests/profiles/noing.py
rename to profiles/noing.py
diff --git a/src/python/oftest/controller.py b/src/python/oftest/controller.py
index cf4cf69..cc8b97c 100644
--- a/src/python/oftest/controller.py
+++ b/src/python/oftest/controller.py
@@ -286,6 +286,8 @@
if s and s == self.listen_socket:
if self.switch_socket:
self.logger.warning("Ignoring incoming connection; already connected to switch")
+ (sock, addr) = self.listen_socket.accept()
+ sock.close()
return 0
(sock, addr) = self.listen_socket.accept()
diff --git a/src/python/oftest/illegal_message.py b/src/python/oftest/illegal_message.py
new file mode 100644
index 0000000..0c6a878
--- /dev/null
+++ b/src/python/oftest/illegal_message.py
@@ -0,0 +1,108 @@
+"""
+Support an illegal message
+"""
+
+from cstruct import *
+
+ILLEGAL_MESSAGE_TYPE=217
+
+class illegal_message_type:
+ """
+ Wrapper class for illegal message
+
+ OpenFlow message header: length, version, xid, type
+ @arg length: The total length of the message
+ @arg version: The OpenFlow version (1)
+ @arg xid: The transaction ID
+ @arg type: The message type (OFPT_ECHO_REQUEST=2)
+
+ @arg data: Binary string following message members
+
+ The message type is set to "illegal" and the pack assert
+ check for the OF header is disabled
+ """
+
+ def __init__(self):
+ self.header = ofp_header()
+ self.header.type = ILLEGAL_MESSAGE_TYPE
+ self.data = ""
+
+ def pack(self):
+ """
+ Pack object into string
+
+ @return The packed string which can go on the wire
+
+ """
+ self.header.length = len(self)
+ packed = self.header.pack(assertstruct=False)
+
+ packed += self.data
+ return packed
+
+ def unpack(self, binary_string):
+ """
+ Unpack object from a binary string
+
+ @param binary_string The wire protocol byte string holding the object
+ represented as an array of bytes.
+ @return The remainder of binary_string that was not parsed.
+
+ """
+ binary_string = self.header.unpack(binary_string)
+
+ self.data = binary_string
+ binary_string = ''
+ return binary_string
+
+ def __len__(self):
+ """
+ Return the length of this object once packed into a string
+
+ @return An integer representing the number bytes in the packed
+ string.
+
+ """
+ length = OFP_HEADER_BYTES
+
+ length += len(self.data)
+ return length
+
+ def show(self, prefix=''):
+ """
+ Generate a string (with multiple lines) describing the contents
+ of the object in a readable manner
+
+ @param prefix Pre-pended at the beginning of each line.
+
+ """
+
+ outstr = prefix + 'illegal_message (' + \
+ str(ILLEGAL_MESSAGE_TYPE) + ')\n'
+ prefix += ' '
+ outstr += prefix + 'ofp header\n'
+ outstr += self.header.show(prefix + ' ')
+ outstr += prefix + 'data is of length ' + str(len(self.data)) + '\n'
+ return outstr
+
+ def __eq__(self, other):
+ """
+ Return True if self and other hold the same data
+
+ @param other Other object in comparison
+
+ """
+ if type(self) != type(other): return False
+ if not self.header.__eq__(other.header): return False
+
+ if self.data != other.data: return False
+ return True
+
+ def __ne__(self, other):
+ """
+ Return True if self and other do not hold the same data
+
+ @param other Other object in comparison
+
+ """
+ return not self.__eq__(other)
diff --git a/tests/basic.py b/tests/basic.py
index feda04f..721f953 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -29,6 +29,8 @@
import oftest.dataplane as dataplane
import oftest.action as action
+import oftest.illegal_message as illegal_message
+
from testutils import *
#@var basic_port_map Local copy of the configuration map from OF port
@@ -90,13 +92,19 @@
#@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)")
basic_logger.info("Connected " + str(self.controller.switch_addr))
request = message.features_request()
- reply, pkt = self.controller.transact(request, timeout=10)
+ reply, pkt = self.controller.transact(request)
+ self.assertTrue(reply is not None,
+ "Did not complete features_request for handshake")
self.supported_actions = reply.actions
basic_logger.info("Supported actions: " + hex(self.supported_actions))
@@ -211,6 +219,8 @@
# self.dataplane.show()
# Would like an assert that checks the data plane
+test_prio["DataPlaneOnly"] = -1
+
class Echo(SimpleProtocol):
"""
Test echo response with no data
@@ -218,6 +228,8 @@
def runTest(self):
request = message.echo_request()
response, pkt = self.controller.transact(request)
+ self.assertTrue(response is not None,
+ "Did not get echo reply")
self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
'response is not echo_reply')
self.assertEqual(request.header.xid, response.header.xid,
@@ -232,6 +244,8 @@
request = message.echo_request()
request.data = 'OpenFlow Will Rule The World'
response, pkt = self.controller.transact(request)
+ self.assertTrue(response is not None,
+ "Did not get echo reply (with data)")
self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
'response is not echo_reply')
self.assertEqual(request.header.xid, response.header.xid,
@@ -270,7 +284,7 @@
#@todo Check for unexpected messages?
count = 0
while True:
- (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
+ (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN)
if not response: # Timeout
break
if dataplane.match_exp_pkt(pkt, response.data): # Got match
@@ -308,7 +322,7 @@
self.dataplane.send(of_port, str(pkt))
count = 0
while True:
- (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
+ (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN)
if not response: # Timeout
break
if dataplane.match_exp_pkt(pkt, response.data): # Got match
@@ -471,7 +485,8 @@
request.table_id = 0xff
request.match.wildcards = 0 # ofp.OFPFW_ALL
response, pkt = self.controller.transact(request)
- self.assertTrue(response is not None, "Did not get response")
+ self.assertTrue(response is not None,
+ "Did not get response for flow stats")
basic_logger.debug(response.show())
test_prio["FlowStatsGet"] = -1
@@ -492,7 +507,8 @@
basic_logger.info("Sending table stats request")
request = message.table_stats_request()
response, pkt = self.controller.transact(request)
- self.assertTrue(response is not None, "Did not get response")
+ self.assertTrue(response is not None,
+ "Did not get reply for table stats")
basic_logger.debug(response.show())
class DescStatsGet(SimpleProtocol):
@@ -507,7 +523,8 @@
basic_logger.info("Sending stats request")
request = message.desc_stats_request()
response, pkt = self.controller.transact(request)
- self.assertTrue(response is not None, "Did not get response")
+ self.assertTrue(response is not None,
+ "Did not get reply for desc stats")
basic_logger.debug(response.show())
class FlowMod(SimpleProtocol):
@@ -588,7 +605,7 @@
# poll for error message
while True:
- (response, raw) = self.controller.poll(ofp.OFPT_ERROR, 2)
+ (response, raw) = self.controller.poll(ofp.OFPT_ERROR)
if not response: # Timeout
break
if response.code == ofp.OFPPMFC_BAD_PORT:
@@ -602,5 +619,23 @@
self.assertTrue(response is not None, 'Did not receive error message')
+class BadMessage(SimpleProtocol):
+ """
+ Send a message with a bad type and verify an error is returned
+ """
+
+ def runTest(self):
+ basic_logger.info("Running " + str(self))
+ request = illegal_message.illegal_message_type()
+
+ reply, pkt = self.controller.transact(request)
+ self.assertTrue(reply is not None, "Did not get response to bad req")
+ self.assertTrue(reply.header.type == ofp.OFPT_ERROR,
+ "reply not an error message")
+ self.assertTrue(reply.type == ofp.OFPET_BAD_REQUEST,
+ "reply error type is not bad request")
+ self.assertTrue(reply.code == ofp.OFPBRC_BAD_TYPE,
+ "reply error code is not bad type")
+
if __name__ == "__main__":
print "Please run through oft script: ./oft --test_spec=basic"
diff --git a/tests/bsn_ipmask.py b/tests/bsn_ipmask.py
index 0609582..cb64cb5 100644
--- a/tests/bsn_ipmask.py
+++ b/tests/bsn_ipmask.py
@@ -85,7 +85,7 @@
m.data = struct.pack( "!LBBBBL", 1, index, 0, 0, 0, 0 )
rc = self.controller.message_send(m)
self.assertNotEqual(rc, -1, "Error sending get IP mask command")
- m, r = self.controller.poll(ofp.OFPT_VENDOR, 2)
+ m, r = self.controller.poll(ofp.OFPT_VENDOR)
self.assertEqual(m.vendor, 0x005c16c7, "Wrong vendor ID")
x = struct.unpack("!LBBBBL", m.data)
self.assertEqual(x[0], 2, "Wrong subtype")
diff --git a/tests/cxn.py b/tests/cxn.py
new file mode 100644
index 0000000..3f044fc
--- /dev/null
+++ b/tests/cxn.py
@@ -0,0 +1,165 @@
+"""
+Connection test cases
+
+"""
+
+import time
+import signal
+import sys
+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
+
+from testutils import *
+
+#@var cxn_port_map Local copy of the configuration map from OF port
+# numbers to OS interfaces
+cxn_port_map = None
+#@var cxn_logger Local logger object
+cxn_logger = None
+#@var cxn_config Local copy of global configuration data
+cxn_config = None
+
+test_prio = {}
+
+def test_set_init(config):
+ """
+ Set up function for connection test classes
+
+ @param config The configuration dictionary; see oft
+ """
+
+ global cxn_port_map
+ global cxn_logger
+ global cxn_config
+
+ cxn_logger = logging.getLogger("cxn")
+ cxn_logger.info("Initializing test set")
+ cxn_port_map = config["port_map"]
+ cxn_config = config
+
+class BaseHandshake(unittest.TestCase):
+ """
+ Base handshake case to set up controller, but do not send hello.
+ """
+
+ def sig_handler(self, v1, v2):
+ cxn_logger.critical("Received interrupt signal; exiting")
+ print "Received interrupt signal; exiting"
+ self.clean_shutdown = False
+ self.tearDown()
+ sys.exit(1)
+
+ def controllerSetup(self, host, port):
+ self.controller = controller.Controller(host=host,port=port)
+
+ # clean_shutdown should be set to False to force quit app
+ self.clean_shutdown = True
+ # disable initial hello so hello is under control of test
+ self.controller.initial_hello = False
+
+ self.controller.start()
+ #@todo Add an option to wait for a pkt transaction to ensure version
+ # compatibilty?
+ self.controller.connect(timeout=10)
+ self.assertTrue(self.controller.active,
+ "Controller startup failed, not active")
+ self.assertTrue(self.controller.switch_addr is not None,
+ "Controller startup failed, no switch addr")
+
+ def setUp(self):
+ self.logger = cxn_logger
+ self.config = cxn_config
+ #@todo Test cases shouldn't monkey with signals; move SIGINT handler
+ # to top-level oft
+ try:
+ signal.signal(signal.SIGINT, self.sig_handler)
+ except ValueError, e:
+ cxn_logger.info("Could not set SIGINT handler: %s" % e)
+ cxn_logger.info("** START TEST CASE " + str(self))
+
+ self.test_timeout = test_param_get(cxn_config,
+ 'handshake_timeout') or 60
+
+ 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.
+ """
+ self.logger = parent.logger
+ self.config = parent.config
+ cxn_logger.info("** Setup " + str(self) +
+ " inheriting from " + str(parent))
+ self.controller = parent.controller
+
+ def tearDown(self):
+ cxn_logger.info("** END TEST CASE " + str(self))
+ self.controller.shutdown()
+ if self.clean_shutdown:
+ self.controller.join()
+
+ def runTest(self):
+ # do nothing in the base case
+ pass
+
+ def assertTrue(self, cond, msg):
+ if not cond:
+ cxn_logger.error("** FAILED ASSERTION: " + msg)
+ unittest.TestCase.assertTrue(self, cond, msg)
+
+test_prio["BaseHandshake"] = -1
+
+class HandshakeNoHello(BaseHandshake):
+ def runTest(self):
+ self.controllerSetup(cxn_config["controller_host"],
+ cxn_config["controller_port"])
+
+ cxn_logger.info("TCP Connected " +
+ str(self.controller.switch_addr))
+ cxn_logger.info("Hello not sent, waiting for timeout")
+
+ # wait for controller to die
+ count = 0
+ while self.controller.active and count < self.test_timeout:
+ time.sleep(1)
+ count = count + 1
+ self.assertTrue(not self.controller.active,
+ "Expected controller disconnect, but still active")
+
+class HandshakeNoFeaturesRequest(BaseHandshake):
+ def runTest(self):
+ self.controllerSetup(cxn_config["controller_host"],
+ cxn_config["controller_port"])
+
+ cxn_logger.info("TCP Connected " +
+ str(self.controller.switch_addr))
+ cxn_logger.info("Sending hello")
+ self.controller.message_send(message.hello())
+
+ cxn_logger.info("Features request not sent, waiting for timeout")
+
+ # wait for controller to die
+ count = 0
+ while self.controller.active and count < self.test_timeout:
+ time.sleep(1)
+ count = count + 1
+ self.assertTrue(not self.controller.active,
+ "Expected controller disconnect, but still active")
+
diff --git a/tests/flow_query.py b/tests/flow_query.py
index bad4749..63e6cb5 100644
--- a/tests/flow_query.py
+++ b/tests/flow_query.py
@@ -1371,7 +1371,7 @@
# </TBD>
n = 0
while True:
- (resp, pkt) = self.controller.poll(ofp.OFPT_STATS_REPLY, 4)
+ (resp, pkt) = self.controller.poll(ofp.OFPT_STATS_REPLY)
if resp is None:
return False # Did not get expected response
if n == 0:
@@ -1429,7 +1429,7 @@
def barrier(self):
barrier = message.barrier_request()
- (resp, pkt) = self.controller.transact(barrier, 20)
+ (resp, pkt) = self.controller.transact(barrier, 30)
return (resp is not None)
def errors_verify(self, num_exp, type = 0, code = 0):
diff --git a/tests/local.py b/tests/local.py
deleted file mode 100644
index 0a3bc04..0000000
--- a/tests/local.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
-Platform configuration file
-platform == local
-
-Update this file to override defaults
-"""
-
-def platform_config_update(config):
- """
- Update configuration for the local platform
-
- @param config The configuration dictionary to use/update
-
- Update this routine if values other the defaults are required
- """
diff --git a/tests/oft b/tests/oft
index 81d378e..f59e99d 100755
--- a/tests/oft
+++ b/tests/oft
@@ -15,9 +15,6 @@
platform : String identifying the target platform
controller_host : Host on which test controller is running (for sockets)
controller_port : Port on which test controller listens for switch cxn
- port_count : (Optional) Number of ports in dataplane
- base_of_port : (Optional) Base OpenFlow port number in dataplane
- base_if_index : (Optional) Base OS network interface for dataplane
test_dir : (TBD) Directory to search for test files (default .)
test_spec : (TBD) Specification of test(s) to run
log_file : Filename for test logging
@@ -70,15 +67,10 @@
current run. In particular, it should set up config["port_map"] with
the proper map from OF port numbers to OF interface names.
-You can add your own platform, say gp104, by adding a file gp104.py
-that defines the function platform_config_update and then use the
-parameter --platform=gp104 on the command line.
-
-If platform_config_update does not set config["port_map"], an attempt
-is made to generate a default map via the function default_port_map_setup.
-This will use "local" and "remote" for platform names if available
-and generate a sequential map based on the values of base_of_port and
-base_if_index in the configuration structure.
+You can add your own platform, say gp104, by adding a file gp104.py to the
+platforms directory that defines the function platform_config_update and then
+use the parameter --platform=gp104 on the command line. You can also use the
+--platform-dir option to change which directory is searched.
The current model for test sets is basic.py. The current convention is
that the test set should implement a function test_set_init which takes
@@ -105,9 +97,8 @@
@todo Consider moving oft up a level
Current test case setup:
- Files in this or sub directories (or later, directory specified on
-command line) that contain a function test_set_init are considered test
-files.
+ Files in the tests direcoty that contain a function test_set_init are
+considered test files.
The function test_set_init examines the test_spec config variable
and generates a suite of tests.
Support a command line option --test_mod so that all tests in that
@@ -160,6 +151,8 @@
_debug_default = "warning"
_debug_level_default = DEBUG_LEVELS[_debug_default]
+root_dir = os.path.join(os.path.dirname(__file__), "..")
+
##@var config_default
# The default configuration dictionary for OFT
config_default = {
@@ -168,9 +161,6 @@
"platform_args" : None,
"controller_host" : "0.0.0.0",
"controller_port" : 6633,
- "port_count" : 4,
- "base_of_port" : 1,
- "base_if_index" : 1,
"relax" : False,
"test_spec" : "all",
"test_dir" : os.path.dirname(__file__),
@@ -181,12 +171,14 @@
"dbg_level" : _debug_level_default,
"port_map" : {},
"test_params" : "None",
- "profile" : None,
+ "profile" : "default",
"allow_user" : False,
"fail_skipped" : False,
"default_timeout" : 2,
"minsize" : 0,
"random_seed" : None,
+ "platform_dir" : os.path.join(root_dir, "platforms"),
+ "profile_dir" : os.path.join(root_dir, "profiles"),
}
# Default test priority
@@ -286,6 +278,12 @@
parser.add_option("--random-seed", type="int",
help="Random number generator seed",
default=None)
+ parser.add_option("--test-dir", type="string",
+ help="Directory containing tests")
+ parser.add_option("--platform-dir", type="string",
+ help="Directory containing platform modules")
+ parser.add_option("--profile-dir", type="string",
+ help="Directory containing profile modules")
# Might need this if other parsers want command line
# parser.allow_interspersed_args = False
@@ -295,28 +293,21 @@
return (config, args)
-def check_profile(config):
+def load_profile(config):
"""
Import a profile from the profiles library
"""
global profile_mod
- if "profile" in config and config["profile"]:
- logging.info("Importing profile: %s" % config["profile"])
- profile_name = "profiles." + config["profile"]
- try:
- top_mod = __import__(profile_name)
- profile_mod = eval("top_mod." + config["profile"])
- logging.info("Imported profile %s. Dir: %s" %
- (config["profile"], str(dir(profile_mod))))
- except:
- logging.info("Could not import profile: %s.py" %
- config["profile"])
- print "Failed to import profile: %s" % config["profile"]
- raise
- else:
- logging.info("No profile specified")
-
+ logging.info("Importing profile: %s" % config["profile"])
+ try:
+ profile_mod = imp.load_module(config["profile"], *imp.find_module(config["profile"], [config["profile_dir"]]))
+ if not "skip_test_list" in dir(profile_mod):
+ die("Profile did not define skip_test_list")
+ except:
+ logging.info("Could not import profile: %s.py" % config["profile"])
+ print "Failed to import profile: %s" % config["profile"]
+ raise
def logging_setup(config):
"""
@@ -328,31 +319,6 @@
level=config["dbg_level"],
format=_format, datefmt=_datefmt)
-def default_port_map_setup(config):
- """
- Setup the OF port mapping based on config
- @param config The OFT configuration structure
- @return Port map dictionary
- """
- if (config["base_of_port"] is None) or not config["port_count"]:
- return None
- port_map = {}
- if config["platform"] == "local":
- # For local, use every other veth port
- for idx in range(config["port_count"]):
- port_map[config["base_of_port"] + idx] = "veth" + \
- str(config["base_if_index"] + (2 * idx))
- elif config["platform"] == "remote":
- # For remote, use eth ports
- for idx in range(config["port_count"]):
- port_map[config["base_of_port"] + idx] = "eth" + \
- str(config["base_if_index"] + idx)
- else:
- return None
-
- logging.info("Built default port map")
- return port_map
-
def test_list_generate(config):
"""Generate the list of all known tests indexed by module name
@@ -427,10 +393,9 @@
If set in the test_prio variable for the module, return
that value. Otherwise return 100 (default)
"""
- if profile_mod:
- if profile_mod.skip_test_list and test in profile_mod.skip_test_list:
- logging.info("Skipping test %s due to profile" % test)
- return TEST_PRIO_SKIP
+ if test in profile_mod.skip_test_list:
+ logging.info("Skipping test %s due to profile" % test)
+ return TEST_PRIO_SKIP
if 'test_prio' in dir(mod):
if test in mod.test_prio.keys():
return mod.test_prio[test]
@@ -443,9 +408,14 @@
# Get configuration, set up logging, import platform from file
(config, args) = config_setup(config_default)
+logging_setup(config)
+logging.info("++++++++ " + time.asctime() + " ++++++++")
+
test_list_generate(config)
oft_config = config
+load_profile(config)
+
# Check if test list is requested; display and exit if so
if config["list"]:
did_print = False
@@ -501,11 +471,6 @@
print "%s.%s" % (mod.__name__, test)
sys.exit(0)
-logging_setup(config)
-logging.info("++++++++ " + time.asctime() + " ++++++++")
-
-check_profile(config)
-
# Generate the test suite
#@todo Decide if multiple suites are ever needed
suite = unittest.TestSuite()
@@ -549,28 +514,24 @@
else:
die("Bad test spec: " + ts_entry)
-# Check if platform specified
-if config["platform"]:
- _imp_string = "from " + config["platform"] + " import *"
- logging.info("Importing platform: " + _imp_string)
- try:
- exec(_imp_string)
- except:
- logging.warn("Failed to import " + config["platform"] + " file")
- raise
+# Load the platform module
+platform_name = config["platform"]
+logging.info("Importing platform: " + platform_name)
+platform_mod = None
+try:
+ platform_mod = imp.load_module(platform_name, *imp.find_module(platform_name, [config["platform_dir"], config["test_dir"]]))
+except:
+ logging.warn("Failed to import " + platform_name + " platform module")
+ raise
try:
- platform_config_update(config)
+ platform_mod.platform_config_update(config)
except:
logging.warn("Could not run platform host configuration")
raise
if not config["port_map"]:
- # Try to set up default port mapping if not done by platform
- config["port_map"] = default_port_map_setup(config)
-
-if not config["port_map"]:
- die("Interface port map is not defined. Exiting")
+ die("Interface port map was not defined by the platform. Exiting.")
logging.debug("Configuration: " + str(config))
logging.info("OF port map: " + str(config["port_map"]))
diff --git a/tests/pktact.py b/tests/pktact.py
index 14f9e8f..660aa07 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -458,7 +458,7 @@
exp_port = None
while True:
- (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
+ (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN)
if not response: # Timeout
break
if dataplane.match_exp_pkt(pkt, response.data): # Got match
@@ -1888,6 +1888,88 @@
"""
test_prio["MixedVLAN"] = -1
-
+
+class MatchEach(basic.SimpleDataPlane):
+ """
+ Check that each match field is actually matched on.
+ Installs two flows that differ in one field. The flow that should not
+ match has a higher priority, so if that field is ignored during matching
+ the packet will be sent out the wrong port.
+
+ TODO test UDP, ARP, ICMP, etc.
+ """
+ def runTest(self):
+ of_ports = pa_port_map.keys()
+ of_ports.sort()
+ self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+ delete_all_flows(self.controller, pa_logger)
+
+ pkt = simple_tcp_packet()
+ ingress_port = of_ports[0]
+ egress_port = of_ports[1]
+
+ def testField(field, mask):
+ pa_logger.info("Testing field %s" % field)
+
+ def addFlow(matching, priority, output_port):
+ match = packet_to_flow_match(self, pkt)
+ self.assertTrue(match is not None, "Could not generate flow match from pkt")
+ match.wildcards &= ~ofp.OFPFW_IN_PORT
+ match.in_port = ingress_port
+ if not matching:
+ # Make sure flow doesn't match
+ orig = getattr(match, field)
+ if isinstance(orig, list):
+ new = map(lambda a: ~a[0] & a[1], zip(orig, mask))
+ else:
+ new = ~orig & mask
+ setattr(match, field, new)
+ request = message.flow_mod()
+ request.match = match
+ request.buffer_id = 0xffffffff
+ request.priority = priority
+ act = action.action_output()
+ act.port = output_port
+ self.assertTrue(request.actions.add(act), "Could not add action")
+ pa_logger.info("Inserting flow")
+ self.controller.message_send(request)
+
+ # This flow should match.
+ addFlow(matching=True, priority=0, output_port=egress_port)
+ # This flow should not match, but it has a higher priority.
+ addFlow(matching=False, priority=1, output_port=ofp.OFPP_IN_PORT)
+
+ self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+ pa_logger.info("Sending packet to dp port " + str(ingress_port))
+ self.dataplane.send(ingress_port, str(pkt))
+
+ exp_pkt_arg = None
+ exp_port = None
+ if pa_config["relax"]:
+ exp_pkt_arg = pkt
+ exp_port = egress_port
+
+ (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(port_number=exp_port,
+ exp_pkt=exp_pkt_arg)
+ self.assertTrue(rcv_pkt is not None, "Did not receive packet")
+ pa_logger.debug("Packet len " + str(len(rcv_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')
+
+ # TODO in_port
+ testField("dl_src", [0xff]*6)
+ testField("dl_dst", [0xff]*6)
+ testField("dl_type", 0xffff)
+ testField("dl_vlan", 0xfff)
+ # TODO dl_vlan_pcp
+ testField("nw_src", 0xffffffff)
+ testField("nw_dst", 0xffffffff)
+ testField("nw_tos", 0x3f)
+ testField("nw_proto", 0xff)
+ testField("tp_src", 0xffff)
+ testField("tp_dst", 0xffff)
+
if __name__ == "__main__":
print "Please run through oft script: ./oft --test_spec=basic"
diff --git a/tests/profiles/__init__.py b/tests/profiles/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/profiles/__init__.py
+++ /dev/null
diff --git a/tests/serial_failover.py b/tests/serial_failover.py
index 90a4236..d12040e 100644
--- a/tests/serial_failover.py
+++ b/tests/serial_failover.py
@@ -80,11 +80,15 @@
self.controller.start()
#@todo Add an option to wait for a pkt transaction to ensure version
# compatibilty?
- self.controller.connect()
+ self.controller.connect(timeout=10)
self.assertTrue(self.controller.active,
"Controller startup failed, not active")
self.assertTrue(self.controller.switch_addr is not None,
"Controller startup failed, no switch addr")
+ request = message.features_request()
+ reply, pkt = self.controller.transact(request, timeout=20)
+ self.assertTrue(reply is not None,
+ "Did not complete features_request for handshake")
serial_failover_logger.info("Connected " +
str(self.controller.switch_addr))
@@ -117,7 +121,7 @@
# controller_list is list of ip/port tuples
partial_list = test_param_get(serial_failover_config,
'controller_list')
- serial_failover_logger.debug(str(partial_list))
+ serial_failover_logger.debug("ctrl list: " + str(partial_list))
self.controller_list = [(serial_failover_config["controller_host"],
serial_failover_config["controller_port"])]
if partial_list is not None: