From 41dc1fac33cbb3938a5dc5f42f5b841a0a734c27 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Wed, 27 Aug 2008 20:31:38 +0200 Subject: Runit service provider This provider manages daemons running supervised by Runit[1]. It tries to detect the service directory, with by order of preference: * /service * /var/service * /etc/service The daemon directory should be placed in a directory that can be by default in: * /etc/sv * /var/lib/service or this can be overriden in the service resource parameters: service { "myservice": provider => "runit", path => "/path/to/daemons"; } This provider supports out of the box: * start/stop * enable/disable * restart * status [1]: http://smarden.sunsite.dk/runit/ --- CHANGELOG | 2 + lib/puppet/provider/service/runit.rb | 93 ++++++++++++++++++++++++++++ spec/unit/provider/service/runit.rb | 117 +++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 lib/puppet/provider/service/runit.rb create mode 100644 spec/unit/provider/service/runit.rb diff --git a/CHANGELOG b/CHANGELOG index 11107ebfb..8ce2c3a8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.x + Added daemontools and runit providers for service type + Added simple rake task for running unit tests Added spec Rake task diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb new file mode 100644 index 000000000..230fa75d9 --- /dev/null +++ b/lib/puppet/provider/service/runit.rb @@ -0,0 +1,93 @@ +# Daemontools service management +# +# author Brice Figureau +Puppet::Type.type(:service).provide :runit, :parent => :daemontools do + desc "Runit service management. + This provider manages daemons running supervised by Runit. + It tries to detect the service directory, with by order of preference: + * /service + * /var/service + * /etc/service + The daemon directory should be placed in a directory that can be + by default in: + * /etc/sv + or this can be overriden in the service resource parameters: + service { + \"myservice\": + provider => \"runit\", path => \"/path/to/daemons\"; + } + + This provider supports out of the box: + * start/stop + * enable/disable + * restart + * status" + + commands :sv => "/usr/bin/sv" + + class << self + # 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 + ["/etc/sv", "/var/lib/service"].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 + + # find the service dir on this node + def servicedir + unless defined?(@servicedir) and @servicedir + ["/service", "/etc/service","/var/service"].each do |path| + if FileTest.exist?(path) + @servicedir = path + break + end + end + raise "Could not find service directory" unless @servicedir + end + @servicedir + end + + def restartcmd + [ command(:sv), "restart", self.service] + end + + def status + begin + output = sv "status", self.daemon + return :running if output =~ /^run: / + rescue Puppet::ExecutionFailure => detail + unless detail.message =~ /(warning: |runsv not running$)/ + raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) + end + end + return :stopped + end + + # relay to the stopcmd + def stop + ucommand( :stop ) + end + + def stopcmd + [ command(:sv), "stop", self.service] + end + + # disable by removing the symlink so that runit + # doesn't restart our service behind our back + # note that runit doesn't need to perform a stop + # before a disable + def disable + # unlink the daemon symlink to disable it + File.unlink(self.service) if FileTest.symlink?(self.service) + end +end + diff --git a/spec/unit/provider/service/runit.rb b/spec/unit/provider/service/runit.rb new file mode 100644 index 000000000..8eb53849b --- /dev/null +++ b/spec/unit/provider/service/runit.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby +# +# Unit testing for the Runit service Provider +# +# author Brice Figureau +# +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:service).provider(:runit) + +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 = "/etc/sv" + @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 execute external command 'sv stop /etc/service/myservice'" do + @provider.expects(:ucommand).with(:stop).returns("") + @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 remove the '/etc/service/myservice' symlink" do + FileTest.stubs(:directory?).returns(false) + FileTest.stubs(:symlink?).returns(true) + File.expects(:unlink).with(File.join(@servicedir,"myservice")).returns(0) + @provider.disable + end + end + + describe "when checking status" do + it "should call the external command 'sv status /etc/sv/myservice'" do + @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")) + @provider.status + end + end + + describe "when checking status" do + it "and sv status fails, properly raise a Puppet::Error" do + @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).raises(Puppet::ExecutionFailure, "fail: /etc/sv/myservice: file not found") + lambda { @provider.status }.should raise_error(Puppet::Error, 'Could not get status for service Service[myservice]: fail: /etc/sv/myservice: file not found') + end + it "and sv status returns up, then return :running" do + @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("run: /etc/sv/myservice: (pid 9029) 6s") + @provider.status.should == :running + end + it "and sv status returns not running, then return :stopped" do + @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("fail: /etc/sv/myservice: runsv not running") + @provider.status.should == :stopped + end + it "and sv status returns a warning, then return :stopped" do + @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("warning: /etc/sv/myservice: unable to open supervise/ok: file does not exist") + @provider.status.should == :stopped + end + end + + end -- cgit