KEMBAR78
Testing Ansible | PPTX
Anth Courtney, Solution Architect, anth.courtney@cmdsolutions.com.au
Adam Durbin, Director of Strategy and Architecture, adam.durbin@cmdsolutions.com.au
A helicopter ride over the
Ansible testing landscape
Overview
Why should we test our Ansible artifacts?
What can we test?
What should we test?
How should we test?
But first, a contrived service!
Testing Our Playbook
We can immediately start to test our playbook, without
the need to write large amounts of additional code.
Test the syntax of our playbook and roles
Evaluate the repeatability of our playbooks and roles.
Validate configuration files generated by jinja2
templates.
Syntax Checking Playbooks and Roles
What –syntax-check does do
 Validates the syntax of a playbook
 Validates the syntax of any included
roles.
 Validates presence of any other
includes. i.e. other playbooks,
vars_files.
 Validates that tasks/modules
specified within a playbook or role
are valid.
What –syntax-check doesn't do
 Doesn’t execute a playbook.
 Doesn’t test the validity of
parameters to tasks.
 Doesn’t test for the existence of
undefined variables.
 Doesn’t test the ability to
sudo/become as defined.
 Doesn’t test the validity of any in-
line jinja2.
$ ansible-playbook –syntax-check <playbook>
"Ask yourself,
what would a build
agent do?"
Rob Shand, General DevOps Legend
Typically, we want to:
 Create an environment or
workspace for execution of
our playbook.
 Prepare the environment
or workspace for execution
of the playbook.
 Apply the playbook
against the target
environment.
 Verify the playbook
execution.
 Destroy the environment
or workspace.
Testing Idempotency
An operation is idempotent if the result of performing it once is exactly the same as the
result of performing it repeatedly without any intervening actions.
First run of playbook:
PLAY RECAP
*********************************************************************
default : ok=7 changed=10 unreachable=0 failed=0
Nth run of playbook:
PLAY RECAP
*********************************************************************
default : ok=17 changed=0 unreachable=0 failed=0
Non-Idempotent Playbooks and Roles
Non-idempotent playbooks and roles can be indicative of:
 A resource being changed multiple times within the same playbook run.
 A conditional that isn’t being evaluated correctly.
 Tasks not being properly configured for idempotence.
- name: Install custom application - non-idempotent approach
command: /tmp/application-1.0.0/bin/install --dest /opt/application
- name: Install custom application - idempotent approach
command: /tmp/application-1.0.0/bin/install --dest /opt/application
args:
creates: /opt/application
Use Case: Using Idempotency, Check-Mode
and Diffs for Security
Testing Jinja2 Templates
For copy and template tasks, Ansible provides the ability to perform validation
before copying into place.
- name: Create nginx configuration
template:
src: "etc/nginx/nginx.conf.j2"
dest: "/etc/nginx/nginx.conf"
validate: "nginx -t -c %s"
notify: Reload nginx
Testing Jinja2 Templates
For copy and template tasks, Ansible provides the ability to perform validation before
copying into place.
- name: Copy nginx modular configuration
template:
src: "etc/nginx/conf.d/modules.conf.j2"
dest: "/etc/nginx/conf.d/modules.conf"
validate: "echo 'http { include '%s'; }' > /tmp/nginx.conf && nginx -t -c /tmp/nginx.conf"
notify: Reload nginx
Batteries Included!
Ansible includes a number of modules which are test-orientated:
 assert
 fail
 stat
 wait_for
 uri
 command
