diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 19e171571..cd8372d48 100644 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -1,164 +1,167 @@ # The standard init-based service type. Many other service types are # customizations of this module. Puppet::Type.type(:service).provide :init, :parent => :base do desc "Standard `init`-style service management." def self.defpath case Facter.value(:operatingsystem) when "FreeBSD", "DragonFly" ["/etc/rc.d", "/usr/local/etc/rc.d"] when "HP-UX" "/sbin/init.d" when "Archlinux" "/etc/rc.d" else "/etc/init.d" end end # We can't confine this here, because the init path can be overridden. #confine :exists => defpath # some init scripts are not safe to execute, e.g. we do not want # to suddently run /etc/init.d/reboot.sh status and reboot our system. The # exclude list could be platform agnostic but I assume an invalid init script # on system A will never be a valid init script on system B def self.excludes excludes = [] # these exclude list was found with grep -L '\/sbin\/runscript' /etc/init.d/* on gentoo excludes += %w{functions.sh reboot.sh shutdown.sh} # this exclude list is all from /sbin/service (5.x), but I did not exclude kudzu excludes += %w{functions halt killall single linuxconf reboot boot} # 'wait-for-state' and 'portmap-wait' are excluded from instances here # because they take parameters that have unclear meaning. It looks like # 'wait-for-state' is a generic waiter mainly used internally for other # upstart services as a 'sleep until something happens' # (http://lists.debian.org/debian-devel/2012/02/msg01139.html), while # 'portmap-wait' is a specific instance of a waiter. There is an open # launchpad bug # (https://bugs.launchpad.net/ubuntu/+source/upstart/+bug/962047) that may # eventually explain how to use the wait-for-state service or perhaps why # it should remain excluded. When that bug is adddressed this should be # reexamined. excludes += %w{wait-for-state portmap-wait} # these excludes were found with grep -r -L start /etc/init.d excludes += %w{rcS module-init-tools} # Prevent puppet failing to get status of the new service introduced # by the fix for this (bug https://bugs.launchpad.net/ubuntu/+source/lightdm/+bug/982889) # due to puppet's inability to deal with upstart services with instances. excludes += %w{plymouth-ready} # Prevent puppet failing to get status of these services, which need parameters # passed in (see https://bugs.launchpad.net/ubuntu/+source/puppet/+bug/1276766). excludes += %w{idmapd-mounting startpar-bridge} + # Prevent puppet failing to get status of cryptdisks-udev, another upstart + # service with instances + excludes += %w{cryptdisks-udev} end # List all services of this type. def self.instances get_services(self.defpath) end def self.get_services(defpath, exclude = self.excludes) defpath = [defpath] unless defpath.is_a? Array instances = [] defpath.each do |path| unless FileTest.directory?(path) Puppet.debug "Service path #{path} does not exist" next end check = [:ensure] check << :enable if public_method_defined? :enabled? Dir.entries(path).each do |name| fullpath = File.join(path, name) next if name =~ /^\./ next if exclude.include? name next if not FileTest.executable?(fullpath) next if not is_init?(fullpath) instances << new(:name => name, :path => path, :hasstatus => true) end end instances end # Mark that our init script supports 'status' commands. def hasstatus=(value) case value when true, "true"; @parameters[:hasstatus] = true when false, "false"; @parameters[:hasstatus] = false else raise Puppet::Error, "Invalid 'hasstatus' value #{value.inspect}" end end # Where is our init script? def initscript @initscript ||= self.search(@resource[:name]) end def paths @paths ||= @resource[:path].find_all do |path| if File.directory?(path) true else if Puppet::FileSystem.exist?(path) self.debug "Search path #{path} is not a directory" else self.debug "Search path #{path} does not exist" end false end end end def search(name) paths.each do |path| fqname = File.join(path,name) if Puppet::FileSystem.exist? fqname return fqname else self.debug("Could not find #{name} in #{path}") end end paths.each do |path| fqname_sh = File.join(path,"#{name}.sh") if Puppet::FileSystem.exist? fqname_sh return fqname_sh else self.debug("Could not find #{name}.sh in #{path}") end end raise Puppet::Error, "Could not find init script for '#{name}'" end # The start command is just the init scriptwith 'start'. def startcmd [initscript, :start] end # The stop command is just the init script with 'stop'. def stopcmd [initscript, :stop] end def restartcmd (@resource[:hasrestart] == :true) && [initscript, :restart] end # If it was specified that the init script has a 'status' command, then # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd (@resource[:hasstatus] == :true) && [initscript, :status] end private def self.is_init?(script = initscript) file = Puppet::FileSystem.pathname(script) !Puppet::FileSystem.symlink?(file) || Puppet::FileSystem.readlink(file) != "/lib/init/upstart-job" end end diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb index 9f5ed2322..b0d7e594e 100755 --- a/spec/unit/provider/service/upstart_spec.rb +++ b/spec/unit/provider/service/upstart_spec.rb @@ -1,527 +1,531 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:upstart) do let(:manual) { "\nmanual" } let(:start_on_default_runlevels) { "\nstart on runlevel [2,3,4,5]" } let(:provider_class) { Puppet::Type.type(:service).provider(:upstart) } def given_contents_of(file, content) File.open(file, 'w') do |file| file.write(content) end end def then_contents_of(file) File.open(file).read end def lists_processes_as(output) Puppet::Util::Execution.stubs(:execpipe).with("/sbin/initctl list").yields(output) provider_class.stubs(:which).with("/sbin/initctl").returns("/sbin/initctl") end it "should be the default provider on Ubuntu" do Facter.expects(:value).with(:operatingsystem).returns("Ubuntu") described_class.default?.should be_true end describe "excluding services" do it "ignores tty and serial on Redhat systems" do Facter.stubs(:value).with(:osfamily).returns('RedHat') expect(described_class.excludes).to include 'serial' expect(described_class.excludes).to include 'tty' end end describe "#instances" do it "should be able to find all instances" do lists_processes_as("rc stop/waiting\nssh start/running, process 712") provider_class.instances.map {|provider| provider.name}.should =~ ["rc","ssh"] end it "should attach the interface name for network interfaces" do lists_processes_as("network-interface (eth0)") provider_class.instances.first.name.should == "network-interface INTERFACE=eth0" end it "should attach the job name for network interface security" do processes = "network-interface-security (network-interface/eth0)" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.first.name.should == "network-interface-security JOB=network-interface/eth0" end it "should not find excluded services" do - processes = "wait-for-state stop/waiting\nportmap-wait start/running\nidmapd-mounting stop/waiting\nstartpar-bridge start/running" + processes = "wait-for-state stop/waiting" + processes += "\nportmap-wait start/running" + processes += "\nidmapd-mounting stop/waiting" + processes += "\nstartpar-bridge start/running" + processes += "\ncryptdisks-udev stop/waiting" provider_class.stubs(:execpipe).yields(processes) provider_class.instances.should be_empty end end describe "#search" do it "searches through paths to find a matching conf file" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/foo-bar.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "foo-bar", :provider => :upstart) provider = provider_class.new(resource) provider.initscript.should == "/etc/init/foo-bar.conf" end it "searches for just the name of a compound named service" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/network-interface.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "network-interface INTERFACE=lo", :provider => :upstart) provider = provider_class.new(resource) provider.initscript.should == "/etc/init/network-interface.conf" end end describe "#status" do it "should use the default status command if none is specified" do resource = Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foo"]).returns("foo start/running, process 1000") Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status.should == :running end it "should properly handle services with 'start' in their name" do resource = Puppet::Type.type(:service).new(:name => "foostartbar", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foostartbar"]).returns("foostartbar stop/waiting") Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status.should == :stopped end end describe "inheritance" do let :resource do resource = Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider = provider_class.new(resource) end describe "when upstart job" do before(:each) do provider.stubs(:is_upstart?).returns(true) end ["start", "stop"].each do |command| it "should return the #{command}cmd of its parent provider" do provider.send("#{command}cmd".to_sym).should == [provider.command(command.to_sym), resource.name] end end it "should return nil for the statuscmd" do provider.statuscmd.should be_nil end end end describe "should be enableable" do let :resource do Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider_class.new(resource) end let :init_script do PuppetSpec::Files.tmpfile("foo.conf") end let :over_script do PuppetSpec::Files.tmpfile("foo.override") end let :disabled_content do "\t # \t start on\nother file stuff" end let :multiline_disabled do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "this line shouldn't be touched\n" end let :multiline_disabled_bad do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled_bad do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "this line shouldn't be touched\n" end let :multiline_enabled_standalone do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" end let :enabled_content do "\t \t start on\nother file stuff" end let :content do "just some text" end describe "Upstart version < 0.6.7" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.6.5") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable then_contents_of(init_script).should == enabled_content end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" + start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable then_contents_of(init_script).should == multiline_enabled_bad end end describe "when disabling" do it "should open and comment the 'start on' line" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == "#" + enabled_content end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_disabled end end describe "when checking whether it is enabled" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end end describe "Upstart version < 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.7.0") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable then_contents_of(init_script).should == enabled_content end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" + start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should remove manual stanzas" do given_contents_of(init_script, multiline_enabled + manual) provider.enable then_contents_of(init_script).should == multiline_enabled end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable then_contents_of(init_script).should == multiline_enabled_bad end end describe "when disabling" do it "should add a manual stanza" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == enabled_content + manual end it "should remove manual stanzas before adding new ones" do given_contents_of(init_script, multiline_enabled + manual + "\n" + multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled + "\n" + multiline_enabled + manual end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled + manual end end describe "when checking whether it is enabled" do describe "with no manual stanza" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end describe "with manual stanza" do it "should consider 'start on ...' to be disabled if there is a trailing manual stanza" do given_contents_of(init_script, enabled_content + manual + "\nother stuff") provider.enabled?.should == :false end it "should consider two start on lines with a manual in the middle to be enabled" do given_contents_of(init_script, enabled_content + manual + "\n" + enabled_content) provider.enabled?.should == :true end end end end describe "Upstart version > 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.9.5") provider.stubs(:search).returns(init_script) provider.stubs(:overscript).returns(over_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do provider.should respond_to(enableable) end end describe "when enabling" do it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable then_contents_of(init_script).should == "this is a file" then_contents_of(over_script).should == start_on_default_runlevels end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable then_contents_of(init_script).should == multiline_disabled then_contents_of(over_script).should == start_on_default_runlevels end it "should remove any manual stanzas from the override file" do given_contents_of(over_script, manual) given_contents_of(init_script, enabled_content) provider.enable then_contents_of(init_script).should == enabled_content then_contents_of(over_script).should == "" end it "should copy existing start on from conf file if conf file is disabled" do given_contents_of(init_script, multiline_enabled_standalone + manual) provider.enable then_contents_of(init_script).should == multiline_enabled_standalone + manual then_contents_of(over_script).should == multiline_enabled_standalone end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) given_contents_of(over_script, "") provider.enable then_contents_of(init_script).should == multiline_disabled_bad then_contents_of(over_script).should == start_on_default_runlevels end end describe "when disabling" do it "should add a manual stanza to the override file" do given_contents_of(init_script, enabled_content) provider.disable then_contents_of(init_script).should == enabled_content then_contents_of(over_script).should == manual end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable then_contents_of(init_script).should == multiline_enabled then_contents_of(over_script).should == manual end end describe "when checking whether it is enabled" do describe "with no override file" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) provider.enabled?.should == :true end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) provider.enabled?.should == :false end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) provider.enabled?.should == :false end end describe "with override file" do it "should consider 'start on ...' to be disabled if there is manual in override file" do given_contents_of(init_script, enabled_content) given_contents_of(over_script, manual + "\nother stuff") provider.enabled?.should == :false end it "should consider '#start on ...' to be enabled if there is a start on in the override file" do given_contents_of(init_script, disabled_content) given_contents_of(over_script, "start on stuff") provider.enabled?.should == :true end end end end end end