Unfortunately this patch went out prior to integration into master, and the spec test broke the general (non-Windows) spec tests. The correction is in-progress, and will be issued once verified.
Mea culpa - Cameron Thomas On Fri, Jul 22, 2011 at 3:22 PM, Cameron Thomas <[email protected]>wrote: > This provider allows us to query the system state through "puppet > resource", and manage the ensure, and enabled properties of services on > Windows. > > This also adds support for a new enabled value of 'manual' on Windows > only. With this we support the three major start types for services on > Windows, with the following mapping of enabled to start type: > > true => Automatic > false => Disabled > manual => Manual (Demand) > > We use the win32-service gem to provide access to the Windows APIs for > our operations. This does add a new gem requirement for running Puppet > on Windows, but we were already requiring some gems from the same suite > that win32-service is a part of. > > When referring to a service, the simple service name must be used, > instead of the display name. For example, "snmptrap", instead of > "SNMP Trap". > > All system services are reported in 'puppet resource service', > including those started prior to run level 3 (system, device drivers, > etc.). These services should probably not be managed, without careful > thought and planning. > > This currently does not support being able to move a service from > {enabled => false, ensure => stopped} to {enabled => true, ensure => > running} (or enabled => manual) in a single Puppet run, since Puppet > currently always tries to sync ensure before any other property. > Because of this, the puppet run will fail every time, and the service > must first be managed as {ensure => stopped, enabled => true} (or > enabled => manual), before it can be managed as running and automatic > start or manual start. > > Reviewed by: Jacob Helwig <[email protected]> > > Signed-off-by: Cameron Thomas <[email protected]> > --- > Local-branch: feature/master/8272-windows_service_support > lib/puppet/feature/base.rb | 3 +- > lib/puppet/provider/service/windows.rb | 101 ++++++++++++++++++++++ > lib/puppet/type/service.rb | 10 ++ > spec/unit/provider/service/windows_spec.rb | 126 > ++++++++++++++++++++++++++++ > spec/unit/type/service_spec.rb | 15 ++++ > 5 files changed, 254 insertions(+), 1 deletions(-) > create mode 100644 lib/puppet/provider/service/windows.rb > create mode 100755 spec/unit/provider/service/windows_spec.rb > > diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb > index 9ed3dee..b4b1313 100644 > --- a/lib/puppet/feature/base.rb > +++ b/lib/puppet/feature/base.rb > @@ -48,8 +48,9 @@ Puppet.features.add(:microsoft_windows) do > require 'sys/admin' > require 'win32/process' > require 'win32/dir' > + require 'win32/service' > rescue LoadError => err > - warn "Cannot run on Microsoft Windows without the sys-admin, > win32-process & win32-dir gems: #{err}" unless Puppet.features.posix? > + warn "Cannot run on Microsoft Windows without the sys-admin, > win32-process, win32-dir & win32-service gems: #{err}" unless > Puppet.features.posix? > end > end > > diff --git a/lib/puppet/provider/service/windows.rb > b/lib/puppet/provider/service/windows.rb > new file mode 100644 > index 0000000..09754ff > --- /dev/null > +++ b/lib/puppet/provider/service/windows.rb > @@ -0,0 +1,101 @@ > +# Windows Service Control Manager (SCM) provider > + > +require 'win32/service' if Puppet.features.microsoft_windows? > + > +Puppet::Type.type(:service).provide :windows do > + > + desc "Support for Windows Service Control Manager (SCM). > + > + Services are controlled according to win32-service gem. > + > + * All SCM operations (start/stop/enable/disable/query) are supported. > + > + * Control of service groups (dependencies) is not yet supported." > + > + defaultfor :operatingsystem => :windows > + confine :operatingsystem => :windows > + > + has_feature :refreshable > + > + 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 ) ) > + Puppet.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 > + Win32::Service.start( @resource[:name] ) > + rescue Win32::Service::Error => 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 > + raise Puppet::Error.new("Cannot start #{@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 > + Puppet.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 > + srvcs = [] > + Win32::Service.services.collect{ |s| > + srvcs << new(:name => s.service_name) > + } > + srvcs > + end > +end > diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb > index 5a2c69b..53ff728 100644 > --- a/lib/puppet/type/service.rb > +++ b/lib/puppet/type/service.rb > @@ -49,9 +49,19 @@ module Puppet > provider.disable > end > > + newvalue(:manual, :event => :service_manual_start) do > + provider.manual_start > + end > + > def retrieve > provider.enabled? > end > + > + validate do |value| > + if value == :manual and !Puppet.features.microsoft_windows? > + raise Puppet::Error.new("Setting enable to manual is only > supported on Microsoft Windows.") > + end > + end > end > > # Handle whether the service should actually be running right now. > diff --git a/spec/unit/provider/service/windows_spec.rb > b/spec/unit/provider/service/windows_spec.rb > new file mode 100755 > index 0000000..4ef2a3a > --- /dev/null > +++ b/spec/unit/provider/service/windows_spec.rb > @@ -0,0 +1,126 @@ > +#!/usr/bin/env rspec > +# > +# Unit testing for the Windows service Provider > +# > + > +require 'spec_helper' > + > +require 'ostruct' > +require 'win32/service' > + > +provider_class = Puppet::Type.type(:service).provider(:windows) > + > +describe provider_class do > + > + before :each do > + @provider = Puppet::Type.type(:service).provider(:windows) > + > Puppet::Type.type(:service).stubs(:provider).returns(@provider) > + end > + > + describe ".instances" do > + it "should enumerate all services" do > + list_of_services = ['snmptrap', 'svchost', > 'sshd'].map {|s| OpenStruct.new(:service_name => s)} > + > Win32::Service.expects(:services).returns(list_of_services) > + > + provider_class.instances.map {|provider| > provider.name}.should =~ ['snmptrap', 'svchost', 'sshd'] > + end > + end > + > + describe "#start" do > + it "should call out to the Win32::Service API to start the > service" do > + Win32::Service.expects(:start).with('snmptrap') > + > + resource = Puppet::Type.type(:service).new(:name => > 'snmptrap') > + resource.provider.start > + end > + > + it "should handle when Win32::Service.start raises a > Win32::Service::Error" do > + > Win32::Service.expects(:start).with('snmptrap').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.") > + ) > + > + resource = Puppet::Type.type(:service).new(:name => > 'snmptrap') > + expect { resource.provider.start }.to raise_error( > + Puppet::Error, > + /Cannot start snmptrap, error was: The > service cannot be started, either/ > + ) > + end > + end > + > + describe "#stop" do > + it "should stop a running service" > + it "should not try to stop an already stopped service" > + end > + > + describe "#status" do > + ['stopped', 'paused', 'stop pending', 'pause pending'].each > do |state| > + it "should report a #{state} service as stopped" do > + > Win32::Service.expects(:status).with('snmptrap').returns( > + stub( > + 'struct_service_status', > + :instance_of? => > Struct::ServiceStatus, > + :current_state => state > + ) > + ) > + resource = > Puppet::Type.type(:service).new(:name => 'snmptrap') > + > + resource.provider.status.should == :stopped > + end > + end > + > + ["running", "continue pending", "start pending" ].each do > |state| > + it "should report a #{state} service as running" do > + > Win32::Service.expects(:status).with('snmptrap').returns( > + stub( > + 'struct_service_status', > + :instance_of? => > Struct::ServiceStatus, > + :current_state => state > + ) > + ) > + resource = > Puppet::Type.type(:service).new(:name => 'snmptrap') > + 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 > + > Win32::Service.expects(:config_info).with('snmptrap').returns( > + stub( > + 'struct_config_info', > + :instance_of? => > Struct::ServiceConfigInfo, > + :start_type => > Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START) > + ) > + ) > + resource = Puppet::Type.type(:service).new(:name => > 'snmptrap') > + resource.provider.enabled?.should == :manual > + end > + > + it "should report a service with a startup type of disabled > as false" do > + > Win32::Service.expects(:config_info).with('snmptrap').returns( > + stub( > + 'struct_config_info', > + :instance_of? => > Struct::ServiceConfigInfo, > + :start_type => > Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) > + ) > + ) > + resource = Puppet::Type.type(:service).new(:name => > 'snmptrap') > + resource.provider.enabled?.should == false > + end > + > + [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 > + > Win32::Service.expects(:config_info).with('snmptrap').returns( > + stub( > + 'struct_config_info', > + :instance_of? => > Struct::ServiceConfigInfo, > + :start_type => start_type > + ) > + ) > + resource = > Puppet::Type.type(:service).new(:name => 'snmptrap') > + resource.provider.enabled?.should == true > + end > + end > + end > +end > diff --git a/spec/unit/type/service_spec.rb > b/spec/unit/type/service_spec.rb > index 40270e7..ab006a4 100755 > --- a/spec/unit/type/service_spec.rb > +++ b/spec/unit/type/service_spec.rb > @@ -57,6 +57,21 @@ describe Puppet::Type.type(:service), "when validating > attribute values" do > Puppet::Type.type(:service).new(:name => "yay", :enable => :false) > end > > + it "should support :manual as a value to :enable on Windows" do > + Puppet.features.stubs(:microsoft_windows?).returns true > + > + Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) > + end > + > + it "should not support :manual as a value to :enable when not on > Windows" do > + Puppet.features.stubs(:microsoft_windows?).returns false > + > + expect { Puppet::Type.type(:service).new(:name => "yay", :enable => > :manual) }.to raise_error( > + Puppet::Error, > + /Setting enable to manual is only supported on Microsoft Windows\./ > + ) > + end > + > it "should support :true as a value to :hasstatus" do > Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true) > end > -- > 1.7.5.4 > > -- Join us for PuppetConf <http://bit.ly/puppetconfsig>, September 22nd and 23rd in Portland, OR. | bit.ly/puppetconfsig -- You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.
