diff --git a/acceptance/tests/ticket_13489_service_refresh.pp b/acceptance/tests/ticket_13489_service_refresh.pp new file mode 100644 index 000000000..ba3e48600 --- /dev/null +++ b/acceptance/tests/ticket_13489_service_refresh.pp @@ -0,0 +1,20 @@ +test_name "#13489: refresh service" + +confine :to, :platform => 'windows' + +manifest = < 'running', +} + +exec { 'hello': + command => "cmd /c echo hello", + path => $::path, + logoutput => true, +} + +Exec['hello'] ~> Service['BITS'] +MANIFEST + +step "Refresh service" +apply_manifest_on(agents, manifest) diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb index 717e585fd..d8ba69961 100644 --- a/lib/puppet/provider/service/windows.rb +++ b/lib/puppet/provider/service/windows.rb @@ -1,111 +1,113 @@ # Windows Service Control Manager (SCM) provider require 'win32/service' if Puppet.features.microsoft_windows? Puppet::Type.type(:service).provide :windows 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 - Win32::Service.start( @resource[:name] ) - rescue Win32::Service::Error => detail + net(:start, @resource[:name]) + rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" ) end def stop - Win32::Service.stop( @resource[:name] ) - rescue Win32::Service::Error => detail + 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 07e4f4d1a..b271adff2 100755 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -1,166 +1,164 @@ #!/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 - before :each do - @resource = Puppet::Type.type(:service).new(:name => 'snmptrap', :provider => :windows) + # make sure we don't try to get/set status for a real service + @resource = Puppet::Type.type(:service).new(:name => 'nonexistentservice', :provider => :windows) + + # make sure we never actually execute anything + @resource.provider.class.expects(:execute).never @config = Struct::ServiceConfigInfo.new @status = Struct::ServiceStatus.new Win32::Service.stubs(:config_info).with(@resource[:name]).returns(@config) Win32::Service.stubs(:status).with(@resource[: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 - it "should call out to the Win32::Service API to start the service" do + before :each do @config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START) + end - Win32::Service.expects(:start).with( @resource[:name] ) + it "should start the service" do + @resource.provider.expects(:net).with(:start, @resource[:name]) @resource.provider.start end - it "should handle when Win32::Service.start raises a Win32::Service::Error" do - @config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START) - - Win32::Service.expects(:start).with( @resource[:name] ).raises( - Win32::Service::Error.new("The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.") - ) + it "should raise an error if the start command fails" do + @resource.provider.expects(:net).with(:start, @resource[:name]).raises(Puppet::ExecutionFailure, "The service name is invalid.") - expect { @resource.provider.start }.to raise_error( - Puppet::Error, - /Cannot start .*, error was: The service cannot be started, either/ - ) + expect { + @resource.provider.start + }.to raise_error(Puppet::Error, /Cannot start #{@resource[: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) - Win32::Service.stubs(:start).with(@resource[:name]) end it "should refuse to start if not managing enable" do expect { @resource.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 + @resource.provider.expects(:net).with(:start, @resource[:name]) Win32::Service.expects(:configure).with('service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) @resource.provider.start end it "should manual start if managing enable and enable is false" do @resource[:enable] = :false + @resource.provider.expects(:net).with(:start, @resource[:name]) Win32::Service.expects(:configure).with('service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) @resource.provider.start end end end describe "#stop" do - it "should call out to the Win32::Service API to stop the service" do - Win32::Service.expects(:stop).with( @resource[:name] ) + it "should stop a running service" do + @resource.provider.expects(:net).with(:stop, @resource[:name]) + @resource.provider.stop - end + end - it "should handle when Win32::Service.stop raises a Win32::Service::Error" do - Win32::Service.expects(:stop).with( @resource[:name] ).raises( - Win32::Service::Error.new("should not try to stop an already stopped service.") - ) + it "should raise an error if the stop command fails" do + @resource.provider.expects(:net).with(:stop, @resource[:name]).raises(Puppet::ExecutionFailure, 'The service name is invalid.') - expect { @resource.provider.stop }.to raise_error( - Puppet::Error, - /Cannot stop .*, error was: should not try to stop an already stopped service/ - ) + expect { + @resource.provider.stop + }.to raise_error(Puppet::Error, /Cannot stop #{@resource[: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 @resource.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 @resource.provider.status.should == :running end 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) @resource.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) @resource.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 @resource.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' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) @resource.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' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED).returns(Win32::Service) @resource.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' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) @resource.provider.manual_start end end - end