diff options
-rw-r--r-- | lib/puppet/type/service.rb | 286 | ||||
-rwxr-xr-x | lib/puppet/type/service/base.rb | 17 | ||||
-rwxr-xr-x | lib/puppet/type/service/init.rb | 141 | ||||
-rw-r--r-- | test/types/tc_service.rb | 35 |
4 files changed, 343 insertions, 136 deletions
diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index ff5fd779b..3e87a136a 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -1,93 +1,51 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - # this is our main way of managing processes right now # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such +require 'puppet/type/service/init' + module Puppet class State class ServiceRunning < State @doc = "Whether a service should be running. **true**/*false*" @name = :running - #@event = :file_created # this whole thing is annoying # i should probably just be using booleans, but for now, i'm not... def should=(should) case should when false,0,"0": - should = 0 + should = :stopped when true,1,"1": - should = 1 + should = :running else - warning "%s: interpreting '%s' as false" % + Puppet.warning "%s: interpreting '%s' as false" % [self.class,should] should = 0 end + Puppet.debug "Service should is %s" % should @should = should end def retrieve - self.is = self.running() - debug "Running value for '%s' is '%s'" % + self.is = @parent.status + Puppet.debug "Running value for '%s' is '%s'" % [self.parent.name,self.is] end - # should i cache this info? - def running - begin - status = self.parent.initcmd("status") - debug "initcmd status for '%s' is '%s'" % - [self.parent.name,status] - - if status # the command succeeded - return 1 - else - return 0 - end - rescue SystemCallError - raise "Could not execute %s" % initscript - end - - end - def sync - if self.running > 0 - status = 1 - else - status = 0 - end - debug "'%s' status is '%s' and should be '%s'" % - [self,status,should] event = nil - if self.should > 0 - if status < 1 - debug "Starting '%s'" % self - if self.parent.initcmd("start") - event = :service_started - else - raise "Failed to start '%s'" % self.parent.name - end - else - debug "'%s' is already running, yo" % self - #debug "Starting '%s'" % self - #unless self.parent.initcmd("start") - # raise "Failed to start %s" % self.name - #end - end - elsif status > 0 - debug "Stopping '%s'" % self - if self.parent.initcmd("stop") - event = :service_stopped - else - raise "Failed to stop %s" % self.name - end + if @should == :running + @parent.start + event = :service_started + elsif @should == :stopped + @parent.stop + event = :service_stopped else - debug "Not running '%s' and shouldn't be running" % self + Puppet.debug "Not running '%s' and shouldn't be running" % + self end return event @@ -101,19 +59,41 @@ module Puppet Puppet::State::ServiceRunning ] @parameters = [ + :binary, + :hasstatus, :name, - :path + :path, + :pattern, + :restart, + :start, + :status, + :stop ] + @paramdoc[:binary] = "The path to the daemon. This is only used for + systems that do not support init scripts." + @paramdoc[:hasstatus] = "Declare the the service's init script has a + functional status command. This is assumed to be default for + most systems, although there might be platforms on which this is + assumed to be true." @paramdoc[:name] = "The name of the service to run. This name is used to find the init script in the search path." @paramdoc[:path] = "The search path for finding init scripts. There is currently no default, but hopefully soon there will be a reasonable default for all platforms." - - @functions = [ - :setpath - ] + @paramdoc[:pattern] = "The pattern to search for in the process table. + This is used for stopping services on platforms that do not + support init scripts, and is also used for determining service + status on those service whose init scripts do not include a status + command." + @paramdoc[:restart] = "Specify a *restart* command manually. If left + unspecified, the restart method will be determined automatically." + @paramdoc[:start] = "Specify a *start* command manually. If left + unspecified, the start method will be determined automatically." + @paramdoc[:status] = "Specify a *status* command manually. If left + unspecified, the status method will be determined automatically." + @paramdoc[:stop] = "Specify a *stop* command manually. If left + unspecified, the stop method will be determined automatically." @doc = "Manage running services. Rather than supporting managing individual processes, puppet uses init scripts to simplify @@ -124,76 +104,152 @@ module Puppet @name = :service @namevar = :name - @searchpaths = Array.new - @allowedmethods = [:setpath] - - def initialize(hash) - @searchpaths = [] - super - - unless defined? @searchpaths and @searchpaths.length > 0 - raise Puppet::Error.new( - "You must specify a valid search path for service %s" % - self.name - ) + # Return the service type we're using. Default to the Service + # class itself, but could be set to a module. + def self.svctype + if defined? @svctype + return @svctype + else + return self end end - def search(name) - @searchpaths.each { |path| - fqname = File.join(path,name) - begin - stat = File.stat(fqname) - rescue - # should probably rescue specific errors... - debug("Could not find %s in %s" % [name,path]) - next - end - - # if we've gotten this far, we found a valid script - return fqname - } - raise Puppet::Error, "Could not find init script for '%s'" % name + # Execute a command. Basically just makes sure it exits with a 0 + # code. + def execute(type, cmd) + output = %x(#{cmd} 2>&1) + unless $? == 0 + raise Puppet::Error, "Could not %s %s: %s" % + [type, self.name, output.chomp] + end end - def parampath=(ary) - @parameters[:path] = ary - # verify each of the paths exists - @searchpaths = ary.find_all { |dir| - FileTest.directory?(dir) + # Get the process ID for a running process. Requires the 'pattern' + # parameter. + def getpid + unless self[:pattern] + raise Puppet::Error, + "Either a stop command or a pattern must be specified" + end + ps = Facter["ps"].value + regex = Regexp.new(self[:pattern]) + IO.popen(ps) { |table| + table.each { |line| + if regex.match(line) + ary = line.split(/\s+/) + return ary[1] + end + } } + + return nil end - # it'd be nice if i didn't throw the output away... - # this command returns true if the exit code is 0, and returns - # false otherwise - def initcmd(cmd) - script = self.initscript + def initialize(hash) + super - debug "Executing '%s %s' as initcmd for '%s'" % - [script,cmd,self] + if self.respond_to?(:configchk) + self.configchk + end + end - rvalue = Kernel.system("%s %s" % - [script,cmd]) + # Basically just a synonym for restarting. Used to respond + # to events. + def refresh + self.restart + end - debug "'%s' ran with exit status '%s'" % - [cmd,rvalue] + # How to restart the process. + def restart + if self[:restart] or self.respond_to?(:restartcmd) + cmd = self[:restart] || self.restartcmd + self.execute("restart", cmd) + else + self.stop + self.start + end + end + # Check if the process is running. Prefer the 'status' parameter, + # then 'statuscmd' method, then look in the process table. We give + # the object the option to not return a status command, which might + # happen if, for instance, it has an init script (and thus responds to + # 'statuscmd') but does not have 'hasstatus' enabled. + def status + if self[:status] or (self.respond_to?(:statuscmd) and self.statuscmd) + cmd = self[:status] || self.statuscmd + output = %x(#{cmd} 2>&1) + Puppet.debug "%s status returned %s" % + [self.name, output] + if $? == 0 + return :running + else + return :stopped + end + elsif pid = self.getpid + return :running + else + return :stopped + end + end - rvalue + # Run the 'start' parameter command, or the specified 'startcmd'. + def start + cmd = self[:start] || self.startcmd + self.execute("start", cmd) end - def initscript - if defined? @initscript - return @initscript + # Stop the service. If a 'stop' parameter is specified, it + # takes precedence; otherwise checks if the object responds to + # a 'stopcmd' method, and if so runs that; otherwise, looks + # for the process in the process table. + # This method will generally not be overridden by submodules. + def stop + if self[:stop] + return self[:stop] + elsif self.respond_to?(:stopcmd) + self.execute("stop", self.stopcmd) else - @initscript = self.search(self.name) + pid = getpid + unless pid + Puppet.info "%s is not running" % self.name + return false + end + output = %x("kill #{pid} 2>&1") + if $? != 0 + raise Puppet::Error, + "Could not kill %s, PID %s: %s" % + [self.name, pid, output] + end + return true end end - def refresh - self.initcmd("restart") + # Now load any overlay modules to provide additional functionality + case Facter["operatingsystem"].value + when "Linux": + case Facter["distro"].value + when "Debian": + require 'puppet/type/service/init' + @svctype = Puppet::ServiceTypes::InitSvc + end + when "SunOS": + release = Integer(Facter["operatingsystemrelease"].value) + if release < 5.10 + require 'puppet/type/service/init' + @svctype = Puppet::ServiceTypes::InitSvc + else + require 'puppet/type/service/smf' + @svctype = Puppet::ServiceTypes::SMFSvc + end end - end # Puppet::Type::Service - end # Puppet::Type + unless defined? @svctype + require 'puppet/type/service/base' + @svctype = Puppet::ServiceTypes::BaseSvc + end + include @svctype + end + end end + +# $Id$ diff --git a/lib/puppet/type/service/base.rb b/lib/puppet/type/service/base.rb new file mode 100755 index 000000000..6c0642541 --- /dev/null +++ b/lib/puppet/type/service/base.rb @@ -0,0 +1,17 @@ +module Puppet + module ServiceTypes + module BaseSvc + + # The command used to start. Generated if the 'binary' argument + # is passed. + def startcmd + if self[:binary] + return self[:binary] + else + raise Puppet::Error, + "Services must specify a start command or a binary" + end + end + end + end +end diff --git a/lib/puppet/type/service/init.rb b/lib/puppet/type/service/init.rb new file mode 100755 index 000000000..42fdfd7f7 --- /dev/null +++ b/lib/puppet/type/service/init.rb @@ -0,0 +1,141 @@ +module Puppet + module ServiceTypes + module InitSvc + # Make sure we've got a search path set up. If they don't + # specify one, try to determine one. + def configchk + unless defined? @searchpaths + @searchpaths = [] + end + unless @searchpaths.length > 0 + if init = self.defaultinit + @searchpaths << init + else + Puppet.notice "No default init for %s" % + Facter["operatingsystem"].value + + raise Puppet::Error.new( + "You must specify a valid search path for service %s" % + self.name + ) + end + end + end + + # Get the default init path. + def defaultinit + unless defined? @defaultinit + case Facter["operatingsystem"].value + when "FreeBSD": + @defaultinit = "/etc/rc.d" + else + @defaultinit = "/etc/init.d" + @defaultrc = "/etc/rc%s.d" + end + end + + return @defaultinit + end + + # Mark that our init script supports 'status' commands. + def hasstatus=(value) + case value + when true, "true": @parameters[:hasstatus] = true + when false, "false": @parameters[:hasstatus] = false + else + raise Puppet::Error, "Invalid 'hasstatus' value %s" % + value.inspect + end + end + + # it'd be nice if i didn't throw the output away... + # this command returns true if the exit code is 0, and returns + # false otherwise + def initcmd(cmd) + script = self.initscript + + Puppet.debug "Executing '%s %s' as initcmd for '%s'" % + [script,cmd,self] + + rvalue = Kernel.system("%s %s" % + [script,cmd]) + + Puppet.debug "'%s' ran with exit status '%s'" % + [cmd,rvalue] + + + rvalue + end + + # Where is our init script? + def initscript + if defined? @initscript + return @initscript + else + @initscript = self.search(self.name) + end + end + + # Store the search path for init scripts. This will generally not + # be called. + def parampath=(ary) + unless ary.is_a?(Array) + ary = [ary] + end + @parameters[:path] = ary + @searchpaths = ary.find_all { |dir| + File.directory?(dir) + } + end + + # Enable a service, to it's started at boot time. This basically + # just creates links in the RC directories, which means that, well, + # we need to know where the rc directories are. + # FIXME This should probably be a state or something, and + # it should actually create use Symlink objects... + #def enable + #end + + #def disable + #end + + def search(name) + @searchpaths.each { |path| + fqname = File.join(path,name) + begin + stat = File.stat(fqname) + rescue + # should probably rescue specific errors... + Puppet.debug("Could not find %s in %s" % [name,path]) + next + end + + # if we've gotten this far, we found a valid script + return fqname + } + raise Puppet::Error, "Could not find init script for '%s'" % name + end + + # The start command is just the init scriptwith 'start'. + def startcmd + self.initscript + " start" + end + + # If it was specified that the init script has a 'status' command, then + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd + if self[:hasstatus] + return self.initscript + " status" + else + return false + end + end + + # The stop command is just the init script with 'stop'. + def stopcmd + self.initscript + " stop" + end + end + end +end diff --git a/test/types/tc_service.rb b/test/types/tc_service.rb index ecacc77d7..c0df3419f 100644 --- a/test/types/tc_service.rb +++ b/test/types/tc_service.rb @@ -28,22 +28,16 @@ class TestService < TestPuppet super end - def mksleeper + def mksleeper(hash = {}) + hash[:name] = "sleeper" + hash[:path] = File.join($puppetbase,"examples/root/etc/init.d") + hash[:running] = true assert_nothing_raised() { - return Puppet::Type::Service.create( - :name => "sleeper", - :path => File.join($puppetbase,"examples/root/etc/init.d"), - :running => 1 - ) + return Puppet::Type::Service.create(hash) } end - def test_process_start - sleeper = mksleeper - # start it - assert_nothing_raised() { - sleeper[:running] = 1 - } + def cyclesleeper(sleeper) assert_nothing_raised() { sleeper.retrieve } @@ -80,15 +74,14 @@ class TestService < TestPuppet assert(sleeper.insync?) end - def test_FailOnNoPath - serv = nil - assert_nothing_raised { - serv = Puppet::Type::Service.create( - :name => "sleeper" - ) - } + def test_processStartWithPattern + sleeper = mksleeper(:pattern => "bin/sleeper") + + cyclesleeper(sleeper) + end - assert_nil(serv) - assert_nil(Puppet::Type::Service["sleeper"]) + def test_processStartWithStatus + sleeper = mksleeper(:hasstatus => true) + cyclesleeper(sleeper) end end |