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.

Reply via email to