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
--
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.