Testing puppet modules

Puppet Labs logoThere are several steps depending on how much involved the tests are, what parts are tested and, of course, how long it takes to run the tests.

For unit testing we use rspec puppet, and we can check that our manifests and modules compile and contain the expected values. It can be used to test that specific types, classes or definitions are in the compiled catalog and that the parameters math the expectations.

Later on we can do some integration testing starting a new VM with Vagrant and checking that there are no errors in the provisioning, as well as checking that some conditions are met.

For rspec-puppet, PuppetLabs has created a project called puppetlabs_spec_helper that let’s us avoid writing a bunch of boilerplate. A missing point though is that it only allows to use modules for testing from git. If you’re already using librarian-puppet (and you should!) you can easily use the same Puppetfile for deploying modules and to test them. Doing otherwise sounds like a bit of useless testing, you could end with different versions in different development machines, CI server, puppet master,… So just add a call to librarian puppet in your rakefile to populate the rspec-puppet fixtures before running the specs.

Unfortunately rspec-puppet doesn’t work with Puppet 3.0.x and  at least Puppet 3.1.0-rc1 is required. It was a bit of a setback when we moved to Puppet 3 and started using hiera, which is proving to be very useful to have simpler manifests and external data injected for our Maestro installations with Puppet from scratch.

You can also use the same Puppetfile to start Vagrant boxes with the exact same version of the modules. We are using Cucumber and Aruba to execute vagrant, provision the VM with puppet and check several things, like open ports, services up,… but that’s a different story 🙂

Example

In this puppet-for-java-devs project you will find the bits that showcase all these tools integrated. It includes definition of a 3-tier system with Puppet definitions for a postgresql database, tomcat nodes with a war installed and apache nodes fronting them.

Install all required gems

bundle install

Install all Puppet modules with Puppet Librarian

librarian-puppet install

Run the specs with puppet-rspec

bundle exec rake

Start all the vms with Vagrant

vagrant up

Rakefile

require 'bundler'
Bundler.require(:rake)
require 'rake/clean'

require 'puppetlabs_spec_helper/rake_tasks'

CLEAN.include('modules', 'spec/fixtures/', 'doc')
CLOBBER.include('.tmp', '.librarian')

task :librarian_spec_prep do
 sh "librarian-puppet install"
end
task :spec_prep => :librarian_spec_prep

task :default => [:spec]

Puppetfile for librarian-puppet

forge 'http://forge.puppetlabs.com'

mod 'puppetlabs/java', '0.1.6'
mod 'puppetlabs/apache', '0.4.0'
mod 'inkling/postgresql', '0.2.0'
mod 'puppetlabs/firewall', '0.0.4'
mod 'tomcat', :git => 'https://github.com/carlossg/puppet-tomcat.git', :ref => 'centos'
mod 'maestrodev/maven', '1.x'
mod 'stahnma/epel', '0.0.2'
mod 'maestrodev/avahi', '1.x'
mod 'other', :path => 'mymodules/other'

tomcat_spec.rb with rspec-puppet

require 'spec_helper'

describe 'tomcat1.acme.com' do
  let(:facts) { {:osfamily => 'RedHat', :operatingsystem => 'CentOS', :operatingsystemrelease => 6.3} }

  it { should contain_class('java').with_distribution /openjdk/ }

  it "configure webapp" do
    should contain_maven('/srv/tomcat/appfuse/webapps/ROOT.war')
    should contain_maven('/srv/tomcat/appfuse/webapps/ROOT/WEB-INF/lib/postgresql-9.1-901.jdbc4.jar')
  end
end

Vagrantfile

Vagrant::Config.run do |config|
  config.vm.box = "CentOS-6.3-x86_64-minimal"
  config.vm.box_url = "https://dl.dropbox.com/u/7225008/Vagrant/CentOS-6.3-x86_64-minimal.box"

  config.vm.customize ["modifyvm", :id, "--rtcuseutc", "on"] # use UTC clock https://github.com/mitchellh/vagrant/issues/912

  # db server
  config.vm.define :db do |config|
    config.vm.host_name = "db.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "db"] # name for VirtualBox GUI
    config.vm.forward_port 5432, 5432
    config.vm.network :hostonly, "192.168.33.10"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

  # tomcat server
  config.vm.define :tomcat1 do |config|
    config.vm.host_name = "tomcat1.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "tomcat1"] # name for VirtualBox GUI
    config.vm.forward_port 8080, 8081
    config.vm.network :hostonly, "192.168.33.11"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

  # web server
  config.vm.define :www do |config|
    config.vm.host_name = "www.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "www"] # name for VirtualBox GUI
    config.vm.forward_port 80, 8080
    config.vm.network :hostonly, "192.168.33.12"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

end

Automatically download and install VirtualBox guest additions in Vagrant

So, are you already using Vagrant to manage your VirtualBox VMs?

Then you probably have realized already how annoying is to keep the VBox guest additions up to date in your VMs.

Don’t worry, you can update them with just one command or automatically on each start using the Vagrant-vbguest plugin.

