Create a more complex class and refactor it
Now that we have all our setup done and a functional class to manage the OpenLDAP client part, let’s deal with a more complex part: the server class.
WRITE THE ACCEPTANCE TESTS
Let’s write the acceptance tests to code the behavior we want in spec/acceptance/openldap__server_spec.rb
:
require 'spec_helper_acceptance'
describe 'openldap::server' do
describe 'running puppet code' do
it 'should work with no errors' do
pp = <<-EOS
class { 'openldap::server': }
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
describe port(389) do
it { is_expected.to be_listening }
end
describe service('slapd') do
it { is_expected.to be_enabled }
it { is_expected.to be_running }
end
end
end
openldap::server
, the server is listening on port 389 and that slapd service is running and enabled at boot time. No need to run it for now because it will obviously fail as the openldap::server
does not exist yet.
WRITE THE UNIT TESTS
Let’s write the unit tests skeleton for our openldap::serverclass
in spec/classes/openldap__server_spec.rb
:
require 'spec_helper'
describe 'openldap::server' do
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts
end
it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_service('slapd')
.that_requires('Package[openldap-servers]')
}
case facts[:osfamily]
when 'Debian'
it { is_expected.to contain_package('openldap-servers').with(
{
:ensure => :present,
:name => 'slapd',
}
) }
else
it { is_expected.to contain_package('openldap-servers').with(
{
:ensure => :present,
:name => 'openldap-servers',
}
) }
end
end
end
end
When we declare the openldap::server
class, we want the catalog to contain a service resource named slapd
that requires a package named slapd
on Debian and openldap-servers
on RedHat.
WRITE THE PUPPET CODE
Now, let’s write the actual Puppet code:
class openldap::server {
$package_name = $::osfamily ? {
'Debian' => 'slapd',
'RedHat' => 'openldap-servers',
}
package { 'openldap-servers':
ensure => present,
name => $package_name,
} ->
service { 'slapd':
ensure => running,
enable => true,
}
}
LAUNCH THE UNIT TESTS
Now, if you launch the unit tests, it should work:
$ bundle exec rake spec SPEC_OPTS=-fd SPEC=spec/classes/openldap__server_spec.rb
...
openldap::server
on debian-7-x86_64
should compile the catalogue without cycles
should contain Service[slapd]
should contain Package[openldap-servers] with ensure => :present and name => "slapd"
on redhat-7-x86_64
should compile the catalogue without cycles
should contain Service[slapd]
should contain Package[openldap-servers] with ensure => :present and name => "openldap-servers"
...
Finished in 1.27 seconds (files took 0.67818 seconds to load)
6 examples, 0 failures
LAUNCH THE ACCEPTANCE TESTS
ON REDHAT 7
Let’s test on RedHat:
BEAKER_set=centos-7-x86_64-vagrant bundle exec rspec spec/acceptance/openldap__server_spec.rb
...
openldap::server
running puppet code
localhost $ scp /tmp/beaker20150305-11384-7chojc centos-7-x64:/tmp/apply_manifest.pp.XqY8IJ {:ignore => }
localhost $ scp /tmp/beaker20150305-11384-1opmurv centos-7-x64:/tmp/apply_manifest.pp.hLyO5k {:ignore => }
should work with no errors
Port "389"
should be listening
Service "slapd"
should be enabled
should be running
Destroying vagrant boxes
==> centos-7-x64: Forcing shutdown of VM...
==> centos-7-x64: Destroying VM and associated drives...
Finished in 19.05 seconds (files took 2 minutes 17.4 seconds to load)
4 examples, 0 failures
ON DEBIAN 7
BEAKER_set=debian-7-x86_64-vagrant bundle exec rspec spec/acceptance/openldap__server_spec.rb
...
openldap::server
running puppet code
localhost $ scp /tmp/beaker20150305-13082-1sffkj5 debian-7-x64:/tmp/apply_manifest.pp.5AjHHb {:ignore => }
localhost $ scp /tmp/beaker20150305-13082-9tjbgg debian-7-x64:/tmp/apply_manifest.pp.1HcVFQ {:ignore => }
should work with no errors
Port "389"
should be listening
Service "slapd"
should be enabled
should be running
Destroying vagrant boxes
==> debian-7-x64: Forcing shutdown of VM...
==> debian-7-x64: Destroying VM and associated drives...
Finished in 17.89 seconds (files took 1 minute 31.4 seconds to load)
4 examples, 0 failures
REFACTOR
Now that everything is working as expected, we can refactor without risking any regression.
For the moment, our class is quite simple, but it might become wildly unmaintainable as it grows. To prevent that, we’ll use the more standard package -> config ~> service
pattern explained in R.I.Pienaar’s excellent blog post "Simple Puppet Module Structure Redux":
$ mkdir manifests/server
The openldap::server
class becomes (we don’t manage configuration for now):
class openldap::server {
class { '::openldap::server::install': } ->
class { '::openldap::server::config': } ~>
class { '::openldap::server::service': } ->
Class['openldap::server']
}
The openldap::server::install
class:
class openldap::server::install {
$package_name = $::osfamily ? {
'Debian' => 'slapd',
'RedHat' => 'openldap-servers',
}
package { 'openldap-servers':
ensure => present,
name => $package_name,
}
}
The openldap::server::config
class (empty for now):
class openldap::server::config {
}
And finally, the openldap::server::service
class:
class openldap::server::service {
service { 'slapd':
ensure => running,
enable => true,
}
}
LAUNCH THE UNIT TESTS
And now we can launch the unit tests to see if we didn’t break anything: Unfortunately, as of version 2.0.1, rspec-puppet does not look up in the full graph for resource dependencies, so we can not yet validate that we don’t break resource application order when refactoring (corresponding thread in puppet-dev mailing list).
We will mark this test as pending for now. That way, our test suite will pass even though this specific test fails, but it will fail again when rspec-puppet is fixed, so that we’re warned. In the meantime, we’ll add some tests that check the dependency between subclasses. This roughly checks the same thing but is less than ideal as the module’s structure should not appear in the tests.
require 'spec_helper'
describe 'openldap::server' do
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts
end
it { is_expected.to compile.with_all_deps }
it {
pending 'rspec-puppet does not support recursive dependency yet.'
is_expected.to contain_service('slapd')
.that_requires('Package[openldap-servers]')
}
it { is_expected.to contain_service('slapd') }
it { is_expected.to contain_class('openldap::server::install')
.that_comes_before('Class[openldap::server::config]')
}
it { is_expected.to contain_class('openldap::server::config')
.that_notifies('Class[openldap::server::service]')
}
case facts[:osfamily]
when 'Debian'
it { is_expected.to contain_package('openldap-servers').with(
{
:ensure => :present,
:name => 'slapd',
}
) }
when 'RedHat'
it { is_expected.to contain_package('openldap-servers').with(
{
:ensure => :present,
:name => 'openldap-servers',
}
) }
end
end
end
end
Then, we launch it again:
Now it works with 2 tests marked as pending that we will fix as soon as rspec-puppet is fixed.
LAUNCH THE ACCEPTANCE TESTS
Finally, we can launch our acceptance tests again and check that everything is still OK:
ON REDHAT 7
BEAKER_set=centos-7-x86_64-vagrant bundle exec rspec spec/acceptance/openldap__server_spec.rb
...
openldap::server
running puppet code
localhost $ scp /tmp/beaker20150305-11384-7chojc centos-7-x64:/tmp/apply_manifest.pp.XqY8IJ {:ignore => }
localhost $ scp /tmp/beaker20150305-11384-1opmurv centos-7-x64:/tmp/apply_manifest.pp.hLyO5k {:ignore => }
should work with no errors
Port "389"
should be listening
Service "slapd"
should be enabled
should be running
Destroying vagrant boxes
==> centos-7-x64: Forcing shutdown of VM...
==> centos-7-x64: Destroying VM and associated drives...
Finished in 19.05 seconds (files took 2 minutes 17.4 seconds to load)
4 examples, 0 failures
ON DEBIAN 7
BEAKER_set=debian-7-x86_64-vagrant bundle exec rspec spec/acceptance/openldap__server_spec.rb
...
openldap::server
running puppet code
localhost $ scp /tmp/beaker20150305-13082-1sffkj5 debian-7-x64:/tmp/apply_manifest.pp.5AjHHb {:ignore => }
localhost $ scp /tmp/beaker20150305-13082-9tjbgg debian-7-x64:/tmp/apply_manifest.pp.1HcVFQ {:ignore => }
should work with no errors
Port "389"
should be listening
Service "slapd"
should be enabled
should be running
Destroying vagrant boxes
==> debian-7-x64: Forcing shutdown of VM...
==> debian-7-x64: Destroying VM and associated drives...
Finished in 17.89 seconds (files took 1 minute 31.4 seconds to load)
4 examples, 0 failures
We have now seen how to use unit and acceptance tests to help limit the risks when refactoring our code. Next time, we’ll go a little bit further by developing a custom type and provider to manage OpenLDAP databases in a behavior/test driven manner.