Reuse your tdd skills to build an application cluster with vagrant and chef: iteration 2

This is the 2nd iteration of our attempt to build an application cluster.

So far we’ve got familiar with vagrant, chef and its plugins. We’ve provisioned our machine with the ‘curl’ package.

The goal of this iteration is to use a more complex cookbook, configure it and test it

There are many testing tools in Ruby. You thus benefit from the whole testing ecosystem. But you’d better use a tool that has the simplest integration with Chef, and it’s even better if it is expressive. minitest-chef-handler was the clear winner. Minitest is an expressive testing framework that allows you 2 syntax: specs or classical bdd. It was developed by the Seattle ruby user group. David Calavera built custom chef assertions (suitable to configuration management) on top of minitest. Bryan MacLellan went one step further: he encapsulated minitest-chef-handler into a cookbook embracing chef execution lifecycle. You can run that cookbook like any other often after all cookbooks have completed. The chef execution will fail if the test doesn’t pass and of course the chef context is available in your test. Exactly what I was looking for: how convenient! OSS I love you…

Setting up minitest-handler-cookbook is like any other cookbook:

  • add the cookbook location to Berksfile,
    cookbook 'minitest-handler', :git => 'https://github.com/btm/minitest-handler-cookbook.git'
    
  • use the cookbook in the provision section of your Vagrantfile (remove your old implementation to note failure)
    config.vm.provision 'chef_solo' do |chef|
      chef.add_recipe 'minitest-handler'
    end
    

You can run vagrant to validate minitest configuration

$ vagrant reload
[2013-08-23T13:14:06-03:00] INFO: Chef Run complete in 30.675623573 seconds
[2013-08-23T13:14:06-03:00] INFO: Running report handlers
Run options: -v --seed 55076

# Running tests:

Finished tests in 0.001199s, 0.0000 tests/s, 0.0000 assertions/s.

0 tests, 0 assertions, 0 failures, 0 errors, 0 skips
[2013-08-23T13:14:06-03:00] INFO: Report handlers complete

We might think that we can juste write some xx_test.rb and minitest will find them. Sadly minitest-handler-cookbook only works with cookbooks. You fisrt have to know a bit about cookbooks. So we’ll first create a minimal but functionnal cookbook. We’ll come back later on the topic.
A cookbook is a catalog that groups related, coherent and self-contained sets of instructions (recipes).
For example the mysql cookbook is composed of independent recipes: install mysql server recipe, install mysql client recipe, create a user recipe, create a database recipe.
Every cookbook has a default recipe. So we’ll create one with a default recipe wich will do nothing for now, write our test, note failure then make it pass.
The convention recommends that custom cookbooks should be placed in a folder named site-coobooks. Inside that directory you can create your cookbook say myapp (because this cookbook is about creating/provisioning the ‘myapp’ cluster)

$ mkdir -p site-cookbooks/myapp/{recipes,files}
$ mkdir -p site-cookbooks/myapp/files/default/test
$ touch site-cookbooks/myapp/metadata.rb
$ touch site-cookbooks/myapp/README.md
$ touch site-cookbooks/myapp/recipes/default.rb
$ touch site-cookbooks/myapp/files/default/test/default_test.rb

Note that the test file is named after the recipe name: the test file of the recipe foo.rb would be foo_test.rb. You can customize that behaviour but it’s often good practice to follow the convention.

Well now your test in default_test.rb:

require 'minitest/spec'

describe_recipe 'myapp::default' do

  it "should install the curl package" do
    package("curl").must_be_installed
  end

 end

The first required file in a cookbook is the metadata file:

$ sudo vi site-cookbooks/myapp/metadata.rb

 

name             'myapp'
maintainer       'Louis Gueye'
maintainer_email 'louis.gueye@gmail.com'
license          'Apache 2.0'
description      'Installs/Configures my app'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'

recipe 'myapp::default', 'Configures my app'

depends 'curl'

Don’t forget to tell chef where your cookbook is and to call your cookbook in Vagrantfile:

config.vm.provision 'chef_solo' do |chef|
  chef.cookbooks_path = %w('cookbooks','site-cookbooks')
  chef.add_recipe 'myapp' # short form for 'myapp::default' which is the actual recipe
  chef.add_recipe 'minitest-handler'
end

And remember: any cookbook sould be uploaded to your server, so referencing your cookbook in Berksfile is mandatory

cookbook 'myapp', :path => './site-cookbooks/myapp'

Note failure:

# Remove previous curl install
$ vagrant ssh -c 'sudo aptitude purge curl'
$ vagrant reload
[2013-08-26T06:02:25-03:00] INFO: Chef Run complete in 0.227265756 seconds
[2013-08-26T06:02:25-03:00] INFO: Running report handlers
Run options: -v --seed 35660

# Running tests:

recipe::myapp::default#test_0001_should install the curl package =
0.16 s = F

Finished tests in 0.161410s, 6.1954 tests/s, 6.1954 assertions/s.

  1) Failure:
recipe::myapp::default#test_0001_should install the curl package [/var/chef/minitest/myapp/default_test.rb:6]:
Expected package 'curl' to be installed

Finally, make it pass:

$ echo "include_recipe 'curl'" >> site-cookbooks/myapp/recipes/default.rb
$ vagrant reload
[2013-08-26T06:04:45-03:00] INFO: Running report handlers
Run options: -v --seed 4601

# Running tests:

recipe::myapp::default#test_0001_should install the curl package =
0.03 s = .

Finished tests in 0.030814s, 32.4528 tests/s, 32.4528 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
[2013-08-26T06:04:45-03:00] INFO: Report handlers complete

Nice: full tdd cycle for package install. We’re armed to test/code/refactor chef cookbooks!

In the next post we’ll write a cookbook for the whole cluster in tdd.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s