Testing salt formulas and models with kitchen-salt
TL;DR
An overview how to implement salt-formula test's with kitchen-salt.
Overview
In this article I will talk about writing and testing https://github.com/salt-formulas[salt-formulas]. An curated collection of formulas behind salt-formulas community. The concepts however will fit to any salt formula.
If you are interested in https://github.com/salt-formulas[salt-formulas] read more at: https://salt-formulas.readthedocs.io/en/latest>[readthedocs salt-formulas].
To test formulas we will use https://github.com/saltstack/kitchen-salt[kitchen-salt] framework.
This particular article extends already published documentation https://salt-formulas.readthedocs.io/en/latest/develop/index.html#testing[salt-formulas/testing] however the additional posts will cover advanced configurations, latest features, verifiers as well as testing salt models/pillars.
Formula repository
To meet required formula repository structure, new formulas can be generated by using this cookiecutter template <https://github.com/salt-formulas/salt-formulas/tree/master/cookiecutter/salt-formula>_.
Mind there is also a script to update existing formula repo to kitchen initial kitchen setup.
Salt Formula testing
Each formula contains Makefile with at least test target. Under tests directory are located resources for test execution.
Test target executes "smoke test" implemented by tests/run_tests.sh <https://github.com/salt-formulas/salt-formulas/blob/master/cookiecutter/salt-formula/%7B%7Bcookiecutter.service_name%7D%7D/tests/run_tests.sh>_ capable to fetch dependencies in python virtual environment by executing salt-call state.show_sls with provided tests/pillar data.
The purpose of the smoke test is to find syntax, typo issues and verify example pillar data against the formula.
Initial content of tests folder contains test pillars and a run_tests.sh as generated by cookiecutter <https://github.com/salt-formulas/salt-formulas/tree/master/cookiecutter/salt-formula>_.
tests ├── pillar │ └── client_single.sls │ └── server_single.sls └── run_tests.sh
Create or update pillars in tests/pillar/\*.sls with test data.
Formula testing with Test Kitchen
Test Kitchen with "now" SaltStack official kitchen-salt provisioner plugin may be used for local development as well as CI scenario.
Test Kitchen is a test harness tool to execute your configured code on one or more platforms in isolation. There is a .kitchen.yml in main directory that defines platforms to be tested and suites to execute on them.
Kitchen CI can spin instances locally or remote, based on used driver. For example .kitchen.yml may define a docker, openstack or vagrant driver.
For more, explore it's rich ecosystem of supported drivers/provisioners/verifiers/...
How it works
Kitchen spin an instances in Docker, Vagrant, OpenStack environment, etc. based on configured driver. Instance is configured as salt minion, where the configuration is defined by .kitchen.yml and tests/pillar/*.sls
Override your specific needs with .kitchen.<backend|local>.yml that you may load as: KITCHEN_LOCAL_YAML=.kitchen.<driver>.yml kitchen <action> <suite>.
Example: KITCHEN_LOCAL_YAML=.kitchen.local kitchen verify server-ubuntu-1404 -t tests/integration.
Test Kitchen then allows you execute several action to perform your testing under configured conditions:
- create, provision an test instance (VM, container)
- converge, run a provisioner driver (shell script, kitchen-salt, ...)
- verify, run a verification tool (inspec, test-infra, ...)
- destroy
Usage
A listing of scenarios to be executed:
$ kitchen list Instance Driver Provisioner Verifier Transport Last Action client-single-ubuntu-1404 Docker SaltSolo Inspec Ssh <Not Created> client-single-ubuntu-1604 Docker SaltSolo Inspec Ssh <Not Created> client-single-centos-71 Docker SaltSolo Inspec Ssh <Not Created>
Out of the box kitchen uses special verifier called Busser <https://github.com/test-kitchen/busser>_ used to recognize verification driver automatically by scripts/configs implemented in <repo>/test/integration. Busser then install, setup verifier(s) and execute.
As the verifier you can use InSpec, test-infra, Shell, Bats, ...). You specify directly
Example workflow:
# list instances and status kitchen list # manually execute integration tests kitchen [test || [create|converge|verify|exec|login|destroy|...]] [instance] -t tests/integration # use with provided Makefile (ie: within CI pipeline) make kitchen
Verifying deployment
There is couple of verifier plugins that are shipped with Test Kitchen. They allow to run simple bash scripts and checking it's exit codes to run specific purpose based frameworks.
The Busser Verifier goes with test-kitchen by default. It is used to setup and run tests implemented in <repo>/test/integration. It guess and installs the particular driver to tested instance. By default InSpec is expected.
You may avoid to install busser framework if you configure specific verifier in .kitchen.yml:
verifier:
name: inspec
For default Inspec Verifier implement your scripts directly in <repo>/test/integration/<suite> directory with _spec.rb suffix.
If you would to write another verification scripts than InSpec store them in <repo>/tests/integration/<suite>/<verifier>. Busser <https://github.com/test-kitchen/busser> is a test setup and execution framework under test kitchen.
Implement integration tests under <repo>/tests/integration/<suite>/<verifier> directory with _spec.<verifier suffix> filename suffix.
InSpec
InSpec is native validation framework for Test Kitchen and as such don't require usage of <verifier> folder. Thus the tests may by stored directly under <repo>/tests/integration/<suite>
Additional resources.
Example verification scripts under tests/integration folder of the formula:
tests ├── integration │ ├── default │ │ └── default_testcase_spec.rb # Written in InSpec │ ├── backupmx │ │ └── serverspec # <Verifier framework> │ │ └── backupmx_spec.rb # Written in ServerSpec │ ├── helpers │ │ └── serverspec │ │ └── spec_helper.rb │ ├── relay │ │ └── serverspec │ │ └── relay_spec.rb │ └── server │ └── serverspec │ ├── aliases_spec.rb │ └── server_spec.rb ├── pillar │ ├── backupmx.sls │ ├── relay.sls │ └── server.sls └── run_tests.sh
Requirements
Use latest stable kitchen-salt and kitchen-test. Supported version of kitchen-salt is available at kitchen-salt <https://github.com/salt-formulas/kitchen-salt> fork.
TL;DR
First you have to install ruby package manager gem.
Install required gems:
# Ruby side:
gem install <gem name from the list below>
# Isolated w/Bundler
gem install bundler
cat > Gemfile <-EOF
source 'https://rubygems.org'
gem 'rake'
gem 'test-kitchen'
gem 'kitchen-docker'
gem 'kitchen-inspec'
gem 'inspec'
gem 'kitchen-salt', :git => 'https://github.com/salt-formulas/kitchen-salt.git'
EOF
bundle install [--path $PWD/.vendor/bundle]
# use with preffix 'bundle kitchen':
# bundle exec kitchen list
Create aliases:
cat > ~/.${SHELL}rc <-EOF
alias bk='nocorrect bundle exec kitchen'
alias kl='nocorrect bundle exec kitchen list'
EOF
See http://kitchen.ci/ for more details.
Install procedure
One may be satisfied installing ruby and gems system-wide right from OS package manager.
If you are an ruby/chef developer you will probably want to use ChefDK <https://downloads.chef.io/chefdk>.
For advanced users or the sake of complex environments you may use rbenv for user side ruby installation.
An example steps to install user side ruby and prerequisites:
# Use package manager to install rbenv and ruby-build sudo apt-get install rbenv ruby-build # list all available versions: rbenv install -l # install a Ruby version of your choice or pick latest rbenv install $(rbenv install -l|grep -E '^[ ]*[0-9]\.[0-9]+'|tail -1) # activate rbenv local 2.4.0 # it's usually a good idea to update rubygems first rbenv exec gem update --system # install test kitchen rbenv exec gem install bundler rbenv exec gem install test-kitchen
Continue with the optional Gemfile in the formula main directory to fetch fine tuned dependencies. If you use Gemfile and Bundler for local dependencies prepend all command with rbenv exec bundler exec and possibly set an alias in your ~/.bashrc, etc.
cat >> ~/.${SHELL}rc <<-EOF
alias rk="rbenv exec kitchen"
alias bk="rbenv exec bundler exec kitchen"
EOF
With such alias set, you should be able to execute rbenv exec bundler exec make kitchen and see test results.
Sample configs
For advanced configs have a look at .kitchen*.yml examples in cookiecutter template <https://github.com/salt-formulas/salt-formulas/tree/master/cookiecutter/salt-formula/%7B%7Bcookiecutter.service_name%7D%7D>_.
.kitchen.yml
---
driver:
name: docker
hostname: opencontrail
use_sudo: true
provisioner:
name: salt_solo
salt_install: bootstrap
salt_bootstrap_url: https://bootstrap.saltstack.com
salt_version: latest
require_chef: false
log_level: error
formula: opencontrail
grains:
noservices: True
dependencies:
- name: linux
repo: git
source: https://github.com/salt-formulas/salt-formula-linux
state_top:
base:
"*":
- linux
- opencontrail
pillars:
top.sls:
base:
"*":
- linux_repo_openstack
- linux_repo_cassandra
- linux_repo_opencontrail
- linux_repo_mos
- linux
- opencontrail
- opencontrail_juniper
linux.sls:
linux:
system:
enabled: true
name: opencontrail
opencontrail_juniper.sls: {}
pillars-from-files:
linux_repo_mos.sls: tests/pillar/repo_mos8.sls
linux_repo_cassandra.sls: tests/pillar/repo_cassandra.sls
linux_repo_openstack.sls: tests/pillar/repo_openstack.sls
linux_repo_opencontrail.sls: tests/pillar/repo_opencontrail.sls
verifier:
name: inspec
sudo: true
platforms:
- name: <%= ENV['PLATFORM'] || 'ubuntu-xenial' %>
driver_config:
image: <%= ENV['PLATFORM'] || 'trevorj/salty-whales:xenial' %>
platform: ubuntu
suites:
- name: <%= ENV['SUITE'] || 'single' %>
provisioner:
pillars-from-files:
opencontrail.sls: tests/pillar/<%= ENV['SUITE'] || 'single' %>.sls
- name: cluster
provisioner:
pillars-from-files:
opencontrail.sls: tests/pillar/cluster.sls
- name: analytics
provisioner:
pillars-from-files:
opencontrail.sls: tests/pillar/analytics.sls
- name: control
provisioner:
pillars-from-files:
opencontrail.sls: tests/pillar/control.sls
- name: vendor-juniper
provisioner:
vendor_repo:
- type: apt
url: http://aptly.local/contrail
key_url: http://aptly.local/public.gpg
components: main
distribution: trusty
pillars-from-files:
opencontrail.sls: tests/pillar/control.sls
pillars:
opencontrail_juniper.sls:
opencontrail:
common:
vendor: juniper
# vim: ft=yaml sw=2 ts=2 sts=2 tw=125
Continous Integration with Travis
Salt-formulas uses Travis CI to run smoke and integration tests. Add .travis.yml:
Sample configs
.travis.yml
sudo: required
services:
- docker
# PREREQUISITES
install:
- pip install PyYAML
- pip install virtualenv
- |
test -e Gemfile || cat <<EOF > Gemfile
source 'https://rubygems.org'
gem 'rake'
gem 'test-kitchen'
gem 'kitchen-docker'
gem 'kitchen-inspec'
gem 'inspec'
gem 'kitchen-salt', :git => 'https://github.com/salt-formulas/kitchen-salt.git
- bundle install
# BUILD MATRIX
env:
- PLATFORM=trevorj/salty-whales:trusty
- PLATFORM=trevorj/salty-whales:xenial
- PLATFORM=trevorj/salty-whales:xenial-2016.3
# SMOKE TEST
before_script:
- set -o pipefail
- make test | tail
# KITCHEN TEST
script:
- bundle exec kitchen test -t tests/integration
# vim: ft=yaml sw=2 ts=2 sts=2 tw=125
Common practices
noservices
At some rare cases execution of given state in the formula is not possible or required. For these cases set grain noservices: True and wrap corresponding code as in the example below:
{%- if not grains.get('noservices', False) %}
mysql_database_{{ database_name }}:
mysql_database.present:
- name: {{ database_name }}
- character_set: {{ database.get('encoding', 'utf8') }}
- connection_user: {{ connection.user }}
- connection_pass: {{ connection.password }}
- connection_charset: {{ connection.charset }}
{%- endif %}
As the mysql database might not be available in the given test environment (travis/docker, etc..).
In .kitchen.yml we set grain noservices: True by default.
grains: noservices: True
** formula dependencies **
Formula dependencies might be specified in <formula repo>/metadata.yml
name: "galera" version: "1.0" source: "https://github.com/salt-formulas/salt-formula-galera" dependencies: - name: mysql source: "https://github.com/salt-formulas/salt-formula-mysql"
While using test-kitchen formula dependencies must be specified in .kitchen.yml as well. Dependencies may be installed from git, spm or even apt repository.
provisioner::
dependencies:
- name: mysql
repo: git
source: https://github.com/salt-formulas/salt-formula-mysql.git
- name: linux
repo: git
source: https://github.com/salt-formulas/salt-formula-linux.git
For convenience kitchen-salt will read metadata.yml of these dependencies and install their dependencies in case you omit them in .kitchen.yml.
** build matrix **
To simplify local CI we ship .kitchen.yml with limited number of platforms. (ie: latest ubuntu as a falback option if no ENV variable PLATFORM is specified)
However this is later extended on Travis CI while using ENV variables in build matrix.
.travis.yml snippet:
# BUILD MATRIX env: - PLATFORM=trevorj/salty-whales:trusty - PLATFORM=trevorj/salty-whales:xenial
.kitchen.yml snippet:
platforms:
- name: <%= ENV['PLATFORM'] || 'ubuntu-xenial' %>
driver_config:
image: <%= ENV['PLATFORM'] || 'trevorj/salty-whales:xenial' %>
platform: ubuntu
= Example forumlas
- https://github.com/salt-formulas/salt-formula-salt[salt-formula-salt]
- https://github.com/salt-formulas/salt-formula-nova[salt-formula-nova]
- https://github.com/salt-formulas/salt-formula-linux[salt-formula-linux]
- https://github.com/salt-formulas/salt-formula-reclass[salt-formula-reclass]
# vim: filetype=asciidoc
Comments
comments powered by Disqus