diff --git a/lib/puppet/util/pidlock.rb b/lib/puppet/util/pidlock.rb index fcf0cf296..a0fe99e89 100644 --- a/lib/puppet/util/pidlock.rb +++ b/lib/puppet/util/pidlock.rb @@ -1,72 +1,117 @@ require 'fileutils' class Puppet::Util::Pidlock attr_reader :lockfile def initialize(lockfile) @lockfile = lockfile end def locked? clear_if_stale + return true if File.exists? @lockfile + + # HACK! There was a temporary change to the lockfile behavior introduced in 2.7.10 and 2.7.11, and reverted + # in 2.7.12. We need to pull some chicanery to be backwards-compatible with those versions. For more info, + # see the comments on the method... and this hack should be removed for the 3.x series. + handle_2_7_10_disabled_lockfile File.exists? @lockfile end def mine? Process.pid == lock_pid end def anonymous? return false unless File.exists?(@lockfile) File.read(@lockfile) == "" end def lock(opts = {}) opts = {:anonymous => false}.merge(opts) if locked? mine? else if opts[:anonymous] File.open(@lockfile, 'w') { |fd| true } else File.open(@lockfile, "w") { |fd| fd.write(Process.pid) } end true end end def unlock(opts = {}) + return false unless locked? + opts = {:anonymous => false}.merge(opts) if mine? or (opts[:anonymous] and anonymous?) File.unlink(@lockfile) true else false end end private def lock_pid if File.exists? @lockfile File.read(@lockfile).to_i else nil end end def clear_if_stale return if lock_pid.nil? errors = [Errno::ESRCH] # Process::Error can only happen, and is only defined, on Windows errors << Process::Error if defined? Process::Error begin Process.kill(0, lock_pid) rescue *errors File.unlink(@lockfile) end end + + + ###################################################################################### + # Backwards compatibility hack + ###################################################################################### + # A change to lockfile behavior was introduced in 2.7.10 and 2.7.11; basically, + # instead of using a single lockfile to indicate both administrative disabling of + # the agent *and* the case where an agent run is already in progress, we started using + # two separate lockfiles: the 'normal' one for the "run in progress" case, and a + # separate one with a ".disabled" extension to indicate administrative disabling. + # + # This was determined to cause incompatibilities with mcollective, so the behavior + # was reverted for 2.7.12. Unfortunately this leaves the possibility that someone + # may have run "agent --disable" to administratively disable a 2.7.10 or 2.7.11 + # agent, and then upgraded to a newer version. This method exists only to + # provide backwards compatibility. Basically, it just recognizes the 2.7.10/2.7.11 + # ".disabled" lock file, warns, and cleans it up. + # + # This should be removed for the 3.x series. + # + # For more information, please see tickets #12844, #3757, #4836, and #11057 + # + # -- cprice 2012-03-01 + # + def handle_2_7_10_disabled_lockfile + disabled_lockfile_path = @lockfile + ".disabled" + if (File.exists?(disabled_lockfile_path)) + Puppet.warning("Found special lockfile '#{disabled_lockfile_path}'; this file was " + + "generated by a call to 'puppet agent --disable' in puppet 2.7.10 or 2.7.11. " + + "The expected lockfile path is '#{@lockfile}'; renaming the lock file.") + File.rename(disabled_lockfile_path, @lockfile) + end + end + private :handle_2_7_10_disabled_lockfile + ###################################################################################### + # End backwards compatibility hack + ###################################################################################### end diff --git a/spec/unit/agent_backward_compatibility_spec.rb b/spec/unit/agent_backward_compatibility_spec.rb new file mode 100644 index 000000000..1cd587fa1 --- /dev/null +++ b/spec/unit/agent_backward_compatibility_spec.rb @@ -0,0 +1,154 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/agent' + + +############################################################################ +# NOTE # +############################################################################ +# # +# This entire spec is only here for backwards compatibility from 2.7.12+ # +# with 2.7.10 and 2.7.11. The entire file should be able to be removed # +# for the 3.x series. # +# # +# For more info, see the comments on the #handle_2_7_10_disabled_lockfile # +# method in pidlock.rb # +# # +# --cprice 2012-03-01 # +############################################################################ + +class AgentTestClient + def run + # no-op + end + def stop + # no-op + end +end + +describe Puppet::Agent do + include PuppetSpec::Files + + let(:agent) { Puppet::Agent.new(AgentTestClient) } + + describe "in order to be backwards-compatibility with versions 2.7.10 and 2.7.11" do + + describe "when the 2.7.10/2.7.11 'disabled' lockfile exists" do + + # the "normal" lockfile + let(:lockfile_path) { tmpfile("agent_spec_lockfile") } + + # the 2.7.10/2.7.11 "disabled" lockfile + # (can't use PuppetSpec::Files.tmpfile here because we need the ".disabled" file to have *exactly* the same + # path/name as the original file, plus the ".disabled" suffix.) + let(:disabled_lockfile_path) { lockfile_path + ".disabled" } + + # some regexes to match log messages + let(:warning_regex) { /^Found special lockfile '#{disabled_lockfile_path}'.*renaming/ } + let(:run_in_progress_regex) { /^Run of .* already in progress; skipping/ } + + before(:each) do + # create the 2.7.10 "disable" lockfile. + FileUtils.touch(disabled_lockfile_path) + + # stub in our temp lockfile path. + AgentTestClient.expects(:lockfile_path).returns lockfile_path + end + + after(:each) do + # manually clean up the files that we didn't create via PuppetSpec::Files.tmpfile + begin + File.unlink(disabled_lockfile_path) + rescue Errno::ENOENT + # some of the tests expect for the agent code to take care of deleting this file, + # so it may (validly) not exist. + end + end + + describe "when the 'regular' lockfile also exists" do + # the logic here is that if a 'regular' lockfile already exists, then there is some state that the + # current version of puppet is responsible for dealing with. All of the tests in this block are + # simply here to make sure that our backwards-compatibility hack does *not* interfere with this. + # + # Even if the ".disabled" lockfile exists--it can be dealt with at another time, when puppet is + # in *exactly* the state that we want it to be in (mostly meaning that the 'regular' lockfile + # does not exist.) + + before(:each) do + # create the "regular" lockfile + FileUtils.touch(lockfile_path) + end + + it "should be recognized as 'running'" do + agent.should be_running + end + + it "should not try to start a new agent run" do + AgentTestClient.expects(:new).never + Puppet.expects(:notice).with { |msg| msg =~ run_in_progress_regex } + + agent.run + end + + it "should not delete the 2.7.10/2.7.11 lockfile" do + agent.run + + File.exists?(disabled_lockfile_path).should == true + end + + it "should not print the warning message" do + Puppet.expects(:warning).with {|msg| msg =~ warning_regex } .never + + agent.run + end + end + + describe "when the 'regular' lockfile does not exist" do + # this block of tests is for actually testing the backwards compatibility hack. This + # is where we're in a clean state and we know it's safe(r) to muck with the lockfile + # situation. + + it "should recognize that the agent is disabled" do + # TODO change this to use "be_disabled" when that is re-introduced. + agent.should be_running + end + + describe "when an agent run is requested" do + it "should not try to start a new agent run" do + AgentTestClient.expects(:new).never + Puppet.expects(:notice).with { |msg| msg =~ run_in_progress_regex } + + agent.run + end + + it "should warn, remove the 2.7.10/2.7.11 lockfile, and create the 'normal' lockfile" do + Puppet.expects(:warning).with { |msg| msg =~ warning_regex } + + agent.run + + File.exists?(disabled_lockfile_path).should == false + File.exists?(lockfile_path).should == true + end + end + + describe "when running --enable" do + it "should recognize that the agent is disabled" do + # TODO change this to use "be_disabled" when that is re-introduced. + agent.should be_running + end + + it "should warn and clean up the 2.7.10/2.7.11 lockfile" do + Puppet.expects(:warning).with { |msg| msg =~ warning_regex } + + agent.enable + + File.exists?(disabled_lockfile_path).should == false + File.exists?(lockfile_path).should == false + end + end + end + end + end + + +end