blob: 5e9785728ca35004ef836f339d55bc3959df2820 [file] [log] [blame]
Dan Talayco48370102010-03-03 15:17:33 -08001#!/usr/bin/env python
2"""
3@package oft
4
5OpenFlow test framework top level script
6
7This script is the entry point for running OpenFlow tests
8using the OFT framework.
9
10The global configuration is passed around in a dictionary
Brandon Heller88f709d2010-04-01 12:29:56 -070011generally called config. The keys have the following
Dan Talayco48370102010-03-03 15:17:33 -080012significance.
13
Dan Talayco2c0dba32010-03-06 22:47:06 -080014<pre>
Dan Talayco48370102010-03-03 15:17:33 -080015 platform : String identifying the target platform
16 controller_host : Host on which test controller is running (for sockets)
17 controller_port : Port on which test controller listens for switch cxn
Dan Talayco2c0dba32010-03-06 22:47:06 -080018 test_dir : (TBD) Directory to search for test files (default .)
Dan Talayco48370102010-03-03 15:17:33 -080019 test_spec : (TBD) Specification of test(s) to run
20 log_file : Filename for test logging
Dan Talayco2c0dba32010-03-06 22:47:06 -080021 list : Boolean: List all tests and exit
Dan Talayco48370102010-03-03 15:17:33 -080022 debug : String giving debug level (info, warning, error...)
Dan Talayco2c0dba32010-03-06 22:47:06 -080023</pre>
Dan Talayco48370102010-03-03 15:17:33 -080024
25See config_defaults below for the default values.
26
Dan Talayco2c0dba32010-03-06 22:47:06 -080027The following are stored in the config dictionary, but are not currently
28configurable through the command line.
29
30<pre>
31 dbg_level : logging module value of debug level
32 port_map : Map of dataplane OpenFlow port to OS interface names
Dan Talayco2c0dba32010-03-06 22:47:06 -080033</pre>
34
Rich Laned1d9c282012-10-04 22:07:10 -070035Each test may be assigned a priority by setting the "priority" property
36in the class definition. For now, the only use of this is to avoid
Dan Talaycoc24aaae2010-07-08 14:05:24 -070037automatic inclusion of tests into the default list. This is done by
Rich Laned1d9c282012-10-04 22:07:10 -070038setting the priority value less than 0. Eventually we may add ordering
Dan Talaycoc24aaae2010-07-08 14:05:24 -070039of test execution by test priority.
40
Dan Talayco2c0dba32010-03-06 22:47:06 -080041To add a test to the system, either: edit an existing test case file (like
42basic.py) to add a test class which inherits from unittest.TestCase (directly
Rich Lane477f4812012-10-04 22:49:00 -070043or indirectly); or add a new file with the test case class. Preferably the
44file is in the same directory as existing tests, though you can specify the
45directory on the command line. The file should not be called "all" as that's
46reserved for the test-spec.
Dan Talayco2c0dba32010-03-06 22:47:06 -080047
48TBD: To add configuration to the system, first add an entry to config_default
Dan Talayco48370102010-03-03 15:17:33 -080049below. If you want this to be a command line parameter, edit config_setup
50to add the option and default value to the parser. Then edit config_get
51to make sure the option value gets copied into the configuration
52structure (which then gets passed to everyone else).
53
54By convention, oft attempts to import the contents of a file by the
55name of $platform.py into the local namespace.
56
57IMPORTANT: That file should define a function platform_config_update which
58takes a configuration dictionary as an argument and updates it for the
59current run. In particular, it should set up config["port_map"] with
60the proper map from OF port numbers to OF interface names.
61
Rich Lane8aebc5e2012-09-25 17:57:53 -070062You can add your own platform, say gp104, by adding a file gp104.py to the
63platforms directory that defines the function platform_config_update and then
64use the parameter --platform=gp104 on the command line. You can also use the
65--platform-dir option to change which directory is searched.
Dan Talayco48370102010-03-03 15:17:33 -080066
Rich Lane477f4812012-10-04 22:49:00 -070067The current model for test sets is basic.py.
Dan Talayco48370102010-03-03 15:17:33 -080068
Dan Talayco52f64442010-03-03 15:32:41 -080069Default setup:
70
71The default setup runs locally using veth pairs. To exercise this,
72checkout and build an openflow userspace datapath. Then start it on
73the local host:
Dan Talayco2c0dba32010-03-06 22:47:06 -080074<pre>
Dan Talayco52f64442010-03-03 15:32:41 -080075 sudo ~/openflow/regress/bin/veth_setup.pl
76 sudo ofdatapath -i veth0,veth2,veth4,veth6 punix:/tmp/ofd &
77 sudo ofprotocol unix:/tmp/ofd tcp:127.0.0.1 --fail=closed --max-backoff=1 &
78
79Next, run oft:
80 sudo ./oft --debug=info
Dan Talayco2c0dba32010-03-06 22:47:06 -080081</pre>
Dan Talayco52f64442010-03-03 15:32:41 -080082
83Examine oft.log if things don't work.
Dan Talayco2c0dba32010-03-06 22:47:06 -080084
Dan Talayco1a88c122010-03-07 22:00:20 -080085@todo Support per-component debug levels (esp controller vs dataplane)
Rich Lane15f64de2012-10-04 21:25:57 -070086@todo Allow specification of priority to override prio check
Dan Talayco2c0dba32010-03-06 22:47:06 -080087
Dan Talayco1a88c122010-03-07 22:00:20 -080088Current test case setup:
Rich Lane477f4812012-10-04 22:49:00 -070089 File with the .py extension in the test directory are considered test files.
90 Support a command line option --test-spec to choose the tests to run.
91 Support test-spec "all" to specify all tests.
Dan Talayco48370102010-03-03 15:17:33 -080092"""
93
94import sys
95from optparse import OptionParser
Dan Talayco2c0dba32010-03-06 22:47:06 -080096from subprocess import Popen,PIPE
Dan Talayco48370102010-03-03 15:17:33 -080097import logging
98import unittest
Dan Talayco2c0dba32010-03-06 22:47:06 -080099import time
Brandon Heller446c1432010-04-01 12:43:27 -0700100import os
Rich Lane6b452bc2012-07-09 16:52:21 -0700101import imp
Rich Lane8592bec2012-09-03 09:06:59 -0700102import random
Rich Lane5bd6cf92012-10-04 17:57:24 -0700103import signal
Rich Lane943be672012-10-04 19:20:16 -0700104import fnmatch
Dan Talayco48370102010-03-03 15:17:33 -0800105
Rich Lanefadf3452012-10-03 16:23:37 -0700106root_dir = os.path.dirname(os.path.realpath(__file__))
107
108pydir = os.path.join(root_dir, 'src', 'python')
Rich Lane39878042012-07-09 14:45:35 -0700109if os.path.exists(os.path.join(pydir, 'oftest')):
110 # Running from source tree
111 sys.path.insert(0, pydir)
112
Rich Lane477f4812012-10-04 22:49:00 -0700113import oftest
114from oftest import config
115
Rich Laneb0470142012-10-04 15:50:35 -0700116try:
117 import oftest.message
118except:
119 sys.exit("Missing OpenFlow message classes: please run \"make -C tools/munger\"")
120
Rich Laneda3b5ad2012-10-03 09:05:32 -0700121import oftest.testutils
Rich Lanee55abf72012-07-26 20:11:42 -0700122import oftest.ofutils
Dan Talaycoba3745c2010-07-21 21:51:08 -0700123
Dan Talayco02eca0b2010-04-15 16:09:43 -0700124try:
125 import scapy.all as scapy
126except:
127 try:
128 import scapy as scapy
129 except:
130 sys.exit("Need to install scapy for packet parsing")
131
Dan Talayco48370102010-03-03 15:17:33 -0800132##@var DEBUG_LEVELS
133# Map from strings to debugging levels
134DEBUG_LEVELS = {
135 'debug' : logging.DEBUG,
136 'verbose' : logging.DEBUG,
137 'info' : logging.INFO,
138 'warning' : logging.WARNING,
139 'warn' : logging.WARNING,
140 'error' : logging.ERROR,
141 'critical' : logging.CRITICAL
142}
143
144_debug_default = "warning"
145_debug_level_default = DEBUG_LEVELS[_debug_default]
146
147##@var config_default
148# The default configuration dictionary for OFT
149config_default = {
Dan Talayco551befa2010-07-15 17:05:32 -0700150 "param" : None,
Dan Talayco48370102010-03-03 15:17:33 -0800151 "platform" : "local",
Rich Lane941d1dd2012-07-30 14:27:53 -0700152 "platform_args" : None,
Dan Talayco69ca4d62012-11-15 11:50:22 -0800153 "switch_ip" : None, # If not none, actively connect to switch
154 "controller_host" : "0.0.0.0", # For passive bind
Dan Talayco48370102010-03-03 15:17:33 -0800155 "controller_port" : 6633,
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700156 "relax" : False,
Rich Lanec76b09a2013-01-02 16:53:22 -0800157 "test_spec" : "",
Rich Lane1a08d232013-01-04 16:03:50 -0800158 "test_file" : None,
Dan Talayco48370102010-03-03 15:17:33 -0800159 "log_file" : "oft.log",
Dan Talayco69ca4d62012-11-15 11:50:22 -0800160 "log_append" : False,
Dan Talayco2c0dba32010-03-06 22:47:06 -0800161 "list" : False,
Jeffrey Townsend6614f972012-08-16 13:27:18 -0700162 "list_test_names" : False,
Dan Talayco48370102010-03-03 15:17:33 -0800163 "debug" : _debug_default,
164 "dbg_level" : _debug_level_default,
Dan Talaycoac25cf32010-07-20 14:08:28 -0700165 "port_map" : {},
Dan Talaycod7c80d12012-04-03 15:20:57 -0700166 "test_params" : "None",
Rich Laneee57ad02012-07-13 15:40:36 -0700167 "allow_user" : False,
Rich Lane9a84a4f2012-07-17 12:27:42 -0700168 "fail_skipped" : False,
Rich Lanee55abf72012-07-26 20:11:42 -0700169 "default_timeout" : 2,
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000170 "minsize" : 0,
Rich Lane8592bec2012-09-03 09:06:59 -0700171 "random_seed" : None,
Rich Lanee284b6b2012-10-03 09:19:58 -0700172 "test_dir" : os.path.join(root_dir, "tests"),
Rich Lane8aebc5e2012-09-25 17:57:53 -0700173 "platform_dir" : os.path.join(root_dir, "platforms"),
Rich Lane8260a2d2012-10-09 14:58:35 -0700174 "priority" : 0,
Dan Talayco48370102010-03-03 15:17:33 -0800175}
176
Dan Talayco1a88c122010-03-07 22:00:20 -0800177#@todo Set up a dict of config params so easier to manage:
178# <param> <cmdline flags> <default value> <help> <optional parser>
179
Dan Talayco48370102010-03-03 15:17:33 -0800180# Map options to config structure
181def config_get(opts):
182 "Convert options class to OFT configuration dictionary"
183 cfg = config_default.copy()
Dan Talayco2c0dba32010-03-06 22:47:06 -0800184 for key in cfg.keys():
Rich Lanea92f2522012-10-04 18:11:04 -0700185 cfg[key] = getattr(opts, key)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800186
187 # Special case checks
Dan Talayco48370102010-03-03 15:17:33 -0800188 if opts.debug not in DEBUG_LEVELS.keys():
189 print "Warning: Bad value specified for debug level; using default"
190 opts.debug = _debug_default
Dan Talayco02eca0b2010-04-15 16:09:43 -0700191 if opts.verbose:
192 cfg["debug"] = "verbose"
Dan Talayco48370102010-03-03 15:17:33 -0800193 cfg["dbg_level"] = DEBUG_LEVELS[cfg["debug"]]
Dan Talayco2c0dba32010-03-06 22:47:06 -0800194
Dan Talayco48370102010-03-03 15:17:33 -0800195 return cfg
196
197def config_setup(cfg_dflt):
198 """
199 Set up the configuration including parsing the arguments
200
201 @param cfg_dflt The default configuration dictionary
202 @return A pair (config, args) where config is an config
203 object and args is any additional arguments from the command line
204 """
205
Rich Lane4113a582013-01-03 10:13:02 -0800206 usage = "usage: %prog [options] (test|group)..."
207
208 description = """
209OFTest is a framework and set of tests for validating OpenFlow switches.
210
211The default configuration assumes that an OpenFlow 1.0 switch is attempting to
212connect to a controller on the machine running OFTest, port 6633. Additionally,
213the interfaces veth1, veth3, veth5, and veth7 should be connected to the switch's
214dataplane.
215
216If no positional arguments are given then OFTest will run all tests that
217depend only on standard OpenFlow 1.0. Otherwise each positional argument
218is interpreted as either a test name or a test group name. The union of
219these will be executed. To see what groups each test belongs to use the
220--list option.
221"""
222
223 parser = OptionParser(version="%prog 0.1",
224 usage=usage,
225 description=description)
Dan Talayco48370102010-03-03 15:17:33 -0800226
Dan Talayco2c0dba32010-03-06 22:47:06 -0800227 #@todo parse port map as option?
Dan Talayco48370102010-03-03 15:17:33 -0800228 # Set up default values
Rich Lanea92f2522012-10-04 18:11:04 -0700229 parser.set_defaults(**cfg_dflt)
Dan Talayco48370102010-03-03 15:17:33 -0800230
Dan Talayco2c0dba32010-03-06 22:47:06 -0800231 #@todo Add options via dictionary
Dan Talayco48370102010-03-03 15:17:33 -0800232 plat_help = """Set the platform type. Valid values include:
233 local: User space virtual ethernet pair setup
234 remote: Remote embedded Broadcom based switch
Dan Talayco673e0852010-03-06 23:09:23 -0800235 Create a new_plat.py file and use --platform=new_plat on the command line
Dan Talayco48370102010-03-03 15:17:33 -0800236 """
Rich Lane941d1dd2012-07-30 14:27:53 -0700237 parser.add_option("-a", "--platform-args", help="Custom arguments per platform.")
Dan Talayco48370102010-03-03 15:17:33 -0800238 parser.add_option("-P", "--platform", help=plat_help)
239 parser.add_option("-H", "--host", dest="controller_host",
240 help="The IP/name of the test controller host")
Dan Talayco69ca4d62012-11-15 11:50:22 -0800241 parser.add_option("-S", "--switch-ip", dest="switch_ip",
242 help="If set, actively connect to this switch by IP")
Dan Talayco48370102010-03-03 15:17:33 -0800243 parser.add_option("-p", "--port", dest="controller_port",
244 type="int", help="Port number of the test controller")
Dan Talayco673e0852010-03-06 23:09:23 -0800245 test_list_help = """Indicate tests to run. Valid entries are "all" (the
Dan Talaycocfa172f2012-03-23 12:03:00 -0700246 default) or a comma separated list of:
Rich Lane4113a582013-01-03 10:13:02 -0800247 group Run all tests in the named group
Dan Talayco673e0852010-03-06 23:09:23 -0800248 module.testcase Run the specific test case
249 """
Rich Lanef261a102012-07-25 13:41:38 -0700250 parser.add_option("-T", "--test-spec", "--test-list", help=test_list_help)
Rich Lane1a08d232013-01-04 16:03:50 -0800251 parser.add_option("-f", "--test-file", help="File of tests to run, one per line")
Dan Talayco48370102010-03-03 15:17:33 -0800252 parser.add_option("--log-file",
253 help="Name of log file, empty string to log to console")
Dan Talayco69ca4d62012-11-15 11:50:22 -0800254 parser.add_option("--log-append", action="store_true",
255 help="Do not delete log file if specified")
Dan Talayco48370102010-03-03 15:17:33 -0800256 parser.add_option("--debug",
257 help="Debug lvl: debug, info, warning, error, critical")
Jeffrey Townsend6614f972012-08-16 13:27:18 -0700258 parser.add_option("--list-test-names", action='store_true',
Rich Laned8e45482013-01-02 17:36:21 -0800259 help="List test names matching the test spec and exit", default=False)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800260 parser.add_option("--list", action="store_true",
Brandon Heller824504e2010-04-01 12:21:37 -0700261 help="List all tests and exit")
Rich Lanee703c682012-10-08 15:45:24 -0700262 parser.add_option("-v", "--verbose", action="store_true",
Dan Talayco02eca0b2010-04-15 16:09:43 -0700263 help="Short cut for --debug=verbose")
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700264 parser.add_option("--relax", action="store_true",
265 help="Relax packet match checks allowing other packets")
Dan Talaycoac25cf32010-07-20 14:08:28 -0700266 parser.add_option("-t", "--test-params",
Dan Talaycof6e76c02012-03-23 10:56:12 -0700267 help="""Set test parameters: key=val;...
268 NOTE: key MUST be a valid Python identifier, egr_count not egr-count
269 See --list""")
Rich Laneee57ad02012-07-13 15:40:36 -0700270 parser.add_option("--allow-user", action="store_true",
271 help="Proceed even if oftest is not run as root")
Rich Lane9a84a4f2012-07-17 12:27:42 -0700272 parser.add_option("--fail-skipped", action="store_true",
273 help="Return failure if any test was skipped")
Rich Lanee55abf72012-07-26 20:11:42 -0700274 parser.add_option("--default-timeout", type="int",
275 help="Timeout in seconds for most operations")
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000276 parser.add_option("--minsize", type="int",
277 help="Minimum allowable packet size on the dataplane.",
278 default=0)
Rich Lane8592bec2012-09-03 09:06:59 -0700279 parser.add_option("--random-seed", type="int",
280 help="Random number generator seed",
281 default=None)
Rich Lanebc3d2962012-09-25 09:34:17 -0700282 parser.add_option("--test-dir", type="string",
283 help="Directory containing tests")
Rich Lane8aebc5e2012-09-25 17:57:53 -0700284 parser.add_option("--platform-dir", type="string",
285 help="Directory containing platform modules")
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000286
Dan Talayco48370102010-03-03 15:17:33 -0800287 # Might need this if other parsers want command line
288 # parser.allow_interspersed_args = False
289 (options, args) = parser.parse_args()
290
291 config = config_get(options)
292
293 return (config, args)
294
295def logging_setup(config):
296 """
297 Set up logging based on config
298 """
Rich Lane71d887d2012-12-22 17:05:13 -0800299 _format = "%(asctime)s.%(msecs)d %(name)-10s: %(levelname)-8s: %(message)s"
Dan Talayco48370102010-03-03 15:17:33 -0800300 _datefmt = "%H:%M:%S"
Dan Talayco69ca4d62012-11-15 11:50:22 -0800301 _mode = config["log_append"] and "a" or "w"
Dan Talayco88fc8802010-03-07 11:37:52 -0800302 logging.basicConfig(filename=config["log_file"],
Dan Talayco69ca4d62012-11-15 11:50:22 -0800303 filemode=_mode,
Dan Talayco88fc8802010-03-07 11:37:52 -0800304 level=config["dbg_level"],
305 format=_format, datefmt=_datefmt)
Dan Talayco48370102010-03-03 15:17:33 -0800306
Rich Lane943be672012-10-04 19:20:16 -0700307def load_test_modules(config):
308 """
309 Load tests from the test_dir directory.
Dan Talayco2c0dba32010-03-06 22:47:06 -0800310
Rich Lane943be672012-10-04 19:20:16 -0700311 Test cases are subclasses of unittest.TestCase
Dan Talayco2c0dba32010-03-06 22:47:06 -0800312
Rich Lanecc45b8e2013-01-02 15:55:02 -0800313 Also updates the _groups member to include "standard" and
314 module test groups if appropriate.
315
Dan Talayco2c0dba32010-03-06 22:47:06 -0800316 @param config The oft configuration dictionary
Rich Lane943be672012-10-04 19:20:16 -0700317 @returns A dictionary from test module names to tuples of
318 (module, dictionary from test names to test classes).
Dan Talayco2c0dba32010-03-06 22:47:06 -0800319 """
320
Rich Lane943be672012-10-04 19:20:16 -0700321 result = {}
Dan Talayco2c0dba32010-03-06 22:47:06 -0800322
Rich Lane943be672012-10-04 19:20:16 -0700323 for root, dirs, filenames in os.walk(config["test_dir"]):
324 # Iterate over each python file
325 for filename in fnmatch.filter(filenames, '[!.]*.py'):
326 modname = os.path.splitext(os.path.basename(filename))[0]
Dan Talayco2c0dba32010-03-06 22:47:06 -0800327
Rich Lane943be672012-10-04 19:20:16 -0700328 try:
329 if sys.modules.has_key(modname):
330 mod = sys.modules[modname]
331 else:
332 mod = imp.load_module(modname, *imp.find_module(modname, [root]))
333 except:
334 logging.warning("Could not import file " + filename)
335 raise
Rich Lane520e4152012-07-09 16:18:16 -0700336
Rich Lane943be672012-10-04 19:20:16 -0700337 # Find all testcases defined in the module
338 tests = dict((k, v) for (k, v) in mod.__dict__.items() if type(v) == type and
339 issubclass(v, unittest.TestCase))
340 if tests:
Rich Lanecc45b8e2013-01-02 15:55:02 -0800341 for (testname, test) in tests.items():
342 # Set default annotation values
343 if not hasattr(test, "_groups"):
344 test._groups = []
345 if not hasattr(test, "_nonstandard"):
346 test._nonstandard = False
347 if not hasattr(test, "_disabled"):
348 test._disabled = False
349
350 # Put test in its module's test group
351 if not test._disabled:
352 test._groups.append(modname)
353
354 # Put test in the standard test group
355 if not test._disabled and not test._nonstandard:
356 test._groups.append("standard")
357 test._groups.append("all") # backwards compatibility
358
Rich Lane943be672012-10-04 19:20:16 -0700359 result[modname] = (mod, tests)
Rich Lane520e4152012-07-09 16:18:16 -0700360
Rich Lane943be672012-10-04 19:20:16 -0700361 return result
Dan Talayco2c0dba32010-03-06 22:47:06 -0800362
Rich Lanec76b09a2013-01-02 16:53:22 -0800363def prune_tests(test_specs, test_modules):
Rich Lane15f64de2012-10-04 21:25:57 -0700364 """
Rich Lanec76b09a2013-01-02 16:53:22 -0800365 Return tests matching the given test-specs
366 @param test_specs A list of group names or test names.
Rich Lane15f64de2012-10-04 21:25:57 -0700367 @param test_modules Same format as the output of load_test_modules.
368 @returns Same format as the output of load_test_modules.
369 """
370 result = {}
Rich Lanec76b09a2013-01-02 16:53:22 -0800371 for e in test_specs:
Rich Lane15f64de2012-10-04 21:25:57 -0700372 matched = False
373 for (modname, (mod, tests)) in test_modules.items():
Rich Lanecc45b8e2013-01-02 15:55:02 -0800374 for (testname, test) in tests.items():
375 if e in test._groups or e == "%s.%s" % (modname, testname):
376 result.setdefault(modname, (mod, {}))
377 result[modname][1][testname] = test
378 matched = True
Rich Lane15f64de2012-10-04 21:25:57 -0700379 if not matched:
Rich Lanecc45b8e2013-01-02 15:55:02 -0800380 die("test-spec element %s did not match any tests" % e)
Rich Lane15f64de2012-10-04 21:25:57 -0700381 return result
382
Dan Talayco2c0dba32010-03-06 22:47:06 -0800383def die(msg, exit_val=1):
384 print msg
385 logging.critical(msg)
386 sys.exit(exit_val)
387
Dan Talayco79f36082010-03-11 16:53:53 -0800388def _space_to(n, str):
389 """
390 Generate a string of spaces to achieve width n given string str
391 If length of str >= n, return one space
392 """
393 spaces = n - len(str)
394 if spaces > 0:
395 return " " * spaces
396 return " "
397
Dan Talayco48370102010-03-03 15:17:33 -0800398#
399# Main script
400#
401
Rich Lane477f4812012-10-04 22:49:00 -0700402# Setup global configuration
403(new_config, args) = config_setup(config_default)
404oftest.config.update(new_config)
Dan Talayco48370102010-03-03 15:17:33 -0800405
Rich Laned7a85c42012-09-28 15:38:45 -0700406logging_setup(config)
407logging.info("++++++++ " + time.asctime() + " ++++++++")
408
Rich Lanee284b6b2012-10-03 09:19:58 -0700409# Allow tests to import each other
410sys.path.append(config["test_dir"])
411
Rich Lanec76b09a2013-01-02 16:53:22 -0800412test_specs = args
413if config["test_spec"] != "":
414 print >> sys.stderr, "WARNING: The --test-spec option is deprecated"
415 test_specs += config["test_spec"].split(',')
Rich Lane1a08d232013-01-04 16:03:50 -0800416if config["test_file"] != None:
417 with open(config["test_file"], 'r') as f:
418 for line in f:
419 line, _, _ = line.partition('#') # remove comments
420 line = line.strip()
421 if line:
422 test_specs.append(line)
Rich Lanec76b09a2013-01-02 16:53:22 -0800423if test_specs == []:
424 test_specs = ["standard"]
425
Rich Laned8e45482013-01-02 17:36:21 -0800426test_modules = load_test_modules(config)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800427
428# Check if test list is requested; display and exit if so
429if config["list"]:
Dan Talayco7f8dba82012-04-12 12:58:52 -0700430 mod_count = 0
431 test_count = 0
Rich Lane37f42112013-01-03 13:41:49 -0800432 all_groups = set()
Rich Lanee811e7b2013-01-03 13:36:54 -0800433 print """\
434Tests are shown grouped by module. If a test is in any groups beyond "standard"
435and its module's group then they are shown in parentheses."""
436 print
437 print """\
438Tests marked with '*' are non-standard and may require vendor extensions or
439special switch configuration. These are not part of the "standard" test group."""
440 print
441 print """\
442Tests marked with '!' are disabled because they are experimental, special-purpose,
443or are too long to be run normally. These are not part of the "standard" test
444group or their module's test group."""
445 print
446 print "Tests marked (TP1) after name take --test-params including:"
447 print " 'vid=N;strip_vlan=bool;add_vlan=bool'"
Rich Lanee811e7b2013-01-03 13:36:54 -0800448 print
449 print "Test List:"
Rich Lane943be672012-10-04 19:20:16 -0700450 for (modname, (mod, tests)) in test_modules.items():
Dan Talayco7f8dba82012-04-12 12:58:52 -0700451 mod_count += 1
Rich Lane80efd652013-01-02 14:05:20 -0800452 desc = (mod.__doc__ or "No description").strip().split('\n')[0]
Dan Talayco79f36082010-03-11 16:53:53 -0800453 start_str = " Module " + mod.__name__ + ": "
454 print start_str + _space_to(22, start_str) + desc
Rich Lane943be672012-10-04 19:20:16 -0700455 for (testname, test) in tests.items():
Dan Talayco551befa2010-07-15 17:05:32 -0700456 try:
Rich Lane0f5b9c72013-01-02 14:05:20 -0800457 desc = (test.__doc__ or "").strip()
Dan Talayco551befa2010-07-15 17:05:32 -0700458 desc = desc.split('\n')[0]
459 except:
460 desc = "No description"
Rich Lanee811e7b2013-01-03 13:36:54 -0800461 groups = set(test._groups) - set(["all", "standard", modname])
Rich Lane37f42112013-01-03 13:41:49 -0800462 all_groups.update(test._groups)
Rich Lanee811e7b2013-01-03 13:36:54 -0800463 if groups:
464 desc = "(%s) %s" % (",".join(groups), desc)
465 start_str = " %s%s %s:" % (test._nonstandard and "*" or " ",
466 test._disabled and "!" or " ",
467 testname)
Dan Talayco551befa2010-07-15 17:05:32 -0700468 if len(start_str) > 22:
469 desc = "\n" + _space_to(22, "") + desc
Dan Talayco79f36082010-03-11 16:53:53 -0800470 print start_str + _space_to(22, start_str) + desc
Dan Talayco7f8dba82012-04-12 12:58:52 -0700471 test_count += 1
Dan Talayco79f36082010-03-11 16:53:53 -0800472 print
Rich Lane15f64de2012-10-04 21:25:57 -0700473 print "%d modules shown with a total of %d tests" % \
474 (mod_count, test_count)
475 print
Rich Lane37f42112013-01-03 13:41:49 -0800476 print "Test groups: %s" % (', '.join(sorted(all_groups)))
477
Dan Talayco2c0dba32010-03-06 22:47:06 -0800478 sys.exit(0)
479
Rich Laned8e45482013-01-02 17:36:21 -0800480test_modules = prune_tests(test_specs, test_modules)
481
Jeffrey Townsend6614f972012-08-16 13:27:18 -0700482# Check if test list is requested; display and exit if so
483if config["list_test_names"]:
Rich Lane943be672012-10-04 19:20:16 -0700484 for (modname, (mod, tests)) in test_modules.items():
Rich Lane943be672012-10-04 19:20:16 -0700485 for (testname, test) in tests.items():
Rich Lanecc45b8e2013-01-02 15:55:02 -0800486 print "%s.%s" % (modname, testname)
Jeffrey Townsend6614f972012-08-16 13:27:18 -0700487 sys.exit(0)
488
Dan Talayco2c0dba32010-03-06 22:47:06 -0800489# Generate the test suite
490#@todo Decide if multiple suites are ever needed
491suite = unittest.TestSuite()
492
Rich Lane15f64de2012-10-04 21:25:57 -0700493for (modname, (mod, tests)) in test_modules.items():
494 for (testname, test) in tests.items():
Rich Lanecc45b8e2013-01-02 15:55:02 -0800495 logging.info("Adding test " + modname + "." + testname)
496 suite.addTest(test())
Dan Talayco2c0dba32010-03-06 22:47:06 -0800497
Rich Lane51590f62012-10-09 15:06:29 -0700498# Allow platforms to import each other
499sys.path.append(config["platform_dir"])
500
Rich Lane8aebc5e2012-09-25 17:57:53 -0700501# Load the platform module
502platform_name = config["platform"]
503logging.info("Importing platform: " + platform_name)
504platform_mod = None
505try:
Rich Lane483e1542012-10-05 09:29:39 -0700506 platform_mod = imp.load_module(platform_name, *imp.find_module(platform_name, [config["platform_dir"]]))
Rich Lane8aebc5e2012-09-25 17:57:53 -0700507except:
508 logging.warn("Failed to import " + platform_name + " platform module")
509 raise
Dan Talayco48370102010-03-03 15:17:33 -0800510
511try:
Rich Lane8aebc5e2012-09-25 17:57:53 -0700512 platform_mod.platform_config_update(config)
Dan Talayco48370102010-03-03 15:17:33 -0800513except:
514 logging.warn("Could not run platform host configuration")
Rich Laneef403e52012-07-09 14:59:43 -0700515 raise
Dan Talayco48370102010-03-03 15:17:33 -0800516
517if not config["port_map"]:
Rich Lane8aebc5e2012-09-25 17:57:53 -0700518 die("Interface port map was not defined by the platform. Exiting.")
Dan Talayco48370102010-03-03 15:17:33 -0800519
520logging.debug("Configuration: " + str(config))
521logging.info("OF port map: " + str(config["port_map"]))
522
Dan Talayco2c0dba32010-03-06 22:47:06 -0800523if config["dbg_level"] == logging.CRITICAL:
524 _verb = 0
525elif config["dbg_level"] >= logging.WARNING:
526 _verb = 1
527else:
528 _verb = 2
Dan Talayco48370102010-03-03 15:17:33 -0800529
Rich Lanee55abf72012-07-26 20:11:42 -0700530oftest.ofutils.default_timeout = config["default_timeout"]
Rich Laneda3b5ad2012-10-03 09:05:32 -0700531oftest.testutils.MINSIZE = config['minsize']
Rich Lanee55abf72012-07-26 20:11:42 -0700532
Rich Laneee57ad02012-07-13 15:40:36 -0700533if os.getuid() != 0 and not config["allow_user"]:
Brandon Heller446c1432010-04-01 12:43:27 -0700534 print "ERROR: Super-user privileges required. Please re-run with " \
535 "sudo or as root."
Rich Laned1d9c282012-10-04 22:07:10 -0700536 sys.exit(1)
Brandon Heller446c1432010-04-01 12:43:27 -0700537
Rich Lane8592bec2012-09-03 09:06:59 -0700538if config["random_seed"] is not None:
539 logging.info("Random seed: %d" % config["random_seed"])
540 random.seed(config["random_seed"])
541
Rich Lane5bd6cf92012-10-04 17:57:24 -0700542# Remove python's signal handler which raises KeyboardError. Exiting from an
543# exception waits for all threads to terminate which might not happen.
544signal.signal(signal.SIGINT, signal.SIG_DFL)
Dan Talaycoac25cf32010-07-20 14:08:28 -0700545
546if __name__ == "__main__":
Rich Lane2c7812c2012-12-27 17:52:23 -0800547 # Set up the dataplane
548 oftest.dataplane_instance = oftest.dataplane.DataPlane(config)
549 for of_port, ifname in config["port_map"].items():
550 oftest.dataplane_instance.port_add(ifname, of_port)
551
Dan Talaycoac25cf32010-07-20 14:08:28 -0700552 logging.info("*** TEST RUN START: " + time.asctime())
Rich Lane6a1ecb82012-07-10 18:59:44 -0700553 result = unittest.TextTestRunner(verbosity=_verb).run(suite)
Rich Laneda3b5ad2012-10-03 09:05:32 -0700554 if oftest.testutils.skipped_test_count > 0:
Dan Talaycoba3745c2010-07-21 21:51:08 -0700555 ts = " tests"
Rich Laneda3b5ad2012-10-03 09:05:32 -0700556 if oftest.testutils.skipped_test_count == 1: ts = " test"
557 logging.info("Skipped " + str(oftest.testutils.skipped_test_count) + ts)
558 print("Skipped " + str(oftest.testutils.skipped_test_count) + ts)
Dan Talaycoac25cf32010-07-20 14:08:28 -0700559 logging.info("*** TEST RUN END : " + time.asctime())
Rich Lane2c7812c2012-12-27 17:52:23 -0800560
561 # Shutdown the dataplane
562 oftest.dataplane_instance.kill()
563 oftest.dataplane_instance = None
564
Rich Lane50d42eb2012-07-16 11:57:03 -0700565 if result.failures or result.errors:
Shudong Zhoud895fcb2012-08-01 19:09:55 -0700566 # exit(1) hangs sometimes
567 os._exit(1)
Rich Laneda3b5ad2012-10-03 09:05:32 -0700568 if oftest.testutils.skipped_test_count > 0 and config["fail_skipped"]:
Shudong Zhoud895fcb2012-08-01 19:09:55 -0700569 os._exit(1)