I started creating and maintaining Ansible modules for Cumulus Networks about a year ago, and love it.
Unit testing Ansible modules is important to me. I realized writing unit tests reduced the time I spent performing integration tests on real switches.
I will show a simple module and discuss how I configured unit tests for it.
Module structure
Ansible modules produce 3 main types of exit signals:
-
AnsibleModule.exit_json(changed=True)
: This means something changed. This activates the notify action, also if available -
AnsibleModule.exit_json(changed=False)
: Nothing changed -
AnsibleModule.fail_json(msg="Something bad")
: This exits the playbook. No further tasks should be run.
In this example, the module uses the following arguments:
-
prefix - can be something like ‘10.1.1.0/24’
-
timeout - how long to run the check before quitting.
Pseudo code for the module
- Create Ansible module instance
- Check if route exists.
- If route exists, return “no change”
- If route doesn not exist, “Fail”
- module does not have a “changed” option
It is common for modules to report a change if something happens. This is one of those few examples where reporting a change doesn’t make sense.
The common use case for this module is when configuring a routing protocol on
the switch or server, you want to confirm that the routes are been exchanged. So
the prefix_check
module, ensures that routing is working as expected. If the
route is missing, then Fail.
Time for some code
Below is the main()
function. Notice that main()
is split out
What to perform unit testing on?
- main()
- Check AnsibleModule variable inputs. In my experience I fat finger stuff in the
main()
function all the time. This adds a basic sanity check to ensure I don’t make any mistakes with the module arguments. This kind of error is easily detected in live system testing. I feel it saves me time during live testing to have this unit test in place. - Module exits correctly under different conditions. That is, if route check fails, run the
exit_json()
function. If it passes run thefail_json
function. When making changes in themain()
, I sometimes mess up the exit logic. This makes sure to catch any errors in the basic outcome logic of the module
- Check AnsibleModule variable inputs. In my experience I fat finger stuff in the
- check_if_route_exists()
- Mock ip route calls and if the route exists, return true
- Mock the timeout, and ensure it returns false if the timeout is reached
- Test that the ip route call is correct
- This ensures that future modifications of the module don’t accidentally change the important system calls of this module.
The next blog post goes into test details and how I performed mocking.
Unit tests eliminate a lot of the common coding errors, I would otherwise encounter during integration testing on real switches. It has saved me a lot of time, and adds some dependability to modules I work on.