| tdc - Linux Traffic Control (tc) unit testing suite |
| |
| Author: Lucas Bates - lucasb@mojatatu.com |
| |
| tdc is a Python script to load tc unit tests from a separate JSON file and |
| execute them inside a network namespace dedicated to the task. |
| |
| |
| REQUIREMENTS |
| ------------ |
| |
| * Minimum Python version of 3.8. |
| |
| * The kernel must have network namespace support if using nsPlugin |
| |
| * The kernel must have veth support available, as a veth pair is created |
| prior to running the tests when using nsPlugin. |
| |
| * The kernel must have the appropriate infrastructure enabled to run all tdc |
| unit tests. See the config file in this directory for minimum required |
| features. As new tests will be added, config options list will be updated. |
| |
| * All tc-related features being tested must be built in or available as |
| modules. To check what is required in current setup run: |
| ./tdc.py -c |
| |
| Note: |
| In the current release, tdc run will abort due to a failure in setup or |
| teardown commands - which includes not being able to run a test simply |
| because the kernel did not support a specific feature. (This will be |
| handled in a future version - the current workaround is to run the tests |
| on specific test categories that your kernel supports) |
| |
| |
| BEFORE YOU RUN |
| -------------- |
| |
| The path to the tc executable that will be most commonly tested can be defined |
| in the tdc_config.py file. Find the 'TC' entry in the NAMES dictionary and |
| define the path. |
| |
| If you need to test a different tc executable on the fly, you can do so by |
| using the -p option when running tdc: |
| ./tdc.py -p /path/to/tc |
| |
| |
| RUNNING TDC |
| ----------- |
| |
| To use tdc, root privileges are required. This is because the |
| commands being tested must be run as root. The code that enforces |
| execution by root uid has been moved into a plugin (see PLUGIN |
| ARCHITECTURE, below). |
| |
| Tests that use a network device should have nsPlugin.py listed as a |
| requirement for that test. nsPlugin executes all commands within a |
| network namespace and creates a veth pair which may be used in those test |
| cases. To disable execution within the namespace, pass the -N option |
| to tdc when starting a test run; the veth pair will still be created |
| by the plugin. |
| |
| Running tdc without any arguments will run all tests. Refer to the section |
| on command line arguments for more information, or run: |
| ./tdc.py -h |
| |
| tdc will list the test names as they are being run, and print a summary in |
| TAP (Test Anything Protocol) format when they are done. If tests fail, |
| output captured from the failing test will be printed immediately following |
| the failed test in the TAP output. |
| |
| |
| OVERVIEW OF TDC EXECUTION |
| ------------------------- |
| |
| One run of tests is considered a "test suite" (this will be refined in the |
| future). A test suite has one or more test cases in it. |
| |
| A test case has four stages: |
| |
| - setup |
| - execute |
| - verify |
| - teardown |
| |
| The setup and teardown stages can run zero or more commands. The setup |
| stage does some setup if the test needs it. The teardown stage undoes |
| the setup and returns the system to a "neutral" state so any other test |
| can be run next. These two stages require any commands run to return |
| success, but do not otherwise verify the results. |
| |
| The execute and verify stages each run one command. The execute stage |
| tests the return code against one or more acceptable values. The |
| verify stage checks the return code for success, and also compares |
| the stdout with a regular expression. |
| |
| Each of the commands in any stage will run in a shell instance. |
| |
| Each test is an atomic unit. A test that for whatever reason spans multiple test |
| definitions is a bug. |
| |
| A test that runs inside a namespace (requires "nsPlugin") will run in parallel |
| with other tests. |
| |
| Tests that use netdevsim or don't run inside a namespace run serially with regards |
| to each other. |
| |
| |
| USER-DEFINED CONSTANTS |
| ---------------------- |
| |
| The tdc_config.py file contains multiple values that can be altered to suit |
| your needs. Any value in the NAMES dictionary can be altered without affecting |
| the tests to be run. These values are used in the tc commands that will be |
| executed as part of the test. More will be added as test cases require. |
| |
| Example: |
| $TC qdisc add dev $DEV1 ingress |
| |
| The NAMES values are used to substitute into the commands in the test cases. |
| |
| |
| COMMAND LINE ARGUMENTS |
| ---------------------- |
| |
| Run tdc.py -h to see the full list of available arguments. |
| |
| PLUGIN ARCHITECTURE |
| ------------------- |
| |
| There is now a plugin architecture, and some of the functionality that |
| was in the tdc.py script has been moved into the plugins. |
| |
| The plugins are in the directory plugin-lib. The are executed from |
| directory plugins. Put symbolic links from plugins to plugin-lib, |
| and name them according to the order you want them to run. This is not |
| necessary if a test case being run requires a specific plugin to work. |
| |
| Example: |
| |
| bjb@bee:~/work/tc-testing$ ls -l plugins |
| total 4 |
| lrwxrwxrwx 1 bjb bjb 27 Oct 4 16:12 10-rootPlugin.py -> ../plugin-lib/rootPlugin.py |
| lrwxrwxrwx 1 bjb bjb 25 Oct 12 17:55 20-nsPlugin.py -> ../plugin-lib/nsPlugin.py |
| -rwxr-xr-x 1 bjb bjb 0 Sep 29 15:56 __init__.py |
| |
| The plugins are a subclass of TdcPlugin, defined in TdcPlugin.py and |
| must be called "SubPlugin" so tdc can find them. They are |
| distinguished from each other in the python program by their module |
| name. |
| |
| This base class supplies "hooks" to run extra functions. These hooks are as follows: |
| |
| pre- and post-suite |
| pre- and post-case |
| pre- and post-execute stage |
| adjust-command (runs in all stages and receives the stage name) |
| |
| The pre-suite hook receives the number of tests and an array of test ids. |
| This allows you to dump out the list of skipped tests in the event of a |
| failure during setup or teardown stage. |
| |
| The pre-case hook receives the ordinal number and test id of the current test. |
| |
| The adjust-command hook receives the stage id (see list below) and the |
| full command to be executed. This allows for last-minute adjustment |
| of the command. |
| |
| The stages are identified by the following strings: |
| |
| - pre (pre-suite) |
| - setup |
| - command |
| - verify |
| - teardown |
| - post (post-suite) |
| |
| |
| To write a plugin, you need to inherit from TdcPlugin in |
| TdcPlugin.py. To use the plugin, you have to put the |
| implementation file in plugin-lib, and add a symbolic link to it from |
| plugins. It will be detected at run time and invoked at the |
| appropriate times. There are a few examples in the plugin-lib |
| directory: |
| |
| - rootPlugin.py: |
| implements the enforcement of running as root |
| - nsPlugin.py: |
| sets up a network namespace and runs all commands in that namespace, |
| while also setting up dummy devices to be used in testing. |
| - valgrindPlugin.py |
| runs each command in the execute stage under valgrind, |
| and checks for leaks. |
| This plugin will output an extra test for each test in the test file, |
| one is the existing output as to whether the test passed or failed, |
| and the other is a test whether the command leaked memory or not. |
| (This one is a preliminary version, it may not work quite right yet, |
| but the overall template is there and it should only need tweaks.) |
| - buildebpfPlugin.py: |
| builds all programs in $EBPFDIR. |
| |
| |
| ACKNOWLEDGEMENTS |
| ---------------- |
| |
| Thanks to: |
| |
| Jamal Hadi Salim, for providing valuable test cases |
| Keara Leibovitz, who wrote the CLI test driver that I used as a base for the |
| first version of the tc testing suite. This work was presented at |
| Netdev 1.2 Tokyo in October 2016. |
| Samir Hussain, for providing help while I dove into Python for the first time |
| and being a second eye for this code. |