Installation

Requires vagrant 0.9.4 or later (including 1.0)

Since vagrant v1.0.0 the prefered installation method for vagrant is using the provided packages or installers.

Therefore if you installed Vagrant as a package (rpm, deb, dmg,…)

vagrant gem install vagrant-vbguest

Or if you installed vagrant using RubyGems (gem install vagrant):

gem install vagrant-vbguest

Usage

By default the plugin will check what version of the guest additions is installed in the VM every time it is started with vagrant start. Note that it won’t be checked when resuming a box.

In any case, it can be disabled in the Vagrantfile

Vagrant::Config.run do |config|
  # set auto_update to false, if do NOT want to check the correct additions
  # version when booting this machine
  config.vbguest.auto_update = false
end

If it detects an outdated version, it will automatically install the matching version from the VirtualBox installation, located at

  • linux : /usr/share/virtualbox/VBoxGuestAdditions.iso
  • Mac : /Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso
  • Windows : %PROGRAMFILES%/Oracle/VirtualBox/VBoxGuestAdditions.iso

The location can be overridden with the iso_path parameter in your Vagrantfile, and can point to a http server

Vagrant::Config.run do |config|
  config.vbguest.iso_path = "#{ENV['HOME']}/Downloads/VBoxGuestAdditions.iso"
  # or
  config.vbguest.iso_path = "http://company.server/VirtualBox/$VBOX_VERSION/VBoxGuestAdditions.iso"
end

If you have disabled the automatic update, it still easy to manually update the VirtualBox Guest Additions version, just running from the command line

vagrant vbguest

Learning Puppet or Chef? Check out Vagrant!

If you are starting to use Puppet or Chef, you must have Vagrant.

Learning Puppet can be a tedious task, getting up the master, agents, writing your first manifests,… A good way to start is using Vagrant, an Oracle VirtualBox command line automation tool, that allows easy Puppet and Chef provisioning on VirtualBox VMs.

Vagrant projects are composed by base boxes, specifically configured for Vagrant with Puppet/Chef, vagrant username and password, and anything else you may want to add, plus the configuration to apply to those base boxes, defined with Puppet or Chef. That way we can have several projects using the same base boxes shared where the only difference are the Puppet/Chef definitions. For instance a database VM and a web server VM can both use the same base box and just have different Puppet manifests, and when Vagrant starts them, it will apply the specific configuration. That also allows to share boxes and configuration files across teams, for instance having one base box with the Linux flavor used in a team, we can just have in source control the Puppet manifests to apply for the different configurations that anybody from Operations to Developers can use.

There is a list of available VMs or base boxes ready to use with Vagrant at www.vagrantbox.es. But you can build your own and share it anywhere, as they are just (big) VirtualBox VM files, easily using VeeWee, or changing a base box and rebundling it with vagrant package.

Usage

Once you have installed Vagrant and VirtualBox.

Vagrant init will create a sample Vagrantfile, the project definition file that can be customized.

$ vagrant init myproject

Then in the Vagrantfile you can change the default box settings, and add basic Puppet provisioning

config.vm.box = "centos-6"
config.vm.box_url = "https://vagrant-centos-6.s3.amazonaws.com/centos-6.box"

config.vm.provision :puppet do |puppet|
  puppet.manifests_path = "manifests"
  puppet.manifest_file = "site.pp"
end

In manifests/site.pp you can try any puppet manifest.

file { '/etc/motd':
  content => 'Welcome to your Vagrant-built virtual machine! Managed by Puppet.\n'
}

Vagrant up will download the box the first time, start the VM and apply any configuration defined in Puppet

$ vagrant up

vagrant ssh will open a shell into the box. Under the hood vagrant is redirecting the host port 2222 to the vagrant box 22

$ vagrant ssh

The vm can be suspended and resumed at any time

$ vagrant suspend
$ vagrant resume

and later on destroyed, which will delete all the VM files.

$ vagrant destroy

And then we can start again from scratch with vagrant up getting a completely new vm where we can make any mistakes 🙂

Headless VirtualBox with Windows 7 guest

Virtualbox logoContinuing my VirtualBox series, let’s talk about headless VirtualBox.

You can run any VBox machine on headless mode, without the GUI. This is particularly interesting for *nix servers where you don’t want to install the X components, but it’s also interesting for Windows boxes that you can use as servers, in order to save some resources (memory, cpu,…) while it’s running.

Use case

I have a Windows 7 box running VPN software (that only runs in Windows), but I don’t want to use the Windows GUI and keep switching from the Mac to the Windows UI do do some things.

Solution

Configuring VirtualBox

Set an IP for your Windows guest, for easier configuration. You could use port forwarding too, but that’d be more cumbersome.

Install VirtualBox extension pack to enable remote display (VRDP support). In the host install a RDP viewer, for instance, for OS X, Microsoft Remote Desktop client.

Configure the guest vbox, enabling in settings Display -> Remote Display and set authentication method to null for no authentication. There are other options for authentication, if you want to check.