Built-in Test Module: assert
- name: Check ansible version is suitable for this playbook
assert:
that: ansible_version.full | version_compare('2.1', '>=')
msg: "Ansible version 2.1 or greater is required for this playbook"
Built-in Module: uri
- name: Verify that the nginx status page is reachable
uri:
url: "http://{{ inventory_hostname }}/status"
register: result
until: result.status == 200
retries: 60
delay: 1
Using 'assert' With Multiple Conditions
- name: Make authenticated request
uri:
url: "http://localhost/api/users/anth"
method: GET
force_basic_auth: yes
user: "admin"
password: "admin123"
register: api_request
- name: Validate that api request response contains required information
assert:
that:
- api_request.json.name is defined
- api_request.json.username == 'anth'
- api_request.json.email | search("@")
Built-in Test Module: stat
- name: Determine if ssh private key exists
stat:
path: /home/ec2-user/.ssh/id_rsa
register: ssh_private_key
- name: Validate that ssh private key exists and has correct mode
assert:
that:
- ssh_private_key.stat.exists
- ssh_private_key.stat.mode == "0640"
msg: "ssh private key does not exist or has wrong mode"
Built-in Test Module: fail
 Used to ‘bail out’ of the execution of a playbook when a certain condition is met.
 Uses when to provide the condition for termination.
- name: Check ansible version is suitable for this playbook
fail:
msg: "Ansible version 2.1 or greater is required for this playbook"
when: ansible_version.full | version_compare('2.1', '<')
Built-in Module: wait_for
 Can be used to wait for a port to become available
 Can be used to wait for a file state to change or for a string/regex to appear
 Can also be used to wait for active connections to be closed before continuing
- name: Verify that cron ran configured @reboot jobs
wait_for:
path: "/var/log/cron"
search_regex: "Running @reboot jobs"
Built-in Parameter: failed_when
 Provides the ability to define ‘what constitutes failure?’ for a task.
- name: Run command to test status of application
command: /opt/cmd/bin/test_application.sh
register: command_result
failed_when: "'FAILED'" in command_result.stdout
How Do People Test Ansible Playbooks?
 From experience, we’ve typically seen a ‘whole box and dice’ approach.
 Take a ‘test everything’ approach, including unit and functional testing.
 Use tools like ServerSpec, Goss, Ansible, etc.
 Increased cost due to duplication of effort and maintenance of tests.
Test the outcome, not the
implementation.
Michael De Haan, Creator of Ansible
The Golden Rule of Testing in Ansible
 Ansible’s modules are designed to “achieve state” and “fail fast”.
 It is not necessary to test that services are started, packages are installed,
etc.
 Try to keep it simple.
“Any intelligent fool can make things bigger, more complex, and more violent. It
takes a touch of genius—and a lot of courage to move in the opposite direction.”
- E. F. Schumacher in “Small Is Beautiful”.
A Suggested Playbook Testing Pattern
For a playbook, have the ability to apply the playbook (and run the embedded
functional tests) against a virtual environment.
$ make USE_VM=true clean setup <--- Applies playbook to virtual environment
$ make USE_VM=true test <--- Runs functional test against
virtual environment
For a playbook, have embedded functional tests which can be toggled on and off by
feature flags or tags.
$ make clean setup <--- Applies playbook to environment
$ make test <--- Runs functional tests against environment
A Suggested Role Testing Pattern
The objectives of testing a role should be similar to that of a playbook:
 Is the YAML syntax of my role correct?
 Is the role built in an idempotent way?
 Does the role run through all tests without failing?
For a role, have the ability to apply the role and run functional tests against a
virtual environment.
$ make clean setup test <--- Applies role to a VM and runs functional tests
I Like Unit Tests! I Do! I Like Them, Sam I Am
If you want to unit test your Ansible playbooks or roles, try to keep these
principles in mind:
 The unit tests should only test a single associated task.
 The unit tests should be tightly associated with the related task that they are
testing.
 The unit tests are read-only in nature.
 The use of tags allows selected execution of the unit tests.
Is Your Role Worthy Of A Gold Star?
The maintainers of Ansible Galaxy are discussing automated manners to give a gold
star or other indicator to a role that meets certain criteria which are an indicator of
quality.
 Documentation should exist
 Documentation should include a definition of all default variables
 travis.yml should include asserts and be more than just a syntax check
 Check that the role does not call deprecated modules
 Check that the role does not use deprecated syntax
 Check that all variables have a default or are included in vars/main.yml
 Role repo should not include issues that have been open for a long time
Playbook and Role Testing Using Tox
Meet Tox: https://tox.readthedocs.io/en/latest/
$ pip install tox
“tox aims to automate and standardize testing in Python.”
 Allows for testing playbooks/roles against different versions of ansible.
 Allows for testing playbooks/roles against different versions of python.
 Works on Windows and Linux.
