Skip to content

How to use toft gem and cucumber to test chef cookbooks

exceedhl edited this page May 16, 2012 · 13 revisions

Prepare a lxc host machine

First you need a linux machine as a development environment because toft use lxc. Here we use vagrant to create a ubuntu natty virtual machine(other linux distribution should work too, but so far only ubuntu natty is fully tested). Follow the instructions to setup one.

Install toft gem

Then add gem "toft", "~> 0.0.11" to your Gemfile if you are using bundler, or install gem directly:

gem install toft

Note that if you are using virtual machine like me then the development process will probably be writing code on your Mac and then run your cucumber features in your vagrant ubuntu box. This is actually not so retarding as it seems to be since vagrant’s NFS share folder works pretty well in this situation. Suppose your code dir structure is like this:

├── features
├── cookbooks
├── roles
├── Gemfile
└── Vagrantfile

You can add this to your Vagrantfile to share this dir between your Mac and virtual machine(see here to know more):

config.vm.network "33.33.33.10"
config.vm.share_folder("v-root1", "/home/vagrant/code", ".", :nfs => true)

This will map your current dir to /home/vagrant/code in your virtual machine, and to run cucumber features in your virtual machine you have to run bundle install in it.

How to use cucumber with toft

Check the toft features to see how to use toft with cucumber. Here I will explain the code a bit.

Here is an example of env.rb:

require 'rspec/expectations'
require 'toft'

CHEF_FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/chef'
PUPPET_FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/puppet'

Toft.cookbook_path = CHEF_FIXTURE_PATH + '/cookbooks'
Toft.role_path = CHEF_FIXTURE_PATH + '/roles'
Toft.data_bag_path = CHEF_FIXTURE_PATH + '/data_bags'
Toft.manifest_path = PUPPET_FIXTURE_PATH

World(Toft)

include Toft

# currently support "natty", "lucid" and "centos-6"
n1 = create_node "n1", {:ip => "192.168.20.2", :type => "centos-6"}  # default type is natty

Before do
  @n1 = n1
end

at_exit do
  n1.destroy
end

Toft provides helper methods in the Toft module so you have to include them in your world with World(Toft), and in the example we create a ubuntu natty node called “n1” to be shared by all features. Note that your cookbook_path and role_path can be configured so you can change the location of cookbooks and roles to be tested.

Note that if you are in a 64bit environment, create_node will create 64bit lxc containers, and create 32bit lxc containers in 32bit environments.

Common Pitfalls and Errors

If you receive the “Remote machine not responding” error on starting your container, there is an easy fix

[12] pry(main)> n1.start
RuntimeError: Remote machine not responding.
from /usr/lib/ruby/gems/1.8/gems/toft-0.0.6/lib/toft/node.rb:170:in `wait_remote_host_reachable'

Simply run lxc-prepare-host and try again. This command will restart your local bind9 and dhcpd servers with the proper configuration

Toft API

Toft provides helpers to manage nodes, run scripts and check environment status, for example:

Manage nodes:

Create a node with static ip

n1 = create_node "n1", {:ip => "192.168.20.2", :type => "centos-6"}

The default node type is “natty”, possible values are “lucid”, “natty” and “centos-6”.

Create a node using dhcp

n1 = create_node "n1", :type => "centos-6"

Get IP of the node

n1.ip # => ip of n1, useful when you use dhcp

Find a node

find("n1") # should be same as n1

Find all nodes

find(:all) # an array containing all existing nodes

Start and stop node

n1.start
n1.running? # => true
n1.stop
n1.running? # => false

Destroy node

n1.destroy

Add and remove cname to nodes

n1.add_cname "foo"
n1.remove_cname "foo"

Actions

Remove files and dirs

n1.rm "/tmp/*"

Run chef recipes with or without overriding attributes

n1.run_chef "recipe[test::one]" # run a single rcipe

Run multiple recipes or roles

n1.run_chef ["recipe[test::one]", "recipe[test::two]", "role[some_role]"] #run multiple

Override attributes

n1.run_chef "recipe[test::one]", Toft::ChefAttributes.new(table) # run a chef recipe with override attributes
# here table is a cucumber table like:
|key|value|
|one|one|
|two.one|two_one|
|two.two|two_two|
|three|three|

Run puppet manifests

n1.run_puppet "manifests/test_module.pp"

Run puppet manifests with customized config files

n1.run_puppet(run_list, :conf_file => "puppet_modules.conf")

Run ssh commands

n1.run_ssh "netstat -nr"  # run a shell command through ssh

You can also access the ssh command output by passing a block to run_ssh

result = n1.run_ssh "pwd" do |output|
  output.should include("some_dir") # the output here is guaranteed to be the whole output of the command execution
end
# result.stdout and result.stderr contains complete output

If there is the ssh command exit with a non-zero status, CommandExecutionError will be raised, you can catch it and access the stdout and stderr:

begin
  r = find(node).run_ssh(cmd)
rescue CommandExecutionError => e
  e.stdout.should include(s)
end

Checkers

n1 = find("n1")
n1.file("/tmp").exist? # true
n1.file("/tmp").mode # 1777
n1.file("/tmp").owner # root
n1.file("/tmp").group # root
n1.file("/tmp").filetype # directory

Again, the toft source code is tested used cucumber, so by checking feature you should see all tests against toft api and usages of its features.