summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/provider/service/daemontools.rb154
-rw-r--r--spec/unit/provider/service/daemontools.rb124
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