diff --git a/lib/puppet/provider/service/systemd.rb b/lib/puppet/provider/service/systemd.rb index 39ef5eaf0..d9bae15b5 100644 --- a/lib/puppet/provider/service/systemd.rb +++ b/lib/puppet/provider/service/systemd.rb @@ -1,67 +1,97 @@ # Manage systemd services using /bin/systemctl Puppet::Type.type(:service).provide :systemd, :parent => :base do desc "Manages `systemd` services using `systemctl`." commands :systemctl => "systemctl" defaultfor :osfamily => [:archlinux] defaultfor :osfamily => :redhat, :operatingsystemmajrelease => "7" defaultfor :osfamily => :redhat, :operatingsystem => :fedora, :operatingsystemmajrelease => ["17", "18", "19", "20", "21"] defaultfor :osfamily => :suse, :operatingsystemmajrelease => ["12", "13"] def self.instances i = [] output = systemctl('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager') - output.scan(/^(\S+)\s+(disabled|enabled)\s*$/i).each do |m| + output.scan(/^(\S+)\s+(disabled|enabled|masked)\s*$/i).each do |m| i << new(:name => m[0]) end return i rescue Puppet::ExecutionFailure return [] end def disable output = systemctl(:disable, @resource[:name]) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable #{self.name}: #{output}", $!.backtrace end def enabled? begin - systemctl("is-enabled", @resource[:name]) + systemctl_info = systemctl( + 'show', + @resource[:name], + '--property', 'LoadState', + '--property', 'UnitFileState', + '--no-pager' + ) + + svc_info = Hash.new + systemctl_info.split.map{|svc| + entry_pair = svc.split('=') + svc_info[entry_pair.first.to_sym] = entry_pair.last + } + + # The masked state is equivalent to the disabled state in terms of + # comparison so we only care to check if it is masked if we want to keep + # it masked. + # + # We only return :mask if we're trying to mask the service. This prevents + # flapping when simply trying to disable a masked service. + return :mask if (@resource[:enable] == :mask) && (svc_info[:LoadState] == 'masked') + return :true if svc_info[:UnitFileState] == 'enabled' rescue Puppet::ExecutionFailure - return :false + # Don't worry about this failing, just return :false if it does. end - :true + return :false end def status begin systemctl("is-active", @resource[:name]) rescue Puppet::ExecutionFailure return :stopped end return :running end def enable + output = systemctl("unmask", @resource[:name]) output = systemctl("enable", @resource[:name]) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable #{self.name}: #{output}", $!.backtrace end + def mask + begin + output = systemctl("mask", @resource[:name]) + rescue Puppet::ExecutionFailure + raise Puppet::Error, "Could not mask #{self.name}: #{output}", $!.backtrace + end + end + def restartcmd [command(:systemctl), "restart", @resource[:name]] end def startcmd [command(:systemctl), "start", @resource[:name]] end def stopcmd [command(:systemctl), "stop", @resource[:name]] end end diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index e6b1e4d9b..87cf466e2 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -1,234 +1,244 @@ # This is our main way of managing processes right now. # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such module Puppet Type.newtype(:service) do @doc = "Manage running services. Service support unfortunately varies widely by platform --- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support is usually capable of doing the right thing, but the more information you can provide, the better behaviour you will get. Puppet 2.7 and newer expect init scripts to have a working status command. If this isn't the case for any of your services' init scripts, you will need to set `hasstatus` to false and possibly specify a custom status command in the `status` attribute. As a last resort, Puppet will attempt to search the process table by calling whatever command is listed in the `ps` fact. The default search pattern is the name of the service, but you can specify it with the `pattern` attribute. **Refresh:** `service` resources can respond to refresh events (via `notify`, `subscribe`, or the `~>` arrow). If a `service` receives an event from another resource, Puppet will restart the service it manages. The actual command used to restart the service depends on the platform and can be configured: * If you set `hasrestart` to true, Puppet will use the init script's restart command. * You can provide an explicit command for restarting with the `restart` attribute. * If you do neither, the service's stop and start commands will be used." feature :refreshable, "The provider can restart the service.", :methods => [:restart] feature :enableable, "The provider can enable and disable the service", :methods => [:disable, :enable, :enabled?] feature :controllable, "The provider uses a control variable." feature :flaggable, "The provider can pass flags to the service." newproperty(:enable, :required_features => :enableable) do desc "Whether a service should be enabled to start at boot. This property behaves quite differently depending on the platform; wherever possible, it relies on local tools to enable or disable a given service." newvalue(:true, :event => :service_enabled) do provider.enable end newvalue(:false, :event => :service_disabled) do provider.disable end newvalue(:manual, :event => :service_manual_start) do provider.manual_start end + # This only makes sense on systemd systems. Otherwise, it just defaults + # to disable. + newvalue(:mask, :event => :service_disabled) do + if provider.respond_to?(:mask) + provider.mask + else + provider.disable + end + 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. newproperty(:ensure) do desc "Whether a service should be running." newvalue(:stopped, :event => :service_stopped) do provider.stop end newvalue(:running, :event => :service_started, :invalidate_refreshes => true) do provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve provider.status end def sync event = super() if property = @resource.property(:enable) val = property.retrieve property.sync unless property.safe_insync?(val) end event end end newproperty(:flags, :required_features => :flaggable) do desc "Specify a string of flags to pass to the startup script." end newparam(:binary) do desc "The path to the daemon. This is only used for systems that do not support init scripts. This binary will be used to start the service if no `start` parameter is provided." end newparam(:hasstatus) do desc "Declare whether the service's init script has a functional status command; defaults to `true`. This attribute's default value changed in Puppet 2.7.0. The init script's status command must return 0 if the service is running and a nonzero value otherwise. Ideally, these exit codes should conform to [the LSB's specification][lsb-exit-codes] for init script status actions, but Puppet only considers the difference between 0 and nonzero to be relevant. If a service's init script does not support any kind of status command, you should set `hasstatus` to false and either provide a specific command using the `status` attribute or expect that Puppet will look for the service name in the process table. Be aware that 'virtual' init scripts (like 'network' under Red Hat systems) will respond poorly to refresh events from other resources if you override the default behavior without providing a status command." newvalues(:true, :false) defaultto :true end newparam(:name) do desc <<-EOT The name of the service to run. This name is used to find the service; on platforms where services have short system names and long display names, this should be the short name. (To take an example from Windows, you would use "wuauserv" rather than "Automatic Updates.") EOT isnamevar end newparam(:path) do desc "The search path for finding init scripts. Multiple values should be separated by colons or provided as an array." munge do |value| value = [value] unless value.is_a?(Array) value.flatten.collect { |p| p.split(File::PATH_SEPARATOR) }.flatten end defaultto { provider.class.defpath if provider.class.respond_to?(:defpath) } end newparam(:pattern) do desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command. Defaults to the name of the service. The pattern can be a simple string or any legal Ruby pattern, including regular expressions (which should be quoted without enclosing slashes)." defaultto { @resource[:binary] || @resource[:name] } end newparam(:restart) do desc "Specify a *restart* command manually. If left unspecified, the service will be stopped and then started." end newparam(:start) do desc "Specify a *start* command manually. Most service subsystems support a `start` command, so this will not need to be specified." end newparam(:status) do desc "Specify a *status* command manually. This command must return 0 if the service is running and a nonzero value otherwise. Ideally, these exit codes should conform to [the LSB's specification][lsb-exit-codes] for init script status actions, but Puppet only considers the difference between 0 and nonzero to be relevant. If left unspecified, the status of the service will be determined automatically, usually by looking for the service in the process table. [lsb-exit-codes]: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html" end newparam(:stop) do desc "Specify a *stop* command manually." end newparam(:control) do desc "The control variable used to manage services (originally for HP-UX). Defaults to the upcased service name plus `START` replacing dots with underscores, for those providers that support the `controllable` feature." defaultto { resource.name.gsub(".","_").upcase + "_START" if resource.provider.controllable? } end newparam :hasrestart do desc "Specify that an init script has a `restart` command. If this is false and you do not specify a command in the `restart` attribute, the init script's `stop` and `start` commands will be used. Defaults to false." newvalues(:true, :false) end newparam(:manifest) do desc "Specify a command to config a service, or a path to a manifest to do so." end # Basically just a synonym for restarting. Used to respond # to events. def refresh # Only restart if we're actually running if (@parameters[:ensure] || newattr(:ensure)).retrieve == :running provider.restart else debug "Skipping restart; service is not running" end end end end diff --git a/spec/unit/provider/service/systemd_spec.rb b/spec/unit/provider/service/systemd_spec.rb index d9772b3d3..572f2d37c 100755 --- a/spec/unit/provider/service/systemd_spec.rb +++ b/spec/unit/provider/service/systemd_spec.rb @@ -1,192 +1,237 @@ #! /usr/bin/env ruby # # Unit testing for the systemd service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:systemd) do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class described_class.stubs(:which).with('systemctl').returns '/bin/systemctl' end let :provider do described_class.new(:name => 'sshd.service') end osfamily = [ 'archlinux' ] osfamily.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.expects(:value).with(:osfamily).returns(osfamily) expect(described_class.default?).to be_truthy end end it "should be the default provider on rhel7" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:redhat) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("7") expect(described_class.default?).to be_truthy end [ 4, 5, 6 ].each do |ver| it "should not be the default provider on rhel#{ver}" do # In Ruby 1.8.7, the order of hash elements differs from 1.9+ and # caused short-circuiting of the logic used by default.all? in the # provider. As a workaround we need to use stubs() instead of # expects() here. Facter.expects(:value).with(:osfamily).at_least_once.returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:redhat) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class.default?).not_to be_truthy end end [ 17, 18, 19, 20, 21 ].each do |ver| it "should be the default provider on fedora#{ver}" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:redhat) Facter.expects(:value).with(:operatingsystem).at_least_once.returns(:fedora) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("#{ver}") expect(described_class.default?).to be_truthy end end it "should be the default provider on sles12" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("12") expect(described_class.default?).to be_truthy end it "should be the default provider on opensuse13" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("13") expect(described_class.default?).to be_truthy end it "should not be the default provider on sles11" do Facter.expects(:value).with(:osfamily).at_least_once.returns(:suse) Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns("11") expect(described_class.default?).not_to be_truthy end [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do expect(provider).to respond_to(method) end end describe ".instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should return only services" do described_class.expects(:systemctl).with('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager').returns File.read(my_fixture('list_unit_files_services')) expect(described_class.instances.map(&:name)).to match_array(%w{ arp-ethers.service auditd.service autovt@.service avahi-daemon.service blk-availability.service }) end end describe "#start" do it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service with systemctl start otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','start','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end end describe "#stop" do it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service with systemctl stop otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','stop','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end end describe "#enabled?" do it "should return :true if the service is enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) - provider.expects(:systemctl).with('is-enabled', 'sshd.service').returns 'enabled' + provider.expects(:systemctl).with( + 'show', + 'sshd.service', + '--property', 'LoadState', + '--property', 'UnitFileState', + '--no-pager' + ).returns "LoadState=loaded\nUnitFileState=enabled\n" expect(provider.enabled?).to eq(:true) end it "should return :false if the service is disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) - provider.expects(:systemctl).with('is-enabled', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-enabled sshd.service' returned 1: disabled" + provider.expects(:systemctl).with( + 'show', + 'sshd.service', + '--property', 'LoadState', + '--property', 'UnitFileState', + '--no-pager' + ).returns "LoadState=loaded\nUnitFileState=disabled\n" expect(provider.enabled?).to eq(:false) end + + it "should return :false if the service is masked and the resource is attempting to be disabled" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :enable => false)) + provider.expects(:systemctl).with( + 'show', + 'sshd.service', + '--property', 'LoadState', + '--property', 'UnitFileState', + '--no-pager' + ).returns "LoadState=masked\nUnitFileState=\n" + expect(provider.enabled?).to eq(:false) + end + + it "should return :mask if the service is masked and the resource is attempting to be masked" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :enable => 'mask')) + provider.expects(:systemctl).with( + 'show', + 'sshd.service', + '--property', 'LoadState', + '--property', 'UnitFileState', + '--no-pager' + ).returns "LoadState=masked\nUnitFileState=\n" + expect(provider.enabled?).to eq(:mask) + end end describe "#enable" do it "should run systemctl enable to enable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('unmask', 'sshd.service') provider.expects(:systemctl).with('enable', 'sshd.service') provider.enable end end describe "#disable" do it "should run systemctl disable to disable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:disable, 'sshd.service') provider.disable end end + + describe "#mask" do + it "should run systemctl mask to mask a service" do + provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) + provider.expects(:systemctl).with('mask', 'sshd.service') + provider.mask + end + end # Note: systemd provider does not care about hasstatus or a custom status # command. I just assume that it does not make sense for systemd. describe "#status" do it "should return running if active" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-active', 'sshd.service').returns 'active' expect(provider.status).to eq(:running) end it "should return stopped if inactive" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with('is-active', 'sshd.service').raises Puppet::ExecutionFailure, "Execution of '/bin/systemctl is-active sshd.service' returned 3: inactive" expect(provider.status).to eq(:stopped) end end # Note: systemd provider does not care about hasrestart. I just assume it # does not make sense for systemd describe "#restart" do it "should use the supplied restart command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with systemctl restart" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end end it "(#16451) has command systemctl without being fully qualified" do expect(described_class.instance_variable_get(:@commands)). to include(:systemctl => 'systemctl') end end diff --git a/spec/unit/type/service_spec.rb b/spec/unit/type/service_spec.rb index 765085ada..c2c7b5ee6 100755 --- a/spec/unit/type/service_spec.rb +++ b/spec/unit/type/service_spec.rb @@ -1,261 +1,266 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service) do it "should have an :enableable feature that requires the :enable, :disable, and :enabled? methods" do expect(Puppet::Type.type(:service).provider_feature(:enableable).methods).to eq([:disable, :enable, :enabled?]) end it "should have a :refreshable feature that requires the :restart method" do expect(Puppet::Type.type(:service).provider_feature(:refreshable).methods).to eq([:restart]) end end describe Puppet::Type.type(:service), "when validating attributes" do [:name, :binary, :hasstatus, :path, :pattern, :start, :restart, :stop, :status, :hasrestart, :control].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:service).attrtype(param)).to eq(:param) end end [:ensure, :enable].each do |param| it "should have an #{param} property" do expect(Puppet::Type.type(:service).attrtype(param)).to eq(:property) end end end describe Puppet::Type.type(:service), "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:service).defaultprovider, :clear => nil, :controllable? => false Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(@provider) end it "should support :running as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :running) end it "should support :stopped as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :stopped) end it "should alias the value :true to :running in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => true) expect(svc.should(:ensure)).to eq(:running) end it "should alias the value :false to :stopped in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => false) expect(svc.should(:ensure)).to eq(:stopped) end describe "the enable property" do before :each do @provider.class.stubs(:supports_parameter?).returns true end it "should support :true as a value" do srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :true) expect(srv.should(:enable)).to eq(:true) end it "should support :false as a value" do srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :false) expect(srv.should(:enable)).to eq(:false) end + it "should support :mask as a value" do + srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :mask) + srv.should(:enable).should == :mask + end + it "should support :manual as a value on Windows" do Puppet.features.stubs(:microsoft_windows?).returns true srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) expect(srv.should(:enable)).to eq(:manual) end it "should not support :manual as a value 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 end it "should support :true as a value to :hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true) expect(srv[:hasstatus]).to eq(:true) end it "should support :false as a value to :hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :false) expect(srv[:hasstatus]).to eq(:false) end it "should specify :true as the default value of hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay") expect(srv[:hasstatus]).to eq(:true) end it "should support :true as a value to :hasrestart" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :true) expect(srv[:hasrestart]).to eq(:true) end it "should support :false as a value to :hasrestart" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :false) expect(srv[:hasrestart]).to eq(:false) end it "should allow setting the :enable parameter if the provider has the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) expect(svc.should(:enable)).to eq(:true) end it "should not allow setting the :enable parameter if the provider is missing the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(false) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) expect(svc.should(:enable)).to be_nil end it "should split paths on '#{File::PATH_SEPARATOR}'" do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => "/one/two#{File::PATH_SEPARATOR}/three/four") expect(svc[:path]).to eq(%w{/one/two /three/four}) end it "should accept arrays of paths joined by '#{File::PATH_SEPARATOR}'" do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => ["/one#{File::PATH_SEPARATOR}/two", "/three#{File::PATH_SEPARATOR}/four"]) expect(svc[:path]).to eq(%w{/one /two /three /four}) end end describe Puppet::Type.type(:service), "when setting default attribute values" do it "should default to the provider's default path if one is available" do FileTest.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:respond_to?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:defpath).returns("testing") svc = Puppet::Type.type(:service).new(:name => "other") expect(svc[:path]).to eq(["testing"]) end it "should default 'pattern' to the binary if one is provided" do svc = Puppet::Type.type(:service).new(:name => "other", :binary => "/some/binary") expect(svc[:pattern]).to eq("/some/binary") end it "should default 'pattern' to the name if no pattern is provided" do svc = Puppet::Type.type(:service).new(:name => "other") expect(svc[:pattern]).to eq("other") end it "should default 'control' to the upcased service name with periods replaced by underscores if the provider supports the 'controllable' feature" do provider = stub 'provider', :controllable? => true, :class => Puppet::Type.type(:service).defaultprovider, :clear => nil Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(provider) svc = Puppet::Type.type(:service).new(:name => "nfs.client") expect(svc[:control]).to eq("NFS_CLIENT_START") end end describe Puppet::Type.type(:service), "when retrieving the host's current state" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should use the provider's status to determine whether the service is running" do @service.provider.expects(:status).returns(:yepper) @service[:ensure] = :running expect(@service.property(:ensure).retrieve).to eq(:yepper) end it "should ask the provider whether it is enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:enabled?).returns(:yepper) @service[:enable] = true expect(@service.property(:enable).retrieve).to eq(:yepper) end end describe Puppet::Type.type(:service), "when changing the host" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should start the service if it is supposed to be running" do @service[:ensure] = :running @service.provider.expects(:start) @service.property(:ensure).sync end it "should stop the service if it is supposed to be stopped" do @service[:ensure] = :stopped @service.provider.expects(:stop) @service.property(:ensure).sync end it "should enable the service if it is supposed to be enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = true @service.provider.expects(:enable) @service.property(:enable).sync end it "should disable the service if it is supposed to be disabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service.provider.expects(:disable) @service.property(:enable).sync end it "should sync the service's enable state when changing the state of :ensure if :enable is being managed" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service[:ensure] = :stopped @service.property(:enable).expects(:retrieve).returns("whatever") @service.property(:enable).expects(:insync?).returns(false) @service.property(:enable).expects(:sync) @service.provider.stubs(:stop) @service.property(:ensure).sync end end describe Puppet::Type.type(:service), "when refreshing the service" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should restart the service if it is running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should restart the service if it is running, even if it is supposed to stopped" do @service[:ensure] = :stopped @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should not restart the service if it is not running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:stopped) @service.refresh end it "should add :ensure as a property if it is not being managed" do @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end end