diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb index c084ffbc9..1a543bc38 100644 --- a/lib/puppet/provider/service/windows.rb +++ b/lib/puppet/provider/service/windows.rb @@ -1,106 +1,106 @@ # Windows Service Control Manager (SCM) provider 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 + rescue SystemCallError => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}", 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 + rescue SystemCallError => detail raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}", 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 + rescue SystemCallError => detail raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}", 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 + rescue SystemCallError => detail raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}", 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}", detail ) end def stop net(:stop, @resource[:name]) rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot stop #{@resource[:name]}, error was: #{detail}", detail ) 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 + rescue SystemCallError => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}", 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/integration/provider/service/windows_spec.rb b/spec/integration/provider/service/windows_spec.rb new file mode 100644 index 000000000..79d18144d --- /dev/null +++ b/spec/integration/provider/service/windows_spec.rb @@ -0,0 +1,48 @@ +#! /usr/bin/env ruby +require 'spec_helper' + +describe Puppet::Type.type(:service).provider(:windows), '(integration)', + :if => Puppet.features.microsoft_windows? do + + require 'puppet/util/windows' + + before :each do + Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class + end + + context 'should fail querying services that do not exist' do + let(:service) do + Puppet::Type.type(:service).new(:name => 'foobarservice1234') + end + + it "with a Puppet::Error when querying enabled?" do + expect { service.provider.enabled? }.to raise_error(Puppet::Error) + end + + it "with a Puppet::Error when querying status" do + expect { service.provider.status }.to raise_error(Puppet::Error) + end + end + + context 'should return valid values when querying a service that does exist' do + let(:service) do + Puppet::Type.type(:service).new(:name => 'lmhosts') + end + + it "with a valid boolean when asked if enabled" do + expect([:true, :false]).to include(service.provider.enabled?) + end + + it "with a valid status when asked about status" do + expect([ + :running, + :'continue pending', + :'pause pending', + :paused, + :running, + :'start pending', + :'stop pending', + :stopped]).to include(service.provider.status) + end + end +end diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index bb883bdf5..3ecff6aa3 100755 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -1,183 +1,231 @@ #! /usr/bin/env ruby # # 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 + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService') + + expect { + provider.start + }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/) + 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 + + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:status).with(name).raises(SystemCallError, 'OpenService') + + expect { + provider.status + }.to raise_error(Puppet::Error, /Cannot get status of #{name}/) + 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, :override_locale => false, :squelch => false, :combine => 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 + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService') + + expect { + provider.enabled? + }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/) + 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 + + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') + + expect { + provider.enable + }.to raise_error(Puppet::Error, /Cannot enable #{name}/) + 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 + + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') + + expect { + provider.disable + }.to raise_error(Puppet::Error, /Cannot disable #{name}/) + 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 + + it "raises an error if the service doesn't exist" do + Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') + + expect { + provider.manual_start + }.to raise_error(Puppet::Error, /Cannot enable #{name}/) + end end end