Role Testing Using Molecule
Meet Molecule: http://molecule.readthedocs.io/
$ pip install molecule
“Molecule is designed to aid in the development and testing of Ansible roles..."
 Supports multiple drivers, including Docker, Vagrant and OpenStack.
 Supports multiple providers, including Virtualbox, Libvrt, Parallels, and VMware
Fusion.
 Supports multiple verifiers, including Testinfra, Serverspec, Goss
 Also includes syntax validation and ansible-lint checking.
Molecule is CI Friendly!
Molecule provides a number of commands which are ‘continuous integration’ friendly.
$ molecule create <--- Creates instance(s)
$ molecule converge <--- Apply ansible playbook to instance(s)
$ molecule idempotence <--- Validates idempotence of role
$ molecule verify <--- Runs tests against converged instance(s)
$ molecule destroy <--- Destroys instance(s)
By default, all of these commands are executed when running:
$ molecule test.
Miscellaneous Tips and Tricks
 Use ‘blocks’ to execute all tests while delaying assertions until the end.
 Use an ‘MVP’ playbook pattern to test ‘is this how you do it?’ type things.
 Use the ping module to check the readiness of hosts.
Summary
 Having one test for a playbook or role is better than zero tests.
 Ask “what would a build agent do?” and model your approach accordingly.
 Test the outcome, not the implementation.
Testing Ansible