Manage the VM headless with VBoxHeadless and VBoxManage commands


# start the VM

VBoxHeadless -s "Windows 7"

# save VM state

VBoxManage controlvm "Windows 7" savestate

Connect to the running VM with your RDP client as you would do with any Windows box.

Web browsing from the host

Install a proxy server on Windows, FreeProxy for instance, but there should be better options out there.

Use FoxyProxy on Firefox on the host, configuring all the remote VPN urls to go through the Windows server. Then you can connect from your host’s Firefox to any url in the VPN.

SSH and other ports from the host

On the Windows guest, connect with Putty to a SSH server on the VPN side, and configure port forwarding. Then you can connect to your VM ip on the ports you forward and end up in the remote server. You could set a server name on /etc/hosts with the VM ip so it’s easier to remember.

With this setup I can use my Mac Firefox, iTerm and other tools without needing to use the Windows interface.

Migrating from VMWare Fusion to VirtualBox: networking

Virtualbox logoIn my previous post on migrating Windows 7 to VirtualBox there were a couple things missing on the networking configuration to make VirtualBox work the same way VMWare does by default.

In VMWare you can just connect to the ip of the guest, the host is by default configured with new virtual network adapters that will route the packages to the guest.

In VirtualBox however by default guests are configured with NAT to allow internet access from the guest through the host, but doesn’t allow connecting from the host to the guest directly, only assigning each port in the host that you want redirected to the guest (which is a PITA).

There are two options to achieve the same result that you had in VMWare:

  1. Change the Network Adapter from NAT to Bridged Adapter. The drawback is that it needs to bridge to a specific hardware adapter (ethernet or airport), so it won’t work if you switch connections.
  2. Add a new Host-Only Network Adapter. First, under VirtualBox global preferences – Network, add a Host-Only network. That will create a virtual interface vboxnet0 in your host machine, and you can customize the ip ranges and DHCP server. Then in the VM settings, under Network, enable Adapter 2 attached to Host-Only adapter and choose the virtual adapter, vboxnet0.

I chose option 2 and works as expected, I just need to connect to 192.168.56.101 (the default assigned ip for the first VM) to get to the host.

Issues

The Windows 7 guest gets the ip from DHCP but for some reason it does not get the default gateway. Windows won’t let you assign the network to a Home or Work group without a default gateway, and therefore Windows Firewall will block incoming connections. The default gateway needs to be added by hand to the tcp/ip network configuration by hand (ie. 192.168.56.100) and then you can assign the network to Home/Work and the firewall will allow traffic.

Migrating Windows 7 from VMWare Fusion to VirtualBox

Virtualbox logoI recently had to start using Windows to connect to a client’s VPN network. Their VPN solution only works (or is supported) on Windows, plus the need to test a few things on Internet Explorer 😦

Previously I had used Amazon EC2 whenever I needed to use windows, to avoid license costs, 20GB of my drive wasted and the CPU/RAM overhead, but this time there was no way out, didn’t seem cool to store the VPN credentials on a public cloud instance, although it’s probably as safe. So I bought Windows 7 online and downloaded the 3GB iso image and VMWare Fusion for OS X which happens to have a 30-day trial. Got Win 7 working there with no problems, which is not what I can say about the VPN setup.

Then I got good recommendations to try VirtualBox, which is both open source and free as in beer, and was glad to see there are ways to easily move your VMWare images to VirtualBox.

Step by step instructions

To move the Windows 7 image to VirtualBox 4.1 just needed to

  1. Uninstall the VMWare tools and shutdown windows
  2. Copy the disk files from VMWare image to a new folder
    1. In Documents/Virtual Machines right click on the image, Show package contents
    2. Copy all *.vmdk files to a new folder
  3. Create a new VirtualBox machine with the same characteristics
    1. Make sure you choose Windows 7 64 bits if that’s what you used
    2. On Virtual Hard Disk, choose the main vmdk file you copied in previous step (although you’ll need to change the default storage config later)
    3. Customize created VM settings:
      1. General: Windows 7 64 bits
      2. System/Motherboard: set the same amount of base memory as the VMWare one
      3. System/Motherboard: Enable IO APIC
      4. Storage: By default VirtualBox adds it to SATA controller but you have to remove the SATA controller, and use IDE PIIX4 without host I/O cache, attaching vmdk to primary master IDE, leave CD/DVD drive
  4. Boot the VirtualBox VM and follow any prompt to restart Windows to install new devices
  5. On the VirtualBox Devices menu, click on install guest additions

Troubleshooting

Several things went wrong before I got it working, so just in case you have the same problems

  • Stuck in “Windows is loading files” black screen, rebooting continuously: make sure you have selected 64 bits Windows and enabled IO APIC
  • Blue screen of death: Remove SATA, SCSI and any other non-IDE controllers from Storage, attach vmdk to IDE and make sure PIIX4 is selected
  • Windows needs to be repaired / Cannot repair windows: same as previous one, make sure disk is attached to IDE