diff --git a/lib/puppet/agent/locker.rb b/lib/puppet/agent/locker.rb index 3f624bcea..c1144f4d5 100644 --- a/lib/puppet/agent/locker.rb +++ b/lib/puppet/agent/locker.rb @@ -1,33 +1,33 @@ require 'puppet/util/pidlock' # This module is responsible for encapsulating the logic for # "locking" the puppet agent during a run; in other words, # keeping track of enough state to answer the question # "is there a puppet agent currently running?" module Puppet::Agent::Locker # Yield if we get a lock, else do nothing. Return # true/false depending on whether we get the lock. def lock if lockfile.lock begin yield ensure lockfile.unlock end end end def running? - lockfile.locked? and !lockfile.anonymous? + lockfile.locked? end def lockfile @lockfile ||= Puppet::Util::Pidlock.new(Puppet[:agent_running_lockfile]) @lockfile end private :lockfile end diff --git a/lib/puppet/util/anonymous_filelock.rb b/lib/puppet/util/anonymous_filelock.rb index ff09c5d12..419dff304 100644 --- a/lib/puppet/util/anonymous_filelock.rb +++ b/lib/puppet/util/anonymous_filelock.rb @@ -1,36 +1,32 @@ class Puppet::Util::AnonymousFilelock attr_reader :lockfile def initialize(lockfile) @lockfile = lockfile end - def anonymous? - true - end - def lock(msg = '') return false if locked? File.open(@lockfile, 'w') { |fd| fd.print(msg) } true end def unlock if locked? File.unlink(@lockfile) true else false end end def locked? File.exists? @lockfile end def message return File.read(@lockfile) if locked? end end \ No newline at end of file diff --git a/lib/puppet/util/pidlock.rb b/lib/puppet/util/pidlock.rb index 09731f688..b51a4c532 100644 --- a/lib/puppet/util/pidlock.rb +++ b/lib/puppet/util/pidlock.rb @@ -1,60 +1,54 @@ require 'fileutils' require 'puppet/util/anonymous_filelock' class Puppet::Util::Pidlock < Puppet::Util::AnonymousFilelock def locked? clear_if_stale File.exists? @lockfile end def mine? Process.pid == lock_pid end - def anonymous? - false - end - def lock return mine? if locked? File.open(@lockfile, "w") { |fd| fd.write(Process.pid) } true end - def unlock(opts = {}) - opts = {:anonymous => false}.merge(opts) - - if mine? or (opts[:anonymous] and anonymous?) + def unlock() + if mine? File.unlink(@lockfile) true else false end end def lock_pid if File.exists? @lockfile File.read(@lockfile).to_i else nil end end private 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 end diff --git a/spec/unit/agent_spec.rb b/spec/unit/agent_spec.rb index e8b280fab..80bfb7f19 100755 --- a/spec/unit/agent_spec.rb +++ b/spec/unit/agent_spec.rb @@ -1,336 +1,335 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' class AgentTestClient def run # no-op end def stop # no-op end end def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Agent do before do @agent = Puppet::Agent.new(AgentTestClient) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. without_warnings { Puppet::Application = Class.new(Puppet::Application) } Puppet::Application.stubs(:clear?).returns(true) Puppet::Application.class_eval do class << self def controlled_run(&block) block.call end end end end after do # restore Puppet::Application from stub-safe subclass, and silence warnings without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should set its client class at initialization" do Puppet::Agent.new("foo").client_class.should == "foo" end it "should include the Locker module" do Puppet::Agent.ancestors.should be_include(Puppet::Agent::Locker) end it "should create an instance of its client class and run it when asked to run" do client = mock 'client' AgentTestClient.expects(:new).returns client client.expects(:run) @agent.stubs(:running?).returns false @agent.stubs(:disabled?).returns false @agent.run end it "should be considered running if the lock file is locked and not anonymous" do lockfile = mock 'lockfile' - @agent.expects(:lockfile).returns(lockfile).twice + @agent.expects(:lockfile).returns(lockfile) lockfile.expects(:locked?).returns true - lockfile.expects(:anonymous?).returns false @agent.should be_running end it "should be considered disabled if the lock file is locked and anonymous" do lockfile = mock 'lockfile' @agent.expects(:disable_lockfile).returns(lockfile).at_least_once lockfile.expects(:locked?).returns(true).at_least_once @agent.should be_disabled end describe "when being run" do before do AgentTestClient.stubs(:lockfile_path).returns "/my/lock" @agent.stubs(:running?).returns false @agent.stubs(:disabled?).returns false end it "should splay" do @agent.expects(:splay) @agent.run end it "should do nothing if already running" do @agent.expects(:running?).returns true AgentTestClient.expects(:new).never @agent.run end it "should do nothing if disabled" do @agent.expects(:disabled?).returns(true) AgentTestClient.expects(:new).never @agent.run end it "(#11057) should notify the user about why a run is skipped" do Puppet::Application.stubs(:controlled_run).returns(false) Puppet::Application.stubs(:run_status).returns('MOCK_RUN_STATUS') # This is the actual test that we inform the user why the run is skipped. # We assume this information is contained in # Puppet::Application.run_status Puppet.expects(:notice).with(regexp_matches(/MOCK_RUN_STATUS/)) @agent.run end it "should display an informative message if the agent is administratively disabled" do @agent.expects(:disabled?).returns true @agent.expects(:disable_message).returns "foo" Puppet.expects(:notice).with(regexp_matches(/Skipping run of .*; administratively disabled.*\(Reason: 'foo'\)/)) @agent.run end it "should use Puppet::Application.controlled_run to manage process state behavior" do calls = sequence('calls') Puppet::Application.expects(:controlled_run).yields.in_sequence(calls) AgentTestClient.expects(:new).once.in_sequence(calls) @agent.run end it "should not fail if a client class instance cannot be created" do AgentTestClient.expects(:new).raises "eh" Puppet.expects(:err) @agent.run end it "should not fail if there is an exception while running its client" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises "eh" Puppet.expects(:err) @agent.run end it "should use a mutex to restrict multi-threading" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client mutex = mock 'mutex' @agent.expects(:sync).returns mutex mutex.expects(:synchronize) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should use a filesystem lock to restrict multiple processes running the agent" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should make its client instance available while running" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with { @agent.client.should equal(client); true } @agent.run end it "should run the client instance with any arguments passed to it" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with("testargs") @agent.run("testargs") end it "should return the agent result" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock).returns(:result) @agent.run.should == :result end describe "when should_fork is true" do before do @agent.should_fork = true Kernel.stubs(:fork) Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 0)] @agent.stubs(:exit) end it "should run the agent in a forked process" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run) Kernel.expects(:fork).yields @agent.run end it "should exit child process if child exit" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises(SystemExit) Kernel.expects(:fork).yields @agent.expects(:exit).with(-1) @agent.run end it "should re-raise exit happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -1)] lambda { @agent.run }.should raise_error(SystemExit) end it "should re-raise NoMoreMemory happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -2)] lambda { @agent.run }.should raise_error(NoMemoryError) end it "should return the child exit code" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 777)] @agent.run.should == 777 end it "should return the block exit code as the child exit code" do Kernel.expects(:fork).yields @agent.expects(:exit).with(777) @agent.run_in_fork { 777 } end end end describe "when splaying" do before do Puppet.settings.stubs(:value).with(:splay).returns true Puppet.settings.stubs(:value).with(:splaylimit).returns "10" end it "should do nothing if splay is disabled" do Puppet.settings.expects(:value).returns false @agent.expects(:sleep).never @agent.splay end it "should do nothing if it has already splayed" do @agent.expects(:splayed?).returns true @agent.expects(:sleep).never @agent.splay end it "should log that it is splaying" do @agent.stubs :sleep Puppet.expects :info @agent.splay end it "should sleep for a random portion of the splaylimit plus 1" do Puppet.settings.expects(:value).with(:splaylimit).returns "50" @agent.expects(:rand).with(51).returns 10 @agent.expects(:sleep).with(10) @agent.splay end it "should mark that it has splayed" do @agent.stubs(:sleep) @agent.splay @agent.should be_splayed end end describe "when checking execution state" do describe 'with regular run status' do before :each do Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(false) Puppet::Application.stubs(:clear?).returns(true) end it 'should be false for :stopping?' do @agent.stopping?.should be_false end it 'should be false for :needing_restart?' do @agent.needing_restart?.should be_false end end describe 'with a stop requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(true) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be true for :stopping?' do @agent.stopping?.should be_true end it 'should be false for :needing_restart?' do @agent.needing_restart?.should be_false end end describe 'with a restart requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(true) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be false for :stopping?' do @agent.stopping?.should be_false end it 'should be true for :needing_restart?' do @agent.needing_restart?.should be_true end end end end diff --git a/spec/unit/util/anonymous_filelock_spec.rb b/spec/unit/util/anonymous_filelock_spec.rb index 784ac0fca..70ae542b9 100644 --- a/spec/unit/util/anonymous_filelock_spec.rb +++ b/spec/unit/util/anonymous_filelock_spec.rb @@ -1,78 +1,74 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/anonymous_filelock' describe Puppet::Util::AnonymousFilelock do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::AnonymousFilelock.new(@lockfile) end - it "should be anonymous" do - @lock.should be_anonymous - end - describe "#lock" do it "should return false if already locked" do @lock.stubs(:locked?).returns(true) @lock.lock.should be_false end it "should return true if it successfully locked" do @lock.lock.should be_true end it "should create a lock file" do @lock.lock File.should be_exists(@lockfile) end it "should create a lock file containing a message" do @lock.lock("message") File.read(@lockfile).should == "message" end end describe "#unlock" do it "should return true when unlocking" do @lock.lock @lock.unlock.should be_true end it "should return false when not locked" do @lock.unlock.should be_false end it "should clear the lock file" do File.open(@lockfile, 'w') { |fd| fd.print("locked") } @lock.unlock File.should_not be_exists(@lockfile) end end it "should be locked when locked" do @lock.lock @lock.should be_locked end it "should not be locked when not locked" do @lock.should_not be_locked end it "should not be locked when unlocked" do @lock.lock @lock.unlock @lock.should_not be_locked end it "should return the lock message" do @lock.lock("lock message") @lock.message.should == "lock message" end end \ No newline at end of file diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb index 37f3ca4f4..643fd1f76 100644 --- a/spec/unit/util/pidlock_spec.rb +++ b/spec/unit/util/pidlock_spec.rb @@ -1,182 +1,178 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/pidlock' describe Puppet::Util::Pidlock do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Pidlock.new(@lockfile) end - it "should not be anonymous" do - @lock.should_not be_anonymous - end - describe "#lock" do it "should not be locked at start" do @lock.should_not be_locked end it "should not be mine at start" do @lock.should_not be_mine end it "should become locked" do @lock.lock @lock.should be_locked end it "should become mine" do @lock.lock @lock.should be_mine end it "should be possible to lock multiple times" do @lock.lock lambda { @lock.lock }.should_not raise_error end it "should return true when locking" do @lock.lock.should be_true end it "should return true if locked by me" do @lock.lock @lock.lock.should be_true end it "should return false if locked by someone else" do Process.stubs(:kill) File.open(@lockfile, "w") { |fd| fd.print('0') } @lock.lock.should be_false end it "should create a lock file" do @lock.lock File.should be_exists(@lockfile) end it "should create a lock file containing our pid" do @lock.lock File.read(@lockfile).to_i.should == Process.pid.to_i end end describe "#unlock" do it "should not be locked anymore" do @lock.lock @lock.unlock @lock.should_not be_locked end it "should return false if not locked" do @lock.unlock.should be_false end it "should return true if properly unlocked" do @lock.lock @lock.unlock.should be_true end it "should get rid of the lock file" do @lock.lock @lock.unlock File.should_not be_exists(@lockfile) end end describe "#locked?" do it "should return true if locked" do @lock.lock @lock.should be_locked end end describe "with a stale lock" do before(:each) do Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234).raises(Errno::ESRCH) Process.stubs(:pid).returns(6789) File.open(@lockfile, 'w') { |fd| fd.write("1234") } end it "should not be locked" do @lock.should_not be_locked end describe "#lock" do it "should clear stale locks" do @lock.locked? File.should_not be_exists(@lockfile) end it "should replace with new locks" do @lock.lock File.should be_exists(@lockfile) @lock.lock_pid.should == 6789 @lock.should be_mine @lock.should be_locked end end describe "#unlock" do it "should not be allowed" do @lock.unlock.should be_false end it "should not remove the lock file" do @lock.unlock File.should be_exists(@lockfile) end end end describe "with another process lock" do before(:each) do Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234) Process.stubs(:pid).returns(6789) File.open(@lockfile, 'w') { |fd| fd.write("1234") } end it "should be locked" do @lock.should be_locked end it "should not be mine" do @lock.should_not be_mine end describe "#lock" do it "should not be possible" do @lock.lock.should be_false end it "should not overwrite the lock" do @lock.lock @lock.should_not be_mine end end describe "#unlock" do it "should not be possible" do @lock.unlock.should be_false end it "should not remove the lock file" do @lock.unlock File.should be_exists(@lockfile) end it "should still not be our lock" do @lock.unlock @lock.should_not be_mine end end end end \ No newline at end of file