Testing Ansible

  • 1.
    Anth Courtney, SolutionArchitect, anth.courtney@cmdsolutions.com.au Adam Durbin, Director of Strategy and Architecture, adam.durbin@cmdsolutions.com.au A helicopter ride over the Ansible testing landscape
  • 2.
    Overview Why should wetest our Ansible artifacts? What can we test? What should we test? How should we test?
  • 3.
    But first, acontrived service!
  • 4.
    Testing Our Playbook Wecan immediately start to test our playbook, without the need to write large amounts of additional code. Test the syntax of our playbook and roles Evaluate the repeatability of our playbooks and roles. Validate configuration files generated by jinja2 templates.
  • 5.
    Syntax Checking Playbooksand Roles What –syntax-check does do  Validates the syntax of a playbook  Validates the syntax of any included roles.  Validates presence of any other includes. i.e. other playbooks, vars_files.  Validates that tasks/modules specified within a playbook or role are valid. What –syntax-check doesn't do  Doesn’t execute a playbook.  Doesn’t test the validity of parameters to tasks.  Doesn’t test for the existence of undefined variables.  Doesn’t test the ability to sudo/become as defined.  Doesn’t test the validity of any in- line jinja2. $ ansible-playbook –syntax-check <playbook>
  • 6.
    "Ask yourself, what woulda build agent do?" Rob Shand, General DevOps Legend Typically, we want to:  Create an environment or workspace for execution of our playbook.  Prepare the environment or workspace for execution of the playbook.  Apply the playbook against the target environment.  Verify the playbook execution.  Destroy the environment or workspace.
  • 7.
    Testing Idempotency An operationis idempotent if the result of performing it once is exactly the same as the result of performing it repeatedly without any intervening actions. First run of playbook: PLAY RECAP ********************************************************************* default : ok=7 changed=10 unreachable=0 failed=0 Nth run of playbook: PLAY RECAP ********************************************************************* default : ok=17 changed=0 unreachable=0 failed=0
  • 8.
    Non-Idempotent Playbooks andRoles Non-idempotent playbooks and roles can be indicative of:  A resource being changed multiple times within the same playbook run.  A conditional that isn’t being evaluated correctly.  Tasks not being properly configured for idempotence. - name: Install custom application - non-idempotent approach command: /tmp/application-1.0.0/bin/install --dest /opt/application - name: Install custom application - idempotent approach command: /tmp/application-1.0.0/bin/install --dest /opt/application args: creates: /opt/application
  • 9.
    Use Case: UsingIdempotency, Check-Mode and Diffs for Security
  • 10.
    Testing Jinja2 Templates Forcopy and template tasks, Ansible provides the ability to perform validation before copying into place. - name: Create nginx configuration template: src: "etc/nginx/nginx.conf.j2" dest: "/etc/nginx/nginx.conf" validate: "nginx -t -c %s" notify: Reload nginx
  • 11.
    Testing Jinja2 Templates Forcopy and template tasks, Ansible provides the ability to perform validation before copying into place. - name: Copy nginx modular configuration template: src: "etc/nginx/conf.d/modules.conf.j2" dest: "/etc/nginx/conf.d/modules.conf" validate: "echo 'http { include '%s'; }' > /tmp/nginx.conf && nginx -t -c /tmp/nginx.conf" notify: Reload nginx
  • 12.
    Batteries Included! Ansible includesa number of modules which are test-orientated:  assert  fail  stat  wait_for  uri  command
  • 13.
    Built-in Test Module:assert - name: Check ansible version is suitable for this playbook assert: that: ansible_version.full | version_compare('2.1', '>=') msg: "Ansible version 2.1 or greater is required for this playbook"
  • 14.
    Built-in Module: uri -name: Verify that the nginx status page is reachable uri: url: "http://{{ inventory_hostname }}/status" register: result until: result.status == 200 retries: 60 delay: 1
  • 15.
    Using 'assert' WithMultiple Conditions - name: Make authenticated request uri: url: "http://localhost/api/users/anth" method: GET force_basic_auth: yes user: "admin" password: "admin123" register: api_request - name: Validate that api request response contains required information assert: that: - api_request.json.name is defined - api_request.json.username == 'anth' - api_request.json.email | search("@")
  • 16.
    Built-in Test Module:stat - name: Determine if ssh private key exists stat: path: /home/ec2-user/.ssh/id_rsa register: ssh_private_key - name: Validate that ssh private key exists and has correct mode assert: that: - ssh_private_key.stat.exists - ssh_private_key.stat.mode == "0640" msg: "ssh private key does not exist or has wrong mode"
  • 17.
    Built-in Test Module:fail  Used to ‘bail out’ of the execution of a playbook when a certain condition is met.  Uses when to provide the condition for termination. - name: Check ansible version is suitable for this playbook fail: msg: "Ansible version 2.1 or greater is required for this playbook" when: ansible_version.full | version_compare('2.1', '<')
  • 18.
    Built-in Module: wait_for Can be used to wait for a port to become available  Can be used to wait for a file state to change or for a string/regex to appear  Can also be used to wait for active connections to be closed before continuing - name: Verify that cron ran configured @reboot jobs wait_for: path: "/var/log/cron" search_regex: "Running @reboot jobs"
  • 19.
    Built-in Parameter: failed_when Provides the ability to define ‘what constitutes failure?’ for a task. - name: Run command to test status of application command: /opt/cmd/bin/test_application.sh register: command_result failed_when: "'FAILED'" in command_result.stdout
  • 20.
    How Do PeopleTest Ansible Playbooks?  From experience, we’ve typically seen a ‘whole box and dice’ approach.  Take a ‘test everything’ approach, including unit and functional testing.  Use tools like ServerSpec, Goss, Ansible, etc.  Increased cost due to duplication of effort and maintenance of tests.
  • 21.
    Test the outcome,not the implementation. Michael De Haan, Creator of Ansible
  • 22.
    The Golden Ruleof Testing in Ansible  Ansible’s modules are designed to “achieve state” and “fail fast”.  It is not necessary to test that services are started, packages are installed, etc.  Try to keep it simple. “Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius—and a lot of courage to move in the opposite direction.” - E. F. Schumacher in “Small Is Beautiful”.
  • 23.
    A Suggested PlaybookTesting Pattern For a playbook, have the ability to apply the playbook (and run the embedded functional tests) against a virtual environment. $ make USE_VM=true clean setup <--- Applies playbook to virtual environment $ make USE_VM=true test <--- Runs functional test against virtual environment For a playbook, have embedded functional tests which can be toggled on and off by feature flags or tags. $ make clean setup <--- Applies playbook to environment $ make test <--- Runs functional tests against environment
  • 24.
    A Suggested RoleTesting Pattern The objectives of testing a role should be similar to that of a playbook:  Is the YAML syntax of my role correct?  Is the role built in an idempotent way?  Does the role run through all tests without failing? For a role, have the ability to apply the role and run functional tests against a virtual environment. $ make clean setup test <--- Applies role to a VM and runs functional tests
  • 25.
    I Like UnitTests! I Do! I Like Them, Sam I Am If you want to unit test your Ansible playbooks or roles, try to keep these principles in mind:  The unit tests should only test a single associated task.  The unit tests should be tightly associated with the related task that they are testing.  The unit tests are read-only in nature.  The use of tags allows selected execution of the unit tests.
  • 26.
    Is Your RoleWorthy Of A Gold Star? The maintainers of Ansible Galaxy are discussing automated manners to give a gold star or other indicator to a role that meets certain criteria which are an indicator of quality.  Documentation should exist  Documentation should include a definition of all default variables  travis.yml should include asserts and be more than just a syntax check  Check that the role does not call deprecated modules  Check that the role does not use deprecated syntax  Check that all variables have a default or are included in vars/main.yml  Role repo should not include issues that have been open for a long time
  • 27.
    Playbook and RoleTesting Using Tox Meet Tox: https://tox.readthedocs.io/en/latest/ $ pip install tox “tox aims to automate and standardize testing in Python.”  Allows for testing playbooks/roles against different versions of ansible.  Allows for testing playbooks/roles against different versions of python.  Works on Windows and Linux.
  • 28.
    Role Testing UsingMolecule Meet Molecule: http://molecule.readthedocs.io/ $ pip install molecule “Molecule is designed to aid in the development and testing of Ansible roles..."  Supports multiple drivers, including Docker, Vagrant and OpenStack.  Supports multiple providers, including Virtualbox, Libvrt, Parallels, and VMware Fusion.  Supports multiple verifiers, including Testinfra, Serverspec, Goss  Also includes syntax validation and ansible-lint checking.
  • 29.
    Molecule is CIFriendly! Molecule provides a number of commands which are ‘continuous integration’ friendly. $ molecule create <--- Creates instance(s) $ molecule converge <--- Apply ansible playbook to instance(s) $ molecule idempotence <--- Validates idempotence of role $ molecule verify <--- Runs tests against converged instance(s) $ molecule destroy <--- Destroys instance(s) By default, all of these commands are executed when running: $ molecule test.
  • 30.
    Miscellaneous Tips andTricks  Use ‘blocks’ to execute all tests while delaying assertions until the end.  Use an ‘MVP’ playbook pattern to test ‘is this how you do it?’ type things.  Use the ping module to check the readiness of hosts.
  • 31.
    Summary  Having onetest for a playbook or role is better than zero tests.  Ask “what would a build agent do?” and model your approach accordingly.  Test the outcome, not the implementation.

