diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 483905cac..18aa554f1 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,111 +1,114 @@ require 'sync' require 'puppet/external/event-loop' require 'puppet/application' # A general class for triggering a run of another # class. class Puppet::Agent require 'puppet/agent/locker' include Puppet::Agent::Locker + require 'puppet/agent/disabler' + include Puppet::Agent::Disabler + attr_reader :client_class, :client, :splayed # Just so we can specify that we are "the" instance. def initialize(client_class) @splayed = false @client_class = client_class end def lockfile_path client_class.lockfile_path end def needing_restart? Puppet::Application.restart_requested? end # Perform a run with our client. def run(*args) if running? Puppet.notice "Run of #{client_class} already in progress; skipping" return end if disabled? Puppet.notice "Run of #{client_class} is administratively disabled; skipping" return end result = nil block_run = Puppet::Application.controlled_run do splay with_client do |client| begin sync.synchronize { lock { result = client.run(*args) } } rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run #{client_class}: #{detail}" end end true end Puppet.notice "Shutdown/restart in progress; skipping run" unless block_run result end def stopping? Puppet::Application.stop_requested? end # Have we splayed already? def splayed? splayed end # Sleep when splay is enabled; else just return. def splay return unless Puppet[:splay] return if splayed? time = rand(Integer(Puppet[:splaylimit]) + 1) Puppet.info "Sleeping for #{time} seconds (splay is enabled)" sleep(time) @splayed = true end # Start listening for events. We're pretty much just listening for # timer events here. def start # Create our timer. Puppet will handle observing it and such. timer = EventLoop::Timer.new(:interval => Puppet[:runinterval], :tolerance => 1, :start? => true) do run end # Run once before we start following the timer timer.sound_alarm end def sync @sync ||= Sync.new end private # Create and yield a client instance, keeping a reference # to it during the yield. def with_client begin @client = client_class.new rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not create instance of #{client_class}: #{detail}" return end yield @client ensure @client = nil end end diff --git a/lib/puppet/agent/disabler.rb b/lib/puppet/agent/disabler.rb new file mode 100644 index 000000000..bc167c35c --- /dev/null +++ b/lib/puppet/agent/disabler.rb @@ -0,0 +1,23 @@ +require 'puppet/util/anonymous_filelock' + +module Puppet::Agent::Disabler + # Let the daemon run again, freely in the filesystem. + def enable + disable_lockfile.unlock + end + + # Stop the daemon from making any catalog runs. + def disable + disable_lockfile.lock + end + + def disable_lockfile + @disable_lockfile ||= Puppet::Util::AnonymousFilelock.new(lockfile_path+".disabled") + + @disable_lockfile + end + + def disabled? + disable_lockfile.locked? + end +end diff --git a/lib/puppet/agent/locker.rb b/lib/puppet/agent/locker.rb index 238e83264..d41b12547 100644 --- a/lib/puppet/agent/locker.rb +++ b/lib/puppet/agent/locker.rb @@ -1,44 +1,30 @@ require 'puppet/util/pidlock' # Break out the code related to locking the agent. This module is just # included into the agent, but having it here makes it easier to test. module Puppet::Agent::Locker - # Let the daemon run again, freely in the filesystem. - def enable - Puppet::Util::AnonymousFilelock.new(lockfile_path + ".disabled").unlock - end - - # Stop the daemon from making any catalog runs. - def disable - Puppet::Util::AnonymousFilelock.new(lockfile_path + ".disabled").lock - end - - def disabled? - Puppet::Util::AnonymousFilelock.new(lockfile_path + ".disabled").locked? - end - # 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 return true else return false end end def lockfile @lockfile ||= Puppet::Util::Pidlock.new(lockfile_path) @lockfile end def running? lockfile.locked? end end diff --git a/spec/unit/agent/disabler_spec.rb b/spec/unit/agent/disabler_spec.rb new file mode 100644 index 000000000..3cbcddca4 --- /dev/null +++ b/spec/unit/agent/disabler_spec.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/agent' +require 'puppet/agent/locker' + +class LockerTester + include Puppet::Agent::Disabler +end + +describe Puppet::Agent::Disabler do + before do + @locker = LockerTester.new + @locker.stubs(:lockfile_path).returns "/my/lock" + end + + it "should use an AnonymousFilelock instance as its disable_lockfile" do + @locker.disable_lockfile.should be_instance_of(Puppet::Util::AnonymousFilelock) + end + + it "should use 'lockfile_path' to determine its disable_lockfile path" do + @locker.expects(:lockfile_path).returns "/my/lock" + lock = Puppet::Util::AnonymousFilelock.new("/my/lock") + Puppet::Util::AnonymousFilelock.expects(:new).with("/my/lock.disabled").returns lock + + @locker.disable_lockfile + end + + it "should reuse the same lock file each time" do + @locker.disable_lockfile.should equal(@locker.disable_lockfile) + end + + it "should lock the anonymous lock when disabled" do + @locker.disable_lockfile.expects(:lock) + + @locker.disable + end + + it "should unlock the anonymous lock when enabled" do + @locker.disable_lockfile.expects(:unlock) + + @locker.enable + end + + it "should check the lock if it is disabled" do + @locker.disable_lockfile.expects(:locked?) + + @locker.disabled? + end +end diff --git a/spec/unit/agent/locker_spec.rb b/spec/unit/agent/locker_spec.rb index 9d0b07639..9b530c0d8 100755 --- a/spec/unit/agent/locker_spec.rb +++ b/spec/unit/agent/locker_spec.rb @@ -1,103 +1,87 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' require 'puppet/agent/locker' class LockerTester include Puppet::Agent::Locker end describe Puppet::Agent::Locker do before do @locker = LockerTester.new @locker.stubs(:lockfile_path).returns "/my/lock" end it "should use a Pidlock instance as its lockfile" do @locker.lockfile.should be_instance_of(Puppet::Util::Pidlock) end it "should use 'lockfile_path' to determine its lockfile path" do @locker.expects(:lockfile_path).returns "/my/lock" lock = Puppet::Util::Pidlock.new("/my/lock") Puppet::Util::Pidlock.expects(:new).with("/my/lock").returns lock @locker.lockfile end it "should reuse the same lock file each time" do @locker.lockfile.should equal(@locker.lockfile) end - it "should use the lock file to anonymously lock the process when disabled" do - lock = stub 'lock' - Puppet::Util::AnonymousFilelock.expects(:new).with("/my/lock.disabled").returns lock - lock.expects(:lock) - - @locker.disable - end - - it "should use the lock file to anonymously unlock the process when enabled" do - lock = stub 'lock' - Puppet::Util::AnonymousFilelock.expects(:new).with("/my/lock.disabled").returns lock - lock.expects(:unlock) - - @locker.enable - end - it "should have a method that yields when a lock is attained" do @locker.lockfile.expects(:lock).returns true yielded = false @locker.lock do yielded = true end yielded.should be_true end it "should return true when the lock method successfully locked" do @locker.lockfile.expects(:lock).returns true @locker.lock {}.should be_true end it "should return true when the lock method does not receive the lock" do @locker.lockfile.expects(:lock).returns false @locker.lock {}.should be_false end it "should not yield when the lock method does not receive the lock" do @locker.lockfile.expects(:lock).returns false yielded = false @locker.lock { yielded = true } yielded.should be_false end it "should not unlock when a lock was not received" do @locker.lockfile.expects(:lock).returns false @locker.lockfile.expects(:unlock).never @locker.lock {} end it "should unlock after yielding upon obtaining a lock" do @locker.lockfile.stubs(:lock).returns true @locker.lockfile.expects(:unlock) @locker.lock {} end it "should unlock after yielding upon obtaining a lock, even if the block throws an exception" do @locker.lockfile.stubs(:lock).returns true @locker.lockfile.expects(:unlock) lambda { @locker.lock { raise "foo" } }.should raise_error(RuntimeError) end it "should be considered running if the lockfile is locked" do @locker.lockfile.expects(:locked?).returns true @locker.should be_running end end