diff --git a/acceptance/tests/resource/service/ticket_1343_should_add_and_remove_symlinks.rb b/acceptance/tests/resource/service/ticket_1343_should_add_and_remove_symlinks.rb new file mode 100644 index 000000000..0ff6ad23c --- /dev/null +++ b/acceptance/tests/resource/service/ticket_1343_should_add_and_remove_symlinks.rb @@ -0,0 +1,54 @@ +test_name 'RedHat Service Symlink Validation' + +manifest_httpd_setup = %Q{ + package { 'httpd': + ensure => present, + } +} + +manifest_httpd_enabled = %Q{ + package { 'httpd': + ensure => present, + } + service { 'httpd': + enable => true, + } +} + +manifest_httpd_disabled = %Q{ + package { 'httpd': + ensure => present, + } + service { 'httpd': + enable => false, + } +} + +init_script = "/etc/init.d/httpd" +start_symlink = "S85httpd" +kill_symlink = "K15httpd" + +start_runlevels = ["2", "3", "4", "5"] +kill_runlevels = ["0", "1", "6"] + +agents.each do |agent| + step "setting up test preconditions" + apply_manifest_on(agent, manifest_httpd_setup, :catch_failures => true) do + # chkconfig --del will remove all rc.d symlinks for this service + on agent, "chkconfig --del httpd" + end + + step "ensure enabling httpd creates the S-symlinks" + apply_manifest_on(agent, manifest_httpd_enabled, :catch_failures => true) do + start_runlevels.each do |runlevel| + on agent, "test -L /etc/rc.d/rc#{runlevel}.d/#{start_symlink} && test -f #{init_script}" + end + end + + step "ensure enabling httpd creates the K-symlinks" + apply_manifest_on(agent, manifest_httpd_enabled, :catch_failures => true) do + kill_runlevels.each do |runlevel| + on agent, "test -L /etc/rc.d/rc#{runlevel}.d/#{kill_symlink} && test -f #{init_script}" + end + end +end diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index ea59d3768..6e49a8f84 100644 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -1,67 +1,70 @@ # Manage Red Hat services. Start/stop uses /sbin/service and enable/disable uses chkconfig Puppet::Type.type(:service).provide :redhat, :parent => :init, :source => :init do desc "Red Hat's (and probably many others') form of `init`-style service management. Uses `chkconfig` for service enabling and disabling. " commands :chkconfig => "/sbin/chkconfig", :service => "/sbin/service" defaultfor :osfamily => [:redhat, :suse] # Remove the symlinks def disable # The off method operates on run levels 2,3,4 and 5 by default We ensure # all run levels are turned off because the reset method may turn on the # service in run levels 0, 1 and/or 6 - output = chkconfig("--level", "0123456", @resource[:name], :off) - rescue Puppet::ExecutionFailure - raise Puppet::Error, "Could not disable #{self.name}: #{output}", $!.backtrace + # We're not using --del here because we want to disable the service only, + # and --del removes the service from chkconfig management + chkconfig("--level", "0123456", @resource[:name], :off) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not disable #{self.name}: #{detail}", detail.backtrace end def enabled? name = @resource[:name] begin output = chkconfig name rescue Puppet::ExecutionFailure return :false end # For Suse OS family, chkconfig returns 0 even if the service is disabled or non-existent # Therefore, check the output for ' on' to see if it is enabled return :false unless Facter.value(:osfamily) != 'Suse' || output =~ /^#{name}\s+on$/ :true end # Don't support them specifying runlevels; always use the runlevels # in the init scripts. def enable + chkconfig("--add", @resource[:name]) chkconfig(@resource[:name], :on) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not enable #{self.name}: #{detail}", detail.backtrace end def initscript raise Puppet::Error, "Do not directly call the init script for '#{@resource[:name]}'; use 'service' instead" end # use hasstatus=>true when its set for the provider. def statuscmd ((@resource.provider.get(:hasstatus) == true) || (@resource[:hasstatus] == :true)) && [command(:service), @resource[:name], "status"] end def restartcmd (@resource[:hasrestart] == :true) && [command(:service), @resource[:name], "restart"] end def startcmd [command(:service), @resource[:name], "start"] end def stopcmd [command(:service), @resource[:name], "stop"] end end diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb index f60c3ae84..108a28593 100755 --- a/spec/unit/provider/service/redhat_spec.rb +++ b/spec/unit/provider/service/redhat_spec.rb @@ -1,162 +1,163 @@ #! /usr/bin/env ruby # # Unit testing for the RedHat service Provider # require 'spec_helper' provider_class = Puppet::Type.type(:service).provider(:redhat) describe provider_class, :as_platform => :posix do before :each do @class = Puppet::Type.type(:service).provider(:redhat) @resource = stub 'resource' @resource.stubs(:[]).returns(nil) @resource.stubs(:[]).with(:name).returns "myservice" @provider = provider_class.new @resource.stubs(:provider).returns @provider @provider.resource = @resource @provider.stubs(:get).with(:hasstatus).returns false FileTest.stubs(:file?).with('/sbin/service').returns true FileTest.stubs(:executable?).with('/sbin/service').returns true Facter.stubs(:value).with(:operatingsystem).returns('CentOS') end osfamily = [ 'RedHat', 'Suse' ] osfamily.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.expects(:value).with(:osfamily).returns(osfamily) provider_class.default?.should be_true end end # test self.instances describe "when getting all service instances" do before :each do @services = ['one', 'two', 'three', 'four', 'kudzu', 'functions', 'halt', 'killall', 'single', 'linuxconf', 'boot', 'reboot'] @not_services = ['functions', 'halt', 'killall', 'single', 'linuxconf', 'reboot', 'boot'] Dir.stubs(:entries).returns @services FileTest.stubs(:directory?).returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do (@services-@not_services).each do |inst| @class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") end results = (@services-@not_services).collect {|x| "#{x}_instance"} @class.instances.should == results end it "should call service status when initialized from provider" do @resource.stubs(:[]).with(:status).returns nil @provider.stubs(:get).with(:hasstatus).returns true @provider.expects(:execute).with{|command, *args| command == ['/sbin/service', 'myservice', 'status']} @provider.send(:status) end end - it "(#15797) should use 'on' when calling enable" do + it "should use '--add' and 'on' when calling enable" do + provider_class.expects(:chkconfig).with("--add", @resource[:name]) provider_class.expects(:chkconfig).with(@resource[:name], :on) @provider.enable end it "(#15797) should explicitly turn off the service in all run levels" do provider_class.expects(:chkconfig).with("--level", "0123456", @resource[:name], :off) @provider.disable end it "should have an enabled? method" do @provider.should respond_to(:enabled?) end describe "when checking enabled? on Suse" do before :each do Facter.expects(:value).with(:osfamily).returns 'Suse' end it "should check for on" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} on" @provider.enabled?.should == :true end it "should check for off" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} off" @provider.enabled?.should == :false end it "should check for unknown service" do provider_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]}: unknown service" @provider.enabled?.should == :false end end it "should have an enable method" do @provider.should respond_to(:enable) end it "should have a disable method" do @provider.should respond_to(:disable) end [:start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do @provider.should respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do @resource.stubs(:[]).with(method).returns "/user/specified/command" @provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } @provider.send(method) end it "should execute the service script with #{method} when no explicit command is provided" do @resource.stubs(:[]).with("has#{method}".intern).returns :true @provider.expects(:execute).with { |command, *args| command == ['/sbin/service', 'myservice', method.to_s]} @provider.send(method) end end end describe "when checking status" do describe "when hasstatus is :true" do before :each do @resource.stubs(:[]).with(:hasstatus).returns :true end it "should execute the service script with fail_on_failure false" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) @provider.status end it "should consider the process running if the command returns 0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(0) @provider.status.should == :running end [-10,-1,1,10].each { |ec| it "should consider the process stopped if the command returns something non-0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(ec) @provider.status.should == :stopped end } end describe "when hasstatus is not :true" do it "should consider the service :running if it has a pid" do @provider.expects(:getpid).returns "1234" @provider.status.should == :running end it "should consider the service :stopped if it doesn't have a pid" do @provider.expects(:getpid).returns nil @provider.status.should == :stopped end end end describe "when restarting and hasrestart is not :true" do it "should stop and restart the process with the server script" do @provider.expects(:texecute).with(:stop, ['/sbin/service', 'myservice', 'stop'], true) @provider.expects(:texecute).with(:start, ['/sbin/service', 'myservice', 'start'], true) @provider.restart end end end