Editor's Notes

  • #4 DEMO 1 : Broken Syntax cd /vagrant/setup/ansible-meetup-demo ansible-playbook -i inventory site.yml   <make breaking change> vi site.yml   FIX   ansible-playbook -i inventory site.yml
  • #6 DEMO 2 : Syntax checks   code ~/cmd-pres/ansible-meetup-examples/tests/syntax-check/site.yml cd /vagrant/tests/syntax-check ansible-playbook --syntax-check site.yml   code ~/cmd-pres/ansible-meetup-examples/tests/syntax-check/bad-playbook.yml   ansible-playbook --syntax-check bad-playbook.yml   code ~/cmd-pres/ansible-meetup-examples/tests/syntax-check/bad-role.yml   ansible-playbook --syntax-check bad-role.yml
  • #7 DEMO 3 : make file with syntax   code ~/cmd-pres/ansible-meetup-examples/tests/syntax-check/Makefile
  • #8 DEMO 4 : idempotency conflict   cd /vagrant/tests/idempotency/conflict ansible-playbook -i inventory site.yml   ansible-playbook -i inventory site.yml     ansible-playbook -i inventory -t hardening site.yml ansible-playbook -i inventory -t hardening site.yml
  • #9 DEMO 5 : Using idempotency for confident state testing   code ~/cmd-pres/ansible-meetup-examples/tests/idempotency/auditd/Makefile    
  • #10 cd /vagrant/tests/idempotency/auditd make prepare converge test <Manual breaking change> sudo vi /etc/audit/auditd.conf Change log_file = /var/log/audit/audit.log to log_file = /dev/null make test
  • #21 DEMO 6 : Testing playbooks     code ~/cmd-pres/ansible-meetup-examples/tests/playbook/ansible/tests.yml   ansible-playbook -i inventory tests.yml   <Using RAKE>   code ~/cmd-pres/ansible-meetup-examples/tests/playbook/serverspec/spec/localhost/playbook_spec.rb   cd /vagrant/tests/playbook/serverspec sudo /root/.gem/ruby/bin/rake -T sudo /root/.gem/ruby/bin/rake spec DEMO 7 : GOSS:   cd /vagrant/tests/playbook/goss goss add package nginx goss add service nginx goss add port 80 goss add http http://localhost goss validate   cat goss.yaml     goss autoadd sshd goss validate   cat goss.yaml
  • #24 DEMO 9 : cd /vagrant/setup/ansible-meetup-demo ls code ~/cmd-pres/ansible-meetup-examples/setup/ansible-meetup-demo/site.yml code ~/cmd-pres/ansible-meetup-examples/setup/ansible-meetup-demo/tests.yml code ~/cmd-pres/ansible-meetup-examples/setup/ansible-meetup-demo/Makefile DEMO 10 :   code ~/cmd-pres/ansible-meetup-examples/setup/ansible-meetup-demo-rolling-upgrade/site.yml
  • #25 ON HOST MACHINE!! DEMO 11:   code ~/cmd-pres/ansible-role-nginx/tests/site.yml   code ~/cmd-pres/ansible-role-nginx/tests/test.yml code ~/cmd-pres/ansible-role-nginx/Makefile code ~/cmd-pres/ansible-role-nginx/tasks/check-version.yml   code ~/cmd-pres/ansible-role-nginx/tasks/monit.yml cd ~/cmd-pres/ansible-role-nginx   make clean setup test
  • #26 ON VAGRANT!! DEMO 12 : Unit tests code ~/cmd-pres/ansible-meetup-examples/tests/unit-tests/ansible-role-nginx/tasks/main.yml   cd /vagrant/tests/unit-tests code ~/cmd-pres/ansible-meetup-examples/tests/unit-tests/ansible-role-nginx-v2/tasks/main.yml
  • #27 DEMO 13 : Testing roles   cd /vagrant/tests/deprecated-syntax ansible-lint -L   ansible-lint -t ANSIBLE0008,ANSIBLE0015 site.yml   code ~/cmd-pres/ansible-meetup-examples/tests/deprecated-syntax/Makefile
  • #28 ON HOST MACHINE!! DEMO 13 : Tox   cd ~/cmd-pres/ansible-meetup-demo code ~/cmd-pres/ansible-meetup-demo/tox.ini tox -l   tox -e py27-ansible21   tox -e py27-ansible22
  • #29 ON HOST Machine!! DEMO 14 : Molecule   cd /tmp/ molecule init --help   molecule init --role ansible-role-ntp --verifier serverspec   cd ansible-role-ntp vi molecule.yml   Name and box: centos/7   vi tasks/main.yml   - name: Install ntp package: name: ntp state: present   - name: Start ntpd service service: name: ntpd state: started enabled: true   describe command('ntpstat') do its(:exit_status) { should eq 0 } end   molecule test
  • #31 ON VAGRANT BOX!! DEMO 15 :   code ~/cmd-pres/ansible-meetup-examples/setup/ansible-meetup-demo-block-tests/tests.yml   MVP code ~/cmd-pres/ansible-meetup-examples/tests/mvp/test.yml DEMO 16 : PING   code ~/cmd-pres/ansible-meetup-examples/tests/ping/playbook.yml   cd /vagrant/tests/ping ansible-playbook -i inventory playbook.yml     code ~/cmd-pres/ansible-meetup-examples/tests/ping/playbook2.yml   ansible-playbook -i inventory playbook2.yml