diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb index 2d8f559f7..6c6fa8114 100755 --- a/lib/puppet/provider/service/base.rb +++ b/lib/puppet/provider/service/base.rb @@ -1,144 +1,105 @@ -Puppet::Type.type(:service).provide :base do - desc "The simplest form of service support. +Puppet::Type.type(:service).provide :base, :parent => :service do + desc "The simplest form of Unix service support. You have to specify enough about your service for this to work; the minimum you can specify is a binary for starting the process, and this same binary will be searched for in the process table to stop the service. As with `init`-style services, it is preferable to specify start, stop, and status commands. " commands :kill => "kill" - def self.instances - [] - end - # Get the process ID for a running process. Requires the 'pattern' # parameter. def getpid @resource.fail "Either stop/status commands or a pattern must be specified" unless @resource[:pattern] ps = Facter["ps"].value @resource.fail "You must upgrade Facter to a version that includes 'ps'" unless ps and ps != "" regex = Regexp.new(@resource[:pattern]) self.debug "Executing '#{ps}'" IO.popen(ps) { |table| table.each_line { |line| if regex.match(line) ary = line.sub(/^\s+/, '').split(/\s+/) return ary[1] end } } nil end - # How to restart the process. - def restart - if @resource[:restart] or restartcmd - ucommand(:restart) - else - self.stop - self.start - end - end - - # There is no default command, which causes other methods to be used - def restartcmd - 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 @resource[:status] or statuscmd # Don't fail when the exit status is not 0. ucommand(:status, false) # Expicitly calling exitstatus to facilitate testing if $CHILD_STATUS.exitstatus == 0 return :running else return :stopped end elsif pid = self.getpid self.debug "PID is #{pid}" return :running else return :stopped end end # There is no default command, which causes other methods to be used def statuscmd end # Run the 'start' parameter command, or the specified 'startcmd'. def start ucommand(:start) end # The command used to start. Generated if the 'binary' argument # is passed. def startcmd if @resource[:binary] return @resource[:binary] else raise Puppet::Error, "Services must specify a start command or a binary" end end # 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 @resource[:stop] or stopcmd ucommand(:stop) else pid = getpid unless pid self.info "#{self.name} is not running" return false end begin output = kill pid rescue Puppet::ExecutionFailure => detail @resource.fail "Could not kill #{self.name}, PID #{pid}: #{output}" end return true end end # There is no default command, which causes other methods to be used def stopcmd end - - # A simple wrapper so execution failures are a bit more informative. - def texecute(type, command, fof = true) - begin - # #565: Services generally produce no output, so squelch them. - execute(command, :failonfail => fof, :squelch => true) - rescue Puppet::ExecutionFailure => detail - @resource.fail "Could not #{type} #{@resource.ref}: #{detail}" - end - nil - end - - # Use either a specified command or the default for our provider. - def ucommand(type, fof = true) - if c = @resource[type] - cmd = [c] - else - cmd = [send("#{type}cmd")].flatten - end - texecute(type, cmd, fof) - end end diff --git a/lib/puppet/provider/service/service.rb b/lib/puppet/provider/service/service.rb new file mode 100644 index 000000000..ec2372f93 --- /dev/null +++ b/lib/puppet/provider/service/service.rb @@ -0,0 +1,43 @@ +Puppet::Type.type(:service).provide :service do + desc "The simplest form of service support." + + def self.instances + [] + end + + # How to restart the process. + def restart + if @resource[:restart] or restartcmd + ucommand(:restart) + else + self.stop + self.start + end + end + + # There is no default command, which causes other methods to be used + def restartcmd + end + + # A simple wrapper so execution failures are a bit more informative. + def texecute(type, command, fof = true) + begin + # #565: Services generally produce no output, so squelch them. + execute(command, :failonfail => fof, :squelch => true) + rescue Puppet::ExecutionFailure => detail + @resource.fail "Could not #{type} #{@resource.ref}: #{detail}" + end + nil + end + + # Use either a specified command or the default for our provider. + def ucommand(type, fof = true) + if c = @resource[type] + cmd = [c] + else + cmd = [send("#{type}cmd")].flatten + end + texecute(type, cmd, fof) + end +end + diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb index d8ba69961..1e58843bf 100644 --- a/lib/puppet/provider/service/windows.rb +++ b/lib/puppet/provider/service/windows.rb @@ -1,113 +1,106 @@ # Windows Service Control Manager (SCM) provider -require 'win32/service' if Puppet.features.microsoft_windows? - -Puppet::Type.type(:service).provide :windows do +Puppet::Type.type(:service).provide :windows, :parent => :service do desc <<-EOT Support for Windows Service Control Manager (SCM). This provider can start, stop, enable, and disable services, and the SCM provides working status methods for all services. Control of service groups (dependencies) is not yet supported, nor is running services as a specific user. EOT defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_feature :refreshable commands :net => 'net.exe' def enable w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START ) raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue Win32::Service::Error => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}" ) end def disable w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED ) raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue Win32::Service::Error => detail raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}" ) end def manual_start w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START ) raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? ) rescue Win32::Service::Error => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}" ) end def enabled? w32ss = Win32::Service.config_info( @resource[:name] ) raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) ) debug("Service #{@resource[:name]} start type is #{w32ss.start_type}") case w32ss.start_type when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START), Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START), Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START) :true when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START) :manual when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) :false else raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}") end rescue Win32::Service::Error => detail raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}" ) end def start if enabled? == :false # If disabled and not managing enable, respect disabled and fail. if @resource[:enable].nil? raise Puppet::Error, "Will not start disabled service #{@resource[:name]} without managing enable. Specify 'enable => false' to override." # Otherwise start. If enable => false, we will later sync enable and # disable the service again. elsif @resource[:enable] == :true enable else manual_start end end net(:start, @resource[:name]) rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" ) end def stop net(:stop, @resource[:name]) rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot stop #{@resource[:name]}, error was: #{detail}" ) end - def restart - self.stop - self.start - end - def status w32ss = Win32::Service.status( @resource[:name] ) raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) ) state = case w32ss.current_state when "stopped", "pause pending", "stop pending", "paused" then :stopped when "running", "continue pending", "start pending" then :running else raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'") end debug("Service #{@resource[:name]} is #{w32ss.current_state}") return state rescue Win32::Service::Error => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}" ) end # returns all providers for all existing services and startup state def self.instances Win32::Service.services.collect { |s| new(:name => s.service_name) } end end diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index 038c91c4e..a7ff108a0 100755 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -1,164 +1,183 @@ #!/usr/bin/env rspec # # Unit testing for the Windows service Provider # require 'spec_helper' require 'win32/service' if Puppet.features.microsoft_windows? describe Puppet::Type.type(:service).provider(:windows), :if => Puppet.features.microsoft_windows? do let(:name) { 'nonexistentservice' } let(:resource) { Puppet::Type.type(:service).new(:name => name, :provider => :windows) } let(:provider) { resource.provider } let(:config) { Struct::ServiceConfigInfo.new } let(:status) { Struct::ServiceStatus.new } before :each do # make sure we never actually execute anything (there are two execute methods) provider.class.expects(:execute).never provider.expects(:execute).never Win32::Service.stubs(:config_info).with(name).returns(config) Win32::Service.stubs(:status).with(name).returns(status) end describe ".instances" do it "should enumerate all services" do list_of_services = ['snmptrap', 'svchost', 'sshd'].map { |s| stub('service', :service_name => s) } Win32::Service.expects(:services).returns(list_of_services) described_class.instances.map(&:name).should =~ ['snmptrap', 'svchost', 'sshd'] end end describe "#start" do before :each do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START) end it "should start the service" do provider.expects(:net).with(:start, name) provider.start end it "should raise an error if the start command fails" do provider.expects(:net).with(:start, name).raises(Puppet::ExecutionFailure, "The service name is invalid.") expect { provider.start }.to raise_error(Puppet::Error, /Cannot start #{name}, error was: The service name is invalid./) end describe "when the service is disabled" do before :each do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) end it "should refuse to start if not managing enable" do expect { provider.start }.to raise_error(Puppet::Error, /Will not start disabled service/) end it "should enable if managing enable and enable is true" do resource[:enable] = :true provider.expects(:net).with(:start, name) Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) provider.start end it "should manual start if managing enable and enable is false" do resource[:enable] = :false provider.expects(:net).with(:start, name) Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) provider.start end end end describe "#stop" do it "should stop a running service" do provider.expects(:net).with(:stop, name) provider.stop end it "should raise an error if the stop command fails" do provider.expects(:net).with(:stop, name).raises(Puppet::ExecutionFailure, 'The service name is invalid.') expect { provider.stop }.to raise_error(Puppet::Error, /Cannot stop #{name}, error was: The service name is invalid./) end end describe "#status" do ['stopped', 'paused', 'stop pending', 'pause pending'].each do |state| it "should report a #{state} service as stopped" do status.current_state = state provider.status.should == :stopped end end ["running", "continue pending", "start pending" ].each do |state| it "should report a #{state} service as running" do status.current_state = state provider.status.should == :running end end end + describe "#restart" do + it "should use the supplied restart command if specified" do + resource[:restart] = 'c:/bin/foo' + + provider.expects(:execute).never + provider.expects(:execute).with(['c:/bin/foo'], :failonfail => true, :squelch => true) + + provider.restart + end + + it "should restart the service" do + seq = sequence("restarting") + provider.expects(:stop).in_sequence(seq) + provider.expects(:start).in_sequence(seq) + + provider.restart + end + end + describe "#enabled?" do it "should report a service with a startup type of manual as manual" do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START) provider.enabled?.should == :manual end it "should report a service with a startup type of disabled as false" do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) provider.enabled?.should == :false end # We need to guard this section explicitly since rspec will always # construct all examples, even if it isn't going to run them. if Puppet.features.microsoft_windows? [Win32::Service::SERVICE_AUTO_START, Win32::Service::SERVICE_BOOT_START, Win32::Service::SERVICE_SYSTEM_START].each do |start_type_const| start_type = Win32::Service.get_start_type(start_type_const) it "should report a service with a startup type of '#{start_type}' as true" do config.start_type = start_type provider.enabled?.should == :true end end end end describe "#enable" do it "should set service start type to Service_Auto_Start when enabled" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) provider.enable end end describe "#disable" do it "should set service start type to Service_Disabled when disabled" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DISABLED).returns(Win32::Service) provider.disable end end describe "#manual_start" do it "should set service start type to Service_Demand_Start (manual) when manual" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) provider.manual_start end end end