diff options
-rw-r--r-- | lib/puppet/provider/service/daemontools.rb | 154 | ||||
-rw-r--r-- | spec/unit/provider/service/daemontools.rb | 124 |
2 files changed, 278 insertions, 0 deletions
diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb new file mode 100644 index 000000000..52d8c6b6c --- /dev/null +++ b/lib/puppet/provider/service/daemontools.rb @@ -0,0 +1,154 @@ +# Daemontools service management +# +# author Brice Figureau <brice-puppet@daysofwonder.com> +Puppet::Type.type(:service).provide :daemontools, :parent => :base do + desc "Daemontools service management. + This provider manages daemons running supervised by D.J.Bernstein daemontools. + It tries to detect the service directory, with by order of preference: + * /service + * /etc/service + * /var/lib/svscan + The daemon directory should be placed in a directory that can be + by default in: + * /var/lib/service + * /etc + or this can be overriden in the service resource parameters: + service { + \"myservice\": + provider => \"daemontools\", path => \"/path/to/daemons\"; + } + + This provider supports out of the box: + * start/stop (mapped to enable/disable) + * enable/disable + * restart + * status" + + commands :svc => "/usr/bin/svc" + commands :svstat => "/usr/bin/svstat" + + class << self + attr_writer :defpath + + # this is necessary to autodetect a valid resource + # default path, since there is no standard for such directory. + def defpath + unless defined?(@defpath) and @defpath + ["/var/lib/service", "/etc"].each do |path| + if FileTest.exist?(path) + @defpath = path + break + end + end + raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath + end + @defpath + end + end + + attr_writer :servicedir + + # returns all providers for all existing services in @defpath + # ie enabled or not + def self.instances + path = self.defpath + unless FileTest.directory?(path) + Puppet.notice "Service path %s does not exist" % path + next + end + + # reject entries that aren't either a directory + # or don't contain a run file + Dir.entries(path).reject { |e| + fullpath = File.join(path, e) + e =~ /^\./ or ! FileTest.directory?(fullpath) or ! FileTest.exist?(File.join(fullpath,"run")) + }.collect do |name| + new(:name => name, :path => path) + end + end + + # returns the daemon dir on this node + def self.daemondir + self.defpath + end + + # find the service dir on this node + def servicedir + unless defined?(@servicedir) and @servicedir + ["/service", "/etc/service","/var/lib/svscan"].each do |path| + if FileTest.exist?(path) + @servicedir = path + break + end + end + raise "Could not find service directory" unless @servicedir + end + @servicedir + end + + # returns the full path of this service when enabled + # (ie in the service directory) + def service + File.join(self.servicedir, resource[:name]) + end + + # returns the full path to the current daemon directory + # note that this path can be overriden in the resource + # definition + def daemon + File.join(resource[:path], resource[:name]) + end + + def restartcmd + [ command(:svc), "-t", self.service] + end + + # The start command does nothing, service are automatically started + # when enabled by svscan. But, forces an enable if necessary + def start + # to start make sure the sevice is enabled + self.enable + # start is then automatic + end + + def status + begin + output = svstat self.service + return :running if output =~ /\bup\b/ + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) + end + return :stopped + end + + # unfortunately it is not possible + # to stop without disabling the service + def stop + self.disable + end + + # disable by stopping the service + # and removing the symlink so that svscan + # doesn't restart our service behind our back + def disable + # should stop the service + # stop the log subservice if any + log = File.join(self.service, "log") + texecute("stop log", [ command(:svc) , '-dx', log] ) if FileTest.directory?(log) + + # stop the main resource + texecute("stop", [command(:svc), '-dx', self.service] ) + + # unlink the daemon symlink to disable it + File.unlink(self.service) if FileTest.symlink?(self.service) + end + + def enabled? + FileTest.symlink?(self.service) + end + + def enable + File.symlink(self.daemon, self.service) if ! FileTest.symlink?(self.service) + end +end + diff --git a/spec/unit/provider/service/daemontools.rb b/spec/unit/provider/service/daemontools.rb new file mode 100644 index 000000000..29e9dd5be --- /dev/null +++ b/spec/unit/provider/service/daemontools.rb @@ -0,0 +1,124 @@ +#!/usr/bin/env ruby +# +# Unit testing for the Daemontools service Provider +# +# author Brice Figureau +# +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:service).provider(:daemontools) + +describe provider_class do + + before(:each) do + # Create a mock resource + @resource = stub 'resource' + + @provider = provider_class.new + @servicedir = "/etc/service" + @provider.servicedir=@servicedir + @daemondir = "/var/lib/service" + @provider.class.defpath=@daemondir + + # A catch all; no parameters set + @resource.stubs(:[]).returns(nil) + + # But set name, source and path (because we won't run + # the thing that will fetch the resource path from the provider) + @resource.stubs(:[]).with(:name).returns "myservice" + @resource.stubs(:[]).with(:ensure).returns :enabled + @resource.stubs(:[]).with(:path).returns @daemondir + @resource.stubs(:ref).returns "Service[myservice]" + + @provider.stubs(:resource).returns @resource + end + + it "should have a restartcmd method" do + @provider.should respond_to(:restartcmd) + end + + it "should have a start method" do + @provider.should respond_to(:start) + end + + it "should have a stop method" do + @provider.should respond_to(:stop) + end + + it "should have an enabled? method" do + @provider.should respond_to(:enabled?) + end + + it "should have an enable method" do + @provider.should respond_to(:enable) + end + + it "should have a disable method" do + @provider.should respond_to(:disable) + end + + describe "when starting" do + it "should call enable" do + @provider.expects(:enable) + @provider.start + end + end + + describe "when stopping" do + it "should call disable" do + @provider.expects(:disable) + @provider.stop + end + end + + describe "when enabling" do + it "should create a symlink between daemon dir and service dir" do + FileTest.stubs(:symlink?).returns(false) + File.expects(:symlink).with(File.join(@daemondir,"myservice"), File.join(@servicedir,"myservice")).returns(0) + @provider.enable + end + end + + describe "when disabling" do + it "should stop and then remove the symlink between daemon dir and service dir" do + FileTest.stubs(:directory?).returns(false) + FileTest.stubs(:symlink?).returns(true) + File.expects(:unlink).with(File.join(@servicedir,"myservice")).returns(0) + @provider.stubs(:texecute).returns("") + @provider.disable + end + end + + describe "when disabling" do + it "should also call 'svc -dx /etc/service/myservice'" do + FileTest.stubs(:directory?).returns(false) + FileTest.stubs(:symlink?).returns(true) + File.expects(:unlink).with(File.join(@servicedir,"myservice")).returns(0) + @provider.expects(:texecute).with("stop", [nil, '-dx', File.join(@servicedir,"myservice")]).returns "" + @provider.disable + end + end + + describe "when checking status" do + it "should call the external command 'svstat /etc/service/myservice'" do + @provider.expects(:svstat).with(File.join(@servicedir,"myservice")) + @provider.status + end + end + + describe "when checking status" do + it "and svstat fails, properly raise a Puppet::Error" do + @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).raises(Puppet::ExecutionFailure, "failure") + lambda { @provider.status }.should raise_error(Puppet::Error, 'Could not get status for service Service[myservice]: failure') + end + it "and svstat returns up, then return :running" do + @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).returns("/etc/service/myservice: up (pid 454) 954326 seconds") + @provider.status.should == :running + end + it "and svstat returns not running, then return :stopped" do + @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).returns("/etc/service/myservice: supervise not running") + @provider.status.should == :stopped + end + end + + end |