diff -uNr puppet/debian/changelog puppet-new/debian/changelog --- puppet/debian/changelog 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/debian/changelog 2012-03-09 09:51:39.041050394 +0000 @@ -1,3 +1,16 @@ +puppet (2.7.11-1ubuntu2) precise; urgency=low + + * Added patch to solve puppetlabs bug #12844 + - Reverts behaviour of agent lockfiles + - Fixes unit test to readmit old behaviour + * debian/puppetmaster-passenger.postinst (LP: #948983) + - Fixed rack directory location + - Added proper enabling of apache2 headers mod + * debian/puppetmaster-passenger.postinst (LP: #950183) + - Make sure we error if puppet config print doesn't work + + -- Marc Cluet Fri, 09 Mar 2012 09:49:23 +0000 + puppet (2.7.11-1) unstable; urgency=high * New upstream release diff -uNr puppet/debian/patches/debian-changes.~1~ puppet-new/debian/patches/debian-changes.~1~ --- puppet/debian/patches/debian-changes.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/debian/patches/debian-changes.~1~ 2012-03-09 09:53:52.399698793 +0000 @@ -0,0 +1,393 @@ +Description: + TODO: Put a short summary on the line above and replace this paragraph + with a longer explanation of this change. Complete the meta-information + with other relevant fields (see below for details). To make it easier, the + information below has been extracted from the changelog. Adjust it or drop + it. + . + puppet (2.7.11-1ubuntu2) precise; urgency=low + . + * Added patch to solve puppetlabs bug #12844 + - Reverts behaviour of agent lockfiles + - Fixes unit test to readmit old behaviour + * debian/puppetmaster-passenger.postinst (LP: #948983) + - Fixed rack directory location + - Added proper enabling of apache2 headers mod + * debian/puppetmaster-passenger.postinst (LP: #950183) + - Make sure we error if puppet config print doesn't work +Author: Marc Cluet +Bug-Ubuntu: https://bugs.launchpad.net/bugs/948983 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/950183 + +--- +The information above should follow the Patch Tagging Guidelines, please +checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here +are templates for supplementary fields that you might want to add: + +Origin: , +Bug: +Bug-Debian: http://bugs.debian.org/ +Bug-Ubuntu: https://launchpad.net/bugs/ +Forwarded: +Reviewed-By: +Last-Update: + +--- puppet-2.7.11.orig/Rakefile ++++ puppet-2.7.11/Rakefile +@@ -9,7 +9,7 @@ require 'rspec' + require "rspec/core/rake_task" + + module Puppet +- PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" ++ PUPPETVERSION = File.read('/usr/lib/ruby/1.8/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" + end + + Dir['tasks/**/*.rake'].each { |t| load t } +--- puppet-2.7.11.orig/lib/puppet/provider/service/init.rb ++++ puppet-2.7.11/lib/puppet/provider/service/init.rb +@@ -129,7 +129,15 @@ Puppet::Type.type(:service).provide :ini + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd +- (@resource[:hasstatus] == :true) && [initscript, :status] ++ if @resource[:hasstatus] == :true then ++ # Workaround the fact that initctl status command doesn't return ++ # proper exit codes. Can be removed once LP: #552786 is fixed. ++ if File.symlink?(initscript) && File.readlink(initscript) == "/lib/init/upstart-job" then ++ ['sh', '-c', "LANG=C invoke-rc.d #{File::basename(initscript)} status | grep -q '^#{File::basename(initscript)}.*running'" ] ++ else ++ [initscript, :status ] ++ end ++ end + end + + end +--- /dev/null ++++ puppet-2.7.11/lib/puppet/util/instrumentation/listeners/process_name.rb +@@ -0,0 +1,112 @@ ++require 'monitor' ++ ++# Unlike the other instrumentation plugins, this one doesn't give back ++# data. Instead it changes the process name of the currently running process ++# with the last labels and data. ++Puppet::Util::Instrumentation.new_listener(:process_name) do ++ include Sync_m ++ ++ # start scrolling when process name is longer than ++ SCROLL_LENGTH = 50 ++ ++ attr_accessor :active, :reason ++ ++ def notify(label, event, data) ++ start(label) if event == :start ++ stop if event == :stop ++ end ++ ++ def start(activity) ++ push_activity(Thread.current, activity) ++ end ++ ++ def stop() ++ pop_activity(Thread.current) ++ end ++ ++ def subscribed ++ synchronize do ++ @oldname = $0 ++ @scroller ||= Thread.new do ++ loop do ++ scroll ++ sleep 1 ++ end ++ end ++ end ++ end ++ ++ def unsubscribed ++ synchronize do ++ $0 = @oldname if @oldname ++ Thread.kill(@scroller) ++ @scroller = nil ++ end ++ end ++ ++ def setproctitle ++ $0 = "#{base}: " + rotate(process_name,@x) ++ end ++ ++ def push_activity(thread, activity) ++ synchronize do ++ @reason ||= {} ++ @reason[thread] ||= [] ++ @reason[thread].push(activity) ++ setproctitle ++ end ++ end ++ ++ def pop_activity(thread) ++ synchronize do ++ @reason[thread].pop ++ if @reason[thread].empty? ++ @reason.delete(thread) ++ end ++ setproctitle ++ end ++ end ++ ++ def process_name ++ out = (@reason || {}).inject([]) do |out, reason| ++ out << "#{thread_id(reason[0])} #{reason[1].join(',')}" ++ end ++ out.join(' | ') ++ end ++ ++ # Getting the ruby thread id might not be portable to other ruby ++ # interpreters than MRI, because Thread#inspect might not return the same ++ # information on a different runtime. ++ def thread_id(thread) ++ thread.inspect.gsub(/^#<.*:0x([a-f0-9]+) .*>$/, '\1') ++ end ++ ++ def rotate(string, steps) ++ steps ||= 0 ++ if string.length > 0 && steps > 0 ++ steps = steps % string.length ++ return string[steps..-1].concat " -- #{string[0..(steps-1)]}" ++ end ++ string ++ end ++ ++ def base ++ basename = case Puppet.run_mode.name ++ when :master ++ "master" ++ when :agent ++ "agent" ++ else ++ "puppet" ++ end ++ end ++ ++ def scroll ++ @x ||= 1 ++ return if process_name.length < SCROLL_LENGTH ++ synchronize do ++ setproctitle ++ @x += 1 ++ end ++ end ++end +\ No newline at end of file +--- puppet-2.7.11.orig/test/lib/puppettest/fakes.rb ++++ puppet-2.7.11/test/lib/puppettest/fakes.rb +@@ -1,4 +1,4 @@ +-require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/puppet/util')) ++require '/usr/lib/ruby/1.8/puppet/util' + + module PuppetTest + # A baseclass for the faketypes. +--- /dev/null ++++ puppet-2.7.11/spec/unit/util/instrumentation/listeners/process_name_spec.rb +@@ -0,0 +1,201 @@ ++#!/usr/bin/env rspec ++require 'spec_helper' ++require 'puppet/util/instrumentation' ++ ++Puppet::Util::Instrumentation.init ++process_name = Puppet::Util::Instrumentation.listener(:process_name) ++ ++describe process_name do ++ before(:each) do ++ @process_name = process_name.new ++ end ++ ++ it "should have a notify method" do ++ @process_name.should respond_to(:notify) ++ end ++ ++ it "should not have a data method" do ++ @process_name.should_not respond_to(:data) ++ end ++ ++ describe "when managing thread activity" do ++ before(:each) do ++ @process_name.stubs(:setproctitle) ++ @process_name.stubs(:base).returns("base") ++ end ++ ++ it "should be able to append activity" do ++ thread1 = stub 'thread1' ++ @process_name.push_activity(:thread1,"activity1") ++ @process_name.push_activity(:thread1,"activity2") ++ ++ @process_name.reason[:thread1].should == ["activity1", "activity2"] ++ end ++ ++ it "should be able to remove activity" do ++ @process_name.push_activity(:thread1,"activity1") ++ @process_name.push_activity(:thread1,"activity1") ++ @process_name.pop_activity(:thread1) ++ ++ @process_name.reason[:thread1].should == ["activity1"] ++ end ++ ++ it "should maintain activity thread by thread" do ++ @process_name.push_activity(:thread1,"activity1") ++ @process_name.push_activity(:thread2,"activity2") ++ ++ @process_name.reason[:thread1].should == ["activity1"] ++ @process_name.reason[:thread2].should == ["activity2"] ++ end ++ ++ it "should set process title" do ++ @process_name.expects(:setproctitle) ++ ++ @process_name.push_activity("thread1","activity1") ++ end ++ end ++ ++ describe "when computing the current process name" do ++ before(:each) do ++ @process_name.stubs(:setproctitle) ++ @process_name.stubs(:base).returns("base") ++ end ++ ++ it "should include every running thread activity" do ++ thread1 = stub 'thread1', :inspect => "\#", :hash => 1 ++ thread2 = stub 'thread2', :inspect => "\#", :hash => 0 ++ ++ @process_name.push_activity(thread1,"Compiling node1.domain.com") ++ @process_name.push_activity(thread2,"Compiling node4.domain.com") ++ @process_name.push_activity(thread1,"Parsing file site.pp") ++ @process_name.push_activity(thread2,"Parsing file node.pp") ++ ++ @process_name.process_name.should =~ /12344321 Compiling node4.domain.com,Parsing file node.pp/ ++ @process_name.process_name.should =~ /deadbeef Compiling node1.domain.com,Parsing file site.pp/ ++ end ++ end ++ ++ describe "when finding base process name" do ++ {:master => "master", :agent => "agent", :user => "puppet"}.each do |program,base| ++ it "should return #{base} for #{program}" do ++ Puppet.run_mode.stubs(:name).returns(program) ++ @process_name.base.should == base ++ end ++ end ++ end ++ ++ describe "when finding a thread id" do ++ it "should return the id from the thread inspect string" do ++ thread = stub 'thread', :inspect => "\#" ++ @process_name.thread_id(thread).should == "1234abdc" ++ end ++ end ++ ++ describe "when scrolling the instrumentation string" do ++ it "should rotate the string of various step" do ++ @process_name.rotate("this is a rotation", 10).should == "rotation -- this is a " ++ end ++ ++ it "should not rotate the string for the 0 offset" do ++ @process_name.rotate("this is a rotation", 0).should == "this is a rotation" ++ end ++ end ++ ++ describe "when setting process name" do ++ before(:each) do ++ @process_name.stubs(:process_name).returns("12345 activity") ++ @process_name.stubs(:base).returns("base") ++ @oldname = $0 ++ end ++ ++ after(:each) do ++ $0 = @oldname ++ end ++ ++ it "should do it if the feature is enabled" do ++ @process_name.setproctitle ++ ++ $0.should == "base: 12345 activity" ++ end ++ end ++ ++ describe "when subscribed" do ++ before(:each) do ++ thread = stub 'thread', :inspect => "\#" ++ Thread.stubs(:current).returns(thread) ++ end ++ ++ it "should start the scroller" do ++ Thread.expects(:new) ++ @process_name.subscribed ++ end ++ end ++ ++ describe "when unsubscribed" do ++ before(:each) do ++ @thread = stub 'scroller', :inspect => "\#" ++ Thread.stubs(:new).returns(@thread) ++ Thread.stubs(:kill) ++ @oldname = $0 ++ @process_name.subscribed ++ end ++ ++ after(:each) do ++ $0 = @oldname ++ end ++ ++ it "should stop the scroller" do ++ Thread.expects(:kill).with(@thread) ++ @process_name.unsubscribed ++ end ++ ++ it "should reset the process name" do ++ $0 = "let's see what happens" ++ @process_name.unsubscribed ++ $0.should == @oldname ++ end ++ end ++ ++ describe "when setting a probe" do ++ before(:each) do ++ thread = stub 'thread', :inspect => "\#" ++ Thread.stubs(:current).returns(thread) ++ Thread.stubs(:new) ++ @process_name.active = true ++ end ++ ++ it "should push current thread activity and execute the block" do ++ @process_name.notify(:instrumentation, :start, {}) ++ $0.should == "puppet: 1234abdc instrumentation" ++ @process_name.notify(:instrumentation, :stop, {}) ++ end ++ ++ it "should finally pop the activity" do ++ @process_name.notify(:instrumentation, :start, {}) ++ @process_name.notify(:instrumentation, :stop, {}) ++ $0.should == "puppet: " ++ end ++ end ++ ++ describe "when scrolling" do ++ it "should do nothing for shorter process names" do ++ @process_name.expects(:setproctitle).never ++ @process_name.scroll ++ end ++ ++ it "should call setproctitle" do ++ @process_name.stubs(:process_name).returns("x" * 60) ++ @process_name.expects(:setproctitle) ++ @process_name.scroll ++ end ++ ++ it "should increment rotation offset" do ++ name = "x" * 60 ++ @process_name.stubs(:process_name).returns(name) ++ @process_name.expects(:rotate).once.with(name,1).returns("") ++ @process_name.expects(:rotate).once.with(name,2).returns("") ++ @process_name.scroll ++ @process_name.scroll ++ end ++ end ++end +\ No newline at end of file diff -uNr puppet/debian/patches/revert_pidlock puppet-new/debian/patches/revert_pidlock --- puppet/debian/patches/revert_pidlock 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/debian/patches/revert_pidlock 2012-03-08 19:17:53.837884966 +0000 @@ -0,0 +1,879 @@ +Description: Reverts lockfile behaviour to previous model + This reverts commit fcac8f7163c99884fc6b75e3851c4a5e16a3ff07, which + was a backwards-compatibility workaround intended to handle some new + behavior related to puppet agent lockfiles that was introduced in + 2.7.10. The fix is being reverted because we've decided to remove + the new lockfile behavior from the 2.7.x series entirely, and push + it out to 3.x. +Author: cprice + +--- +The information above should follow the Patch Tagging Guidelines, please +checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here +are templates for supplementary fields that you might want to add: + +Origin: upstream +Bug: http://projects.puppetlabs.com/issues/12844 +Forwarded: not-needed +Reviewed-By: Marc Cluet + +--- puppet-2.7.11.orig/lib/puppet/agent.rb ++++ puppet-2.7.11/lib/puppet/agent.rb +@@ -8,9 +8,6 @@ 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. +@@ -35,9 +32,10 @@ class Puppet::Agent + return + end + if disabled? +- Puppet.notice "Skipping run of #{client_class}; administratively disabled: #{disable_message}" ++ Puppet.notice "Skipping run of #{client_class}; administratively disabled; use 'puppet #{client_class} --enable' to re-enable." + return + end ++ + result = nil + block_run = Puppet::Application.controlled_run do + splay +--- puppet-2.7.11.orig/lib/puppet/application/agent.rb ++++ puppet-2.7.11/lib/puppet/application/agent.rb +@@ -39,12 +39,7 @@ class Puppet::Application::Agent < Puppe + end + + option("--centrallogging") +- +- option("--disable [MESSAGE]") do |message| +- options[:disable] = true +- options[:disable_message] = message +- end +- ++ option("--disable") + option("--enable") + option("--debug","-d") + option("--fqdn FQDN","-f") +@@ -106,7 +101,7 @@ similar), or run interactively for testi + USAGE + ----- + puppet agent [--certname ] [-D|--daemonize|--no-daemonize] +- [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [message]] [--enable] ++ [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable] [--enable] + [--fingerprint] [-h|--help] [-l|--logdest syslog||console] + [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] + [-v|--verbose] [-V|--version] [-w|--waitforcert ] +@@ -210,9 +205,6 @@ configuration options can also be genera + not want the central configuration to override the local state until + everything is tested and committed. + +- Disable can also take an optional message that will be reported by the +- 'puppet agent' at the next disabled run. +- + 'puppet agent' uses the same lock file while it is running, so no more + than one 'puppet agent' process is working at a time. + +@@ -394,7 +386,7 @@ Copyright (c) 2011 Puppet Labs, LLC Lice + if options[:enable] + agent.enable + elsif options[:disable] +- agent.disable(options[:disable_message] || 'reason not specified') ++ agent.disable + end + exit(0) + end +--- puppet-2.7.11.orig/lib/puppet/agent/locker.rb ++++ puppet-2.7.11/lib/puppet/agent/locker.rb +@@ -3,6 +3,16 @@ 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 ++ lockfile.unlock(:anonymous => true) ++ end ++ ++ # Stop the daemon from making any catalog runs. ++ def disable ++ lockfile.lock(:anonymous => true) ++ end ++ + # Yield if we get a lock, else do nothing. Return + # true/false depending on whether we get the lock. + def lock +@@ -25,6 +35,10 @@ module Puppet::Agent::Locker + end + + def running? +- lockfile.locked? ++ lockfile.locked? and !lockfile.anonymous? ++ end ++ ++ def disabled? ++ lockfile.locked? and lockfile.anonymous? + end + end +--- puppet-2.7.11.orig/lib/puppet/agent/disabler.rb ++++ puppet-2.7.11/lib/puppet/agent/disabler.rb +@@ -1,11 +1,22 @@ + require 'puppet/util/anonymous_filelock' + ++# ++# A module that can be mixed in to provide enable/disable support for the agent. ++# It would be nice if this module completely encapsulated the details of this ++# process; however, it unfortunately relies on the existence of a "lockfile_path" method ++# on the class it's being mixed in to. ++# + module Puppet::Agent::Disabler + # Let the daemon run again, freely in the filesystem. + def enable + disable_lockfile.unlock ++ ++ # This is here for backwards compatibility with versions prior to 2.7.10 ++ # (see ticket #12844 and comments in the method itself). ++ handle_old_lockfile + end + ++ + # Stop the daemon from making any catalog runs. + def disable(msg='') + disable_lockfile.lock(msg) +@@ -24,4 +35,67 @@ module Puppet::Agent::Disabler + def disable_message + disable_lockfile.message + end ++ ++ ++ ++ # ++ # This method is here for backwards compatibility (see ticket #12844). ++ # ++ # In puppet versions prior to 2.7.10, there was a single lock file (puppetdlock) that ++ # the agent used for two different purposes: ++ # ++ # 1. If the file exists and contains a pid, then ostensibly there is an agent process ++ # currently running on the system. ++ # 2. If the file exists but is empty, then it was most likely generated by a call to ++ # "puppet agent --disable", which is the means for administratively disabling the ++ # agent until further notice. ++ # ++ # In puppet 2.7.10, the code was improved to be a little more explicit about this ++ # distinction; the "--disable/--enable" operations were changed to use a distinct ++ # lock file (puppetdlock.disabled), to reduce ambiguity relating to the existence ++ # of the file. ++ # ++ # However, it is possible that a user ran "--disable" with an older version of puppet, ++ # and then upgraded. Therefore we need to try to detect the lock file by the old name. ++ # If we find it, we need to see if we can tell why it's there, delete it if possible, ++ # and print a warning. ++ # ++ # (cprice 2012-02-28) ++ # ++ def handle_old_lockfile ++ old_disable_lockfile_path = lockfile_path ++ ++ begin ++ contents = File.read(old_disable_lockfile_path) ++ rescue Errno::ENOENT => err ++ # The lock file must not exist, so we don't need to do anything ++ else ++ case contents ++ when /^$/ ++ # The file is empty. This almost certainly means that it was created by an old version of puppet, via ++ # puppet agent --disable, so we'll warn and delete it. ++ Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'. " + ++ "It appears that this lock file was generated by a previous version of puppet, via a call " + ++ "to 'puppet agent --disable'. Deleting the empty file so that agents will be enabled.") ++ File.unlink(old_disable_lockfile_path) ++ when /^\d{3,10}$/ ++ # The file appears to contain a pid. This indicates that puppet believes an agent is currently running ++ # in another process. We'll respect that, but issue a warning just in case something else has happened, ++ # so that at least the user knows why "--enable" isn't necessarily guaranteed to actually enable the agent. ++ Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'. It appears that a puppet " + ++ "agent process is already running (pid #{contents}). If this is not the case, please remove or rename " + ++ "the file in order to enable a new agent run.") ++ else ++ # The file exists, and it's not empty... but it does not appear to contain just a pid. In other words, ++ # we have no idea what's going on. So we'll just print a warning. ++ Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'; unable to determine " + ++ "whether an existing agent is running or not. If not, please remove or rename the file in order to " + ++ "enable a new agent run.") ++ end ++ end ++ ++ end ++ private :handle_old_lockfile ++ ++ + end +--- puppet-2.7.11.orig/lib/puppet/util/pidlock.rb ++++ puppet-2.7.11/lib/puppet/util/pidlock.rb +@@ -1,10 +1,20 @@ + require 'fileutils' +-require 'puppet/util/anonymous_filelock' + +-class Puppet::Util::Pidlock < Puppet::Util::AnonymousFilelock ++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 + +@@ -13,36 +23,39 @@ class Puppet::Util::Pidlock < Puppet::Ut + end + + def anonymous? +- false ++ return false unless File.exists?(@lockfile) ++ File.read(@lockfile) == "" + end + +- def lock +- return mine? if locked? ++ def lock(opts = {}) ++ opts = {:anonymous => false}.merge(opts) + +- File.open(@lockfile, "w") { |fd| fd.write(Process.pid) } +- true ++ 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 = {}) +- if mine? +- begin +- File.unlink(@lockfile) +- rescue Errno::ENOENT +- # Someone deleted it for us ...and so we do nothing. No point whining +- # about a problem that the user can't actually do anything about. +- rescue SystemCallError => e +- # This one is a real failure though. No idea what went wrong, but it +- # is most likely "read only file(system)" or wrong permissions or +- # something like that. +- Puppet.err "Could not remove PID file #{@lockfile}: #{e}" +- puts e.backtrace if Puppet[:trace] +- end ++ 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 +@@ -51,7 +64,6 @@ class Puppet::Util::Pidlock < Puppet::Ut + end + end + +- private + def clear_if_stale + return if lock_pid.nil? + +@@ -65,4 +77,41 @@ class Puppet::Util::Pidlock < Puppet::Ut + 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 +--- /dev/null ++++ puppet-2.7.11/test/util/pidlock.rb +@@ -0,0 +1,125 @@ ++require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') ++ ++require 'puppet/util/pidlock' ++require 'fileutils' ++ ++# This is *fucked* *up* ++Puppet.debug = false ++ ++class TestPuppetUtilPidlock < Test::Unit::TestCase ++ include PuppetTest ++ ++ def setup ++ super ++ @workdir = tstdir ++ end ++ ++ def teardown ++ super ++ FileUtils.rm_rf(@workdir) ++ end ++ ++ def test_00_basic_create ++ l = nil ++ assert_nothing_raised { l = Puppet::Util::Pidlock.new(@workdir + '/nothingmuch') } ++ ++ assert_equal Puppet::Util::Pidlock, l.class ++ ++ assert_equal @workdir + '/nothingmuch', l.lockfile ++ end ++ ++ def test_10_uncontended_lock ++ l = Puppet::Util::Pidlock.new(@workdir + '/test_lock') ++ ++ assert !l.locked? ++ assert !l.mine? ++ assert l.lock ++ assert l.locked? ++ assert l.mine? ++ assert !l.anonymous? ++ # It's OK to call lock multiple times ++ assert l.lock ++ assert l.unlock ++ assert !l.locked? ++ assert !l.mine? ++ end ++ ++ def test_20_someone_elses_lock ++ childpid = nil ++ l = Puppet::Util::Pidlock.new(@workdir + '/someone_elses_lock') ++ ++ # First, we need a PID that's guaranteed to be (a) used, (b) someone ++ # else's, and (c) around for the life of this test. ++ childpid = fork { loop do; sleep 10; end } ++ ++ File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } ++ ++ assert l.locked? ++ assert !l.mine? ++ assert !l.lock ++ assert l.locked? ++ assert !l.mine? ++ assert !l.unlock ++ assert l.locked? ++ assert !l.mine? ++ ensure ++ Process.kill("KILL", childpid) unless childpid.nil? ++ end ++ ++ def test_30_stale_lock ++ # This is a bit hard to guarantee, but we need a PID that is definitely ++ # unused, and will stay so for the the life of this test. Our best ++ # bet is to create a process, get it's PID, let it die, and *then* ++ # lock on it. ++ childpid = fork { exit } ++ ++ # Now we can't continue until we're sure that the PID is dead ++ Process.wait(childpid) ++ ++ l = Puppet::Util::Pidlock.new(@workdir + '/stale_lock') ++ ++ # locked? should clear the lockfile ++ File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } ++ assert File.exists?(l.lockfile) ++ assert !l.locked? ++ assert !File.exists?(l.lockfile) ++ ++ # lock should replace the lockfile with our own ++ File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } ++ assert File.exists?(l.lockfile) ++ assert l.lock ++ assert l.locked? ++ assert l.mine? ++ ++ # unlock should fail, but should clear the stale lockfile. ++ File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } ++ assert File.exists?(l.lockfile) ++ assert !l.unlock ++ assert !File.exists?(l.lockfile) ++ end ++ ++ def test_40_not_locked_at_all ++ l = Puppet::Util::Pidlock.new(@workdir + '/not_locked') ++ ++ assert !l.locked? ++ # We can't unlock if we don't hold the lock ++ assert !l.unlock ++ end ++ ++ def test_50_anonymous_lock ++ l = Puppet::Util::Pidlock.new(@workdir + '/anonymous_lock') ++ ++ assert !l.locked? ++ assert l.lock(:anonymous => true) ++ assert l.locked? ++ assert l.anonymous? ++ assert !l.mine? ++ assert "", File.read(l.lockfile) ++ assert !l.unlock ++ assert l.locked? ++ assert l.anonymous? ++ assert l.unlock(:anonymous => true) ++ assert !File.exists?(l.lockfile) ++ end ++end ++ +--- puppet-2.7.11.orig/spec/unit/agent_spec.rb ++++ puppet-2.7.11/spec/unit/agent_spec.rb +@@ -24,7 +24,6 @@ describe Puppet::Agent do + + # So we don't actually try to hit the filesystem. + @agent.stubs(:lock).yields +- @agent.stubs(:disabled?).returns(false) + + # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. + without_warnings { Puppet::Application = Class.new(Puppet::Application) } +@@ -58,6 +57,13 @@ describe Puppet::Agent do + client.expects(:run) + + @agent.stubs(:running?).returns false ++ @agent.stubs(:disabled?).returns false ++ @agent.run ++ end ++ ++ it "should do nothing if disabled" do ++ @agent.expects(:disabled?).returns(true) ++ AgentTestClient.expects(:new).never + @agent.run + end + +@@ -66,24 +72,34 @@ describe Puppet::Agent do + @agent.lockfile_path.should == "/my/lock" + end + +- it "should be considered running if the lock file is locked" do ++ it "should be considered running if the lock file is locked and not anonymous" do + lockfile = mock 'lockfile' + +- @agent.expects(:lockfile).returns lockfile ++ @agent.expects(:lockfile).returns(lockfile).twice + 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(:lockfile).returns(lockfile).at_least_once ++ lockfile.expects(:locked?).returns(true).at_least_once ++ lockfile.expects(:anonymous?).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.stubs(:running?).returns false + + @agent.run + end +@@ -100,6 +116,12 @@ describe Puppet::Agent do + @agent.run + end + ++ it "should display an informative message if the agent is administratively disabled" do ++ @agent.expects(:disabled?).returns true ++ Puppet.expects(:notice).with(regexp_matches(/Skipping run of .*; administratively disabled/)) ++ @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) +--- /dev/null ++++ puppet-2.7.11/spec/unit/agent_backward_compatibility_spec.rb +@@ -0,0 +1,152 @@ ++#!/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(:disabled_regex) { /^Skipping run of .*; administratively disabled/ } ++ ++ 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 'disabled'" do ++ agent.should be_disabled ++ end ++ ++ it "should not try to start a new agent run" do ++ AgentTestClient.expects(:new).never ++ Puppet.expects(:notice).with(regexp_matches(disabled_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(regexp_matches(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 ++ agent.should be_disabled ++ 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(regexp_matches(disabled_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(regexp_matches(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 ++ agent.should be_disabled ++ end ++ ++ it "should warn and clean up the 2.7.10/2.7.11 lockfile" do ++ Puppet.expects(:warning).with(regexp_matches(warning_regex)) ++ ++ agent.enable ++ ++ File.exists?(disabled_lockfile_path).should == false ++ File.exists?(lockfile_path).should == false ++ end ++ end ++ end ++ end ++ end ++ ++ ++end +--- puppet-2.7.11.orig/spec/unit/application/agent_spec.rb ++++ puppet-2.7.11/spec/unit/application/agent_spec.rb +@@ -91,7 +91,7 @@ describe Puppet::Application::Agent do + @puppetd.command_line.stubs(:args).returns([]) + end + +- [:centrallogging, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| ++ [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| + it "should declare handle_#{option} method" do + @puppetd.should respond_to("handle_#{option}".to_sym) + end +@@ -102,24 +102,6 @@ describe Puppet::Application::Agent do + end + end + +- describe "when handling --disable" do +- it "should declare handle_disable method" do +- @puppetd.should respond_to(:handle_disable) +- end +- +- it "should set disable to true" do +- @puppetd.options.stubs(:[]=) +- @puppetd.options.expects(:[]=).with(:disable, true) +- @puppetd.handle_disable('') +- end +- +- it "should store disable message" do +- @puppetd.options.stubs(:[]=) +- @puppetd.options.expects(:[]=).with(:disable_message, "message") +- @puppetd.handle_disable('message') +- end +- end +- + it "should set an existing handler on server" do + Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) + +@@ -367,20 +349,6 @@ describe Puppet::Application::Agent do + end + end + +- it "should pass the disable message when disabling" do +- @puppetd.options.stubs(:[]).with(:disable).returns(true) +- @puppetd.options.stubs(:[]).with(:disable_message).returns("message") +- @agent.expects(:disable).with("message") +- expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 +- end +- +- it "should pass the default disable message when disabling without a message" do +- @puppetd.options.stubs(:[]).with(:disable).returns(true) +- @puppetd.options.stubs(:[]).with(:disable_message).returns(nil) +- @agent.expects(:disable).with("reason not specified") +- expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 +- end +- + it "should finally exit" do + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end +--- puppet-2.7.11.orig/spec/unit/agent/locker_spec.rb ++++ puppet-2.7.11/spec/unit/agent/locker_spec.rb +@@ -29,6 +29,18 @@ describe Puppet::Agent::Locker do + @locker.lockfile.should equal(@locker.lockfile) + end + ++ it "should use the lock file to anonymously lock the process when disabled" do ++ @locker.lockfile.expects(:lock).with(:anonymous => true) ++ ++ @locker.disable ++ end ++ ++ it "should use the lock file to anonymously unlock the process when enabled" do ++ @locker.lockfile.expects(:unlock).with(:anonymous => true) ++ ++ @locker.enable ++ end ++ + it "should have a method that yields when a lock is attained" do + @locker.lockfile.expects(:lock).returns true + +--- puppet-2.7.11.orig/spec/unit/agent/disabler_spec.rb ++++ puppet-2.7.11/spec/unit/agent/disabler_spec.rb +@@ -8,9 +8,18 @@ class LockerTester + end + + describe Puppet::Agent::Disabler do +- before do ++ before(:all) do ++ @lockdir = Dir.mktmpdir("disabler_spec_tmpdir") ++ @lockfile = File.join(@lockdir, "lock") ++ end ++ ++ after(:all) do ++ FileUtils.rm_rf(@lockdir) ++ end ++ ++ before(:each) do + @locker = LockerTester.new +- @locker.stubs(:lockfile_path).returns "/my/lock" ++ @locker.stubs(:lockfile_path).returns @lockfile + end + + it "should use an AnonymousFilelock instance as its disable_lockfile" do +@@ -18,9 +27,9 @@ describe Puppet::Agent::Disabler do + 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.expects(:lockfile_path).returns @lockfile ++ lock = Puppet::Util::AnonymousFilelock.new(@lockfile) ++ Puppet::Util::AnonymousFilelock.expects(:new).with(@lockfile + ".disabled").returns lock + + @locker.disable_lockfile + end +@@ -57,4 +66,61 @@ describe Puppet::Agent::Disabler do + @locker.disable_lockfile.expects(:message).returns("message") + @locker.disable_message.should == "message" + end ++ ++ describe "when enabling" do ++ ++ # this is for backwards compatibility with puppet versions prior to 2.7.10. ++ # for more detailed information, see the comments in the "#check_for_old_lockfile" method, ++ # in disabler.rb --cprice 2012-02-28 ++ describe "when a lockfile with the old filename already exists" do ++ let(:warning_prefix) { "Found an agent lock file at path '#{@lockfile}'" } ++ ++ after(:each) do ++ File.delete(@lockfile) if File.exists?(@lockfile) ++ end ++ ++ describe "when the lockfile is empty" do ++ before (:each) do ++ FileUtils.touch(@lockfile) ++ end ++ ++ it "should assume it was created by --disable in an old version of puppet, print a warning, and remove it" do ++ Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*Deleting the empty file/ } ++ ++ @locker.enable ++ ++ File.exists?(@lockfile).should == false ++ end ++ end ++ ++ describe "when the lockfile contains a pid" do ++ before (:each) do ++ File.open(@lockfile, "w") { |f| f.print(12345) } ++ end ++ ++ it "should assume that there may be a running agent process, and print a warning" do ++ Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*appears that a puppet agent process is already running/ } ++ ++ @locker.enable ++ ++ File.exists?(@lockfile).should == true ++ end ++ end ++ ++ describe "when the lockfile contains something other than a pid" do ++ before (:each) do ++ File.open(@lockfile, "w") { |f| f.print("Foo\nbar\n\baz") } ++ end ++ ++ it "should admit that it doesn't know what's going on, and print a warning" do ++ Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*unable to determine whether an existing agent is running or not/ } ++ ++ @locker.enable ++ ++ File.exists?(@lockfile).should == true ++ end ++ end ++ end ++ ++ end + end diff -uNr puppet/debian/patches/series puppet-new/debian/patches/series --- puppet/debian/patches/series 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/debian/patches/series 2012-03-08 19:09:42.288240333 +0000 @@ -1,2 +1,3 @@ fix_logcheck debian-changes +revert_pidlock diff -uNr puppet/debian/puppetmaster-passenger.postinst puppet-new/debian/puppetmaster-passenger.postinst --- puppet/debian/puppetmaster-passenger.postinst 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/debian/puppetmaster-passenger.postinst 2012-03-09 09:49:13.696411779 +0000 @@ -20,6 +20,11 @@ if [ ! -e "$(puppet config print hostcert)" ]; then puppet cert generate $(puppet config print certname) fi + # Check that puppet config print works properly + if [ $(puppet config print 2>&1 | grep "Could not parse" | wc -l) != "0" ]; then + echo "Puppet config print not working properly, exiting" + exit 1 + fi # Setup apache2 configuration files APACHE2_SITE_FILE="/etc/apache2/sites-available/puppetmaster" if [ ! -e "${APACHE2_SITE_FILE}" ]; then @@ -30,8 +35,10 @@ sed -r -i "s|(SSLCACertificateFile\s+).+$|\1$(puppet config print localcacert)|" "${APACHE2_SITE_FILE}" sed -r -i "s|(SSLCertificateChainFile\s+).+$|\1$(puppet config print localcacert)|" "${APACHE2_SITE_FILE}" sed -r -i "s|(SSLCARevocationFile\s+).+$|\1$(puppet config print cacrl)|" "${APACHE2_SITE_FILE}" + sed -r -i "s|/etc/puppet/rack|/usr/share/puppet/rack/puppetmasterd|" "${APACHE2_SITE_FILE}" fi a2enmod ssl + a2enmod headers a2ensite puppetmaster if [ -x "/etc/init.d/apache2" ]; then # Seems that a restart is needed. reload breaks ssl apparently. diff -uNr puppet/ext/logcheck/puppet.~1~ puppet-new/ext/logcheck/puppet.~1~ --- puppet/ext/logcheck/puppet.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/ext/logcheck/puppet.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,23 @@ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: (Handled resources in|Resource comparison took|Searched for (host|resources|resource params and tags) in) [0-9.]+ seconds +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Starting Puppet server version [.0-9]+$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Compiled catalog for [._[:alnum:]-]+ in [.0-9]+ seconds$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Caught TERM; shutting down$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Shutting down$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting Puppet client version [.0-9]+$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: getting config$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Caching configuration at [\/._[:alnum:]-]+$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Loaded state in [.0-9]+ seconds$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Calling puppetmaster.getconfig$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Retrieved configuration in [.0-9]+ seconds$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting configuration run$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Finished configuration run in [.0-9]+ seconds$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Caught (TERM|INT); shutting down$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Shutting down$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Restarting with .*$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting catalog run$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Finished catalog run in [.0-9]+ seconds$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Loading fact .*$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Ignoring cache$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Ignoring --listen on onetime run$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Retrieving plugins$ +^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Reopening log files$ diff -uNr puppet/lib/puppet/agent/disabler.rb puppet-new/lib/puppet/agent/disabler.rb --- puppet/lib/puppet/agent/disabler.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/lib/puppet/agent/disabler.rb 2012-03-09 09:53:57.874478000 +0000 @@ -1,11 +1,22 @@ require 'puppet/util/anonymous_filelock' +# +# A module that can be mixed in to provide enable/disable support for the agent. +# It would be nice if this module completely encapsulated the details of this +# process; however, it unfortunately relies on the existence of a "lockfile_path" method +# on the class it's being mixed in to. +# module Puppet::Agent::Disabler # Let the daemon run again, freely in the filesystem. def enable disable_lockfile.unlock + + # This is here for backwards compatibility with versions prior to 2.7.10 + # (see ticket #12844 and comments in the method itself). + handle_old_lockfile end + # Stop the daemon from making any catalog runs. def disable(msg='') disable_lockfile.lock(msg) @@ -24,4 +35,67 @@ def disable_message disable_lockfile.message end + + + + # + # This method is here for backwards compatibility (see ticket #12844). + # + # In puppet versions prior to 2.7.10, there was a single lock file (puppetdlock) that + # the agent used for two different purposes: + # + # 1. If the file exists and contains a pid, then ostensibly there is an agent process + # currently running on the system. + # 2. If the file exists but is empty, then it was most likely generated by a call to + # "puppet agent --disable", which is the means for administratively disabling the + # agent until further notice. + # + # In puppet 2.7.10, the code was improved to be a little more explicit about this + # distinction; the "--disable/--enable" operations were changed to use a distinct + # lock file (puppetdlock.disabled), to reduce ambiguity relating to the existence + # of the file. + # + # However, it is possible that a user ran "--disable" with an older version of puppet, + # and then upgraded. Therefore we need to try to detect the lock file by the old name. + # If we find it, we need to see if we can tell why it's there, delete it if possible, + # and print a warning. + # + # (cprice 2012-02-28) + # + def handle_old_lockfile + old_disable_lockfile_path = lockfile_path + + begin + contents = File.read(old_disable_lockfile_path) + rescue Errno::ENOENT => err + # The lock file must not exist, so we don't need to do anything + else + case contents + when /^$/ + # The file is empty. This almost certainly means that it was created by an old version of puppet, via + # puppet agent --disable, so we'll warn and delete it. + Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'. " + + "It appears that this lock file was generated by a previous version of puppet, via a call " + + "to 'puppet agent --disable'. Deleting the empty file so that agents will be enabled.") + File.unlink(old_disable_lockfile_path) + when /^\d{3,10}$/ + # The file appears to contain a pid. This indicates that puppet believes an agent is currently running + # in another process. We'll respect that, but issue a warning just in case something else has happened, + # so that at least the user knows why "--enable" isn't necessarily guaranteed to actually enable the agent. + Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'. It appears that a puppet " + + "agent process is already running (pid #{contents}). If this is not the case, please remove or rename " + + "the file in order to enable a new agent run.") + else + # The file exists, and it's not empty... but it does not appear to contain just a pid. In other words, + # we have no idea what's going on. So we'll just print a warning. + Puppet.warning("Found an agent lock file at path '#{old_disable_lockfile_path}'; unable to determine " + + "whether an existing agent is running or not. If not, please remove or rename the file in order to " + + "enable a new agent run.") + end + end + + end + private :handle_old_lockfile + + end diff -uNr puppet/lib/puppet/agent/disabler.rb.~1~ puppet-new/lib/puppet/agent/disabler.rb.~1~ --- puppet/lib/puppet/agent/disabler.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/agent/disabler.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,27 @@ +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(msg='') + disable_lockfile.lock(msg) + end + + def disable_lockfile + @disable_lockfile ||= Puppet::Util::AnonymousFilelock.new(lockfile_path+".disabled") + + @disable_lockfile + end + + def disabled? + disable_lockfile.locked? + end + + def disable_message + disable_lockfile.message + end +end diff -uNr puppet/lib/puppet/agent/locker.rb puppet-new/lib/puppet/agent/locker.rb --- puppet/lib/puppet/agent/locker.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/lib/puppet/agent/locker.rb 2012-03-09 09:53:57.874478000 +0000 @@ -3,6 +3,16 @@ # 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 + lockfile.unlock(:anonymous => true) + end + + # Stop the daemon from making any catalog runs. + def disable + lockfile.lock(:anonymous => true) + end + # Yield if we get a lock, else do nothing. Return # true/false depending on whether we get the lock. def lock @@ -25,6 +35,10 @@ end def running? - lockfile.locked? + lockfile.locked? and !lockfile.anonymous? + end + + def disabled? + lockfile.locked? and lockfile.anonymous? end end diff -uNr puppet/lib/puppet/agent/locker.rb.~1~ puppet-new/lib/puppet/agent/locker.rb.~1~ --- puppet/lib/puppet/agent/locker.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/agent/locker.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +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 + # 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 -uNr puppet/lib/puppet/agent.rb puppet-new/lib/puppet/agent.rb --- puppet/lib/puppet/agent.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/lib/puppet/agent.rb 2012-03-09 09:53:57.874478000 +0000 @@ -8,9 +8,6 @@ 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. @@ -35,9 +32,10 @@ return end if disabled? - Puppet.notice "Skipping run of #{client_class}; administratively disabled: #{disable_message}" + Puppet.notice "Skipping run of #{client_class}; administratively disabled; use 'puppet #{client_class} --enable' to re-enable." return end + result = nil block_run = Puppet::Application.controlled_run do splay diff -uNr puppet/lib/puppet/agent.rb.~1~ puppet-new/lib/puppet/agent.rb.~1~ --- puppet/lib/puppet/agent.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/agent.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +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 "Skipping run of #{client_class}; administratively disabled: #{disable_message}" + 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 -uNr puppet/lib/puppet/application/agent.rb puppet-new/lib/puppet/application/agent.rb --- puppet/lib/puppet/application/agent.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/lib/puppet/application/agent.rb 2012-03-09 09:53:57.874478000 +0000 @@ -39,12 +39,7 @@ end option("--centrallogging") - - option("--disable [MESSAGE]") do |message| - options[:disable] = true - options[:disable_message] = message - end - + option("--disable") option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") @@ -106,7 +101,7 @@ USAGE ----- puppet agent [--certname ] [-D|--daemonize|--no-daemonize] - [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [message]] [--enable] + [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable] [--enable] [--fingerprint] [-h|--help] [-l|--logdest syslog||console] [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] [-v|--verbose] [-V|--version] [-w|--waitforcert ] @@ -210,9 +205,6 @@ not want the central configuration to override the local state until everything is tested and committed. - Disable can also take an optional message that will be reported by the - 'puppet agent' at the next disabled run. - 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. @@ -394,7 +386,7 @@ if options[:enable] agent.enable elsif options[:disable] - agent.disable(options[:disable_message] || 'reason not specified') + agent.disable end exit(0) end diff -uNr puppet/lib/puppet/application/agent.rb.~1~ puppet-new/lib/puppet/application/agent.rb.~1~ --- puppet/lib/puppet/application/agent.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/application/agent.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,508 @@ +require 'puppet/application' + +class Puppet::Application::Agent < Puppet::Application + + should_parse_config + run_mode :agent + + attr_accessor :args, :agent, :daemon, :host + + def preinit + # Do an initial trap, so that cancels don't get a stack trace. + Signal.trap(:INT) do + $stderr.puts "Cancelling startup" + exit(0) + end + + { + :waitforcert => nil, + :detailed_exitcodes => false, + :verbose => false, + :debug => false, + :centrallogs => false, + :setdest => false, + :enable => false, + :disable => false, + :client => true, + :fqdn => nil, + :serve => [], + :digest => :MD5, + :fingerprint => false, + }.each do |opt,val| + options[opt] = val + end + + @args = {} + require 'puppet/daemon' + @daemon = Puppet::Daemon.new + @daemon.argv = ARGV.dup + end + + option("--centrallogging") + + option("--disable [MESSAGE]") do |message| + options[:disable] = true + options[:disable_message] = message + end + + option("--enable") + option("--debug","-d") + option("--fqdn FQDN","-f") + option("--test","-t") + option("--verbose","-v") + + option("--fingerprint") + option("--digest DIGEST") + + option("--serve HANDLER", "-s") do |arg| + if Puppet::Network::Handler.handler(arg) + options[:serve] << arg.to_sym + else + raise "Could not find handler for #{arg}" + end + end + + option("--no-client") do |arg| + options[:client] = false + end + + option("--detailed-exitcodes") do |arg| + options[:detailed_exitcodes] = true + end + + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + + option("--waitforcert WAITFORCERT", "-w") do |arg| + options[:waitforcert] = arg.to_i + end + + option("--port PORT","-p") do |arg| + @args[:Port] = arg + end + + def help + <<-HELP + +puppet-agent(8) -- The puppet agent daemon +======== + +SYNOPSIS +-------- +Retrieves the client configuration from the puppet master and applies it to +the local host. + +This service may be run as a daemon, run periodically using cron (or something +similar), or run interactively for testing purposes. + + +USAGE +----- +puppet agent [--certname ] [-D|--daemonize|--no-daemonize] + [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [message]] [--enable] + [--fingerprint] [-h|--help] [-l|--logdest syslog||console] + [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] + [-v|--verbose] [-V|--version] [-w|--waitforcert ] + + +DESCRIPTION +----------- +This is the main puppet client. Its job is to retrieve the local +machine's configuration from a remote server and apply it. In order to +successfully communicate with the remote server, the client must have a +certificate signed by a certificate authority that the server trusts; +the recommended method for this, at the moment, is to run a certificate +authority as part of the puppet server (which is the default). The +client will connect and request a signed certificate, and will continue +connecting until it receives one. + +Once the client has a signed certificate, it will retrieve its +configuration and apply it. + + +USAGE NOTES +----------- +'puppet agent' does its best to find a compromise between interactive +use and daemon use. Run with no arguments and no configuration, it will +go into the background, attempt to get a signed certificate, and retrieve +and apply its configuration every 30 minutes. + +Some flags are meant specifically for interactive use -- in particular, +'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose +logging, causes the daemon to stay in the foreground, exits if the +server's configuration is invalid (this happens if, for instance, you've +left a syntax error on the server), and exits after running the +configuration once (rather than hanging around as a long-running +process). + +'tags' allows you to specify what portions of a configuration you want +to apply. Puppet elements are tagged with all of the class or definition +names that contain them, and you can use the 'tags' flag to specify one +of these names, causing only configuration elements contained within +that class or definition to be applied. This is very useful when you are +testing new configurations -- for instance, if you are just starting to +manage 'ntpd', you would put all of the new elements into an 'ntpd' +class, and call puppet with '--tags ntpd', which would only apply that +small portion of the configuration during your testing, rather than +applying the whole thing. + +'fingerprint' is a one-time flag. In this mode 'puppet agent' will run +once and display on the console (and in the log) the current certificate +(or certificate request) fingerprint. Providing the '--digest' option +allows to use a different digest algorithm to generate the fingerprint. +The main use is to verify that before signing a certificate request on +the master, the certificate request the master received is the same as +the one the client sent (to prevent against man-in-the-middle attacks +when signing certificates). + + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration +file is also a valid long argument. For example, 'server' is a valid +configuration parameter, so you can specify '--server ' as +an argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/stable/configuration.html for the +full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet agent with +'--genconfig'. + +* --certname: + Set the certname (unique ID) of the client. The master reads this + unique identifying string, which is usually set to the node's + fully-qualified domain name, to determine which configurations the + node will receive. Use this option to debug setup problems or + implement unusual node identification schemes. + +* --daemonize: + Send the process into the background. This is the default. + +* --no-daemonize: + Do not send the process into the background. + +* --debug: + Enable full debugging. + +* --detailed-exitcodes: + Provide transaction information via exit codes. If this is enabled, an exit + code of '2' means there were changes, an exit code of '4' means there were + failures during the transaction, and an exit code of '6' means there were both + changes and failures. + +* --digest: + Change the certificate fingerprinting digest algorithm. The default is + MD5. Valid values depends on the version of OpenSSL installed, but + should always at least contain MD5, MD2, SHA1 and SHA256. + +* --disable: + Disable working on the local system. This puts a lock file in place, + causing 'puppet agent' not to work on the system until the lock file + is removed. This is useful if you are testing a configuration and do + not want the central configuration to override the local state until + everything is tested and committed. + + Disable can also take an optional message that will be reported by the + 'puppet agent' at the next disabled run. + + 'puppet agent' uses the same lock file while it is running, so no more + than one 'puppet agent' process is working at a time. + + 'puppet agent' exits after executing this. + +* --enable: + Enable working on the local system. This removes any lock file, + causing 'puppet agent' to start managing the local system again + (although it will continue to use its normal scheduling, so it might + not start for another half hour). + + 'puppet agent' exits after executing this. + +* --fingerprint: + Display the current certificate or certificate signing request + fingerprint and then exit. Use the '--digest' option to change the + digest algorithm used. + +* --help: + Print this help message + +* --logdest: + Where to send messages. Choose between syslog, the console, and a log + file. Defaults to sending messages to syslog, or the console if + debugging or verbosity is enabled. + +* --no-client: + Do not create a config client. This will cause the daemon to run + without ever checking for its configuration automatically, and only + makes sense when puppet agent is being run with listen = true in puppet.conf + or was started with the `--listen` option. + +* --noop: + Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This + is useful for seeing what changes Puppet will make without actually + executing the changes. + +* --onetime: + Run the configuration once. Runs a single (normally daemonized) Puppet + run. Useful for interactively running puppet agent when used in + conjunction with the --no-daemonize option. + +* --serve: + Start another type of server. By default, 'puppet agent' will start a + service handler that allows authenticated and authorized remote nodes + to trigger the configuration to be pulled down and applied. You can + specify any handler here that does not require configuration, e.g., + filebucket, ca, or resource. The handlers are in + 'lib/puppet/network/handler', and the names must match exactly, both + in the call to 'serve' and in 'namespaceauth.conf'. + +* --test: + Enable the most common options used for testing. These are 'onetime', + 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', + 'detailed-exit-codes', 'no-splay', and 'show_diff'. + +* --verbose: + Turn on verbose reporting. + +* --version: + Print the puppet version number and exit. + +* --waitforcert: + This option only matters for daemons that do not yet have certificates + and it is enabled by default, with a value of 120 (seconds). This + causes 'puppet agent' to connect to the server every 2 minutes and ask + it to sign a certificate request. This is useful for the initial setup + of a puppet client. You can turn off waiting for certificates by + specifying a time of 0. + + +EXAMPLE +------- + $ puppet agent --server puppet.domain.com + + +DIAGNOSTICS +----------- + +Puppet agent accepts the following signals: + +* SIGHUP: + Restart the puppet agent daemon. +* SIGINT and SIGTERM: + Shut down the puppet agent daemon. +* SIGUSR1: + Immediately retrieve and apply configurations from the puppet master. + +AUTHOR +------ +Luke Kanies + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + + def run_command + return fingerprint if options[:fingerprint] + return onetime if Puppet[:onetime] + main + end + + def fingerprint + unless cert = host.certificate || host.certificate_request + $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" + exit(1) + return + end + unless fingerprint = cert.fingerprint(options[:digest]) + raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" + end + puts fingerprint + end + + def onetime + unless options[:client] + $stderr.puts "onetime is specified but there is no client" + exit(43) + return + end + + @daemon.set_signal_traps + + begin + report = @agent.run + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err detail.to_s + end + + @daemon.stop(:exit => false) + + if not report + exit(1) + elsif options[:detailed_exitcodes] then + exit(report.exit_status) + else + exit(0) + end + end + + def main + Puppet.notice "Starting Puppet client version #{Puppet.version}" + + @daemon.start + end + + # Enable all of the most common test options. + def setup_test + Puppet.settings.handlearg("--ignorecache") + Puppet.settings.handlearg("--no-usecacheonfailure") + Puppet.settings.handlearg("--no-splay") + Puppet.settings.handlearg("--show_diff") + Puppet.settings.handlearg("--no-daemonize") + options[:verbose] = true + Puppet[:onetime] = true + options[:detailed_exitcodes] = true + end + + # Handle the logging settings. + def setup_logs + if options[:debug] or options[:verbose] + Puppet::Util::Log.newdestination(:console) + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + end + + Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] + end + + def enable_disable_client(agent) + if options[:enable] + agent.enable + elsif options[:disable] + agent.disable(options[:disable_message] || 'reason not specified') + end + exit(0) + end + + def setup_listen + unless FileTest.exists?(Puppet[:rest_authconfig]) + Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" + exit(14) + end + + handlers = nil + + if options[:serve].empty? + handlers = [:Runner] + else + handlers = options[:serve] + end + + require 'puppet/network/server' + # No REST handlers yet. + server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) + + @daemon.server = server + end + + def setup_host + @host = Puppet::SSL::Host.new + waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) + cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] + end + + def setup_agent + # We need tomake the client either way, we just don't start it + # if --no-client is set. + require 'puppet/agent' + require 'puppet/configurer' + @agent = Puppet::Agent.new(Puppet::Configurer) + + enable_disable_client(@agent) if options[:enable] or options[:disable] + + @daemon.agent = agent if options[:client] + + # It'd be nice to daemonize later, but we have to daemonize before the + # waitforcert happens. + @daemon.daemonize if Puppet[:daemonize] + + setup_host + + @objects = [] + + # This has to go after the certs are dealt with. + if Puppet[:listen] + unless Puppet[:onetime] + setup_listen + else + Puppet.notice "Ignoring --listen on onetime run" + end + end + end + + def setup + setup_test if options[:test] + + setup_logs + + exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? + + args[:Server] = Puppet[:server] + if options[:fqdn] + args[:FQDN] = options[:fqdn] + Puppet[:certname] = options[:fqdn] + end + + if options[:centrallogs] + logdest = args[:Server] + + logdest += ":" + args[:Port] if args.include?(:Port) + Puppet::Util::Log.newdestination(logdest) + end + + Puppet.settings.use :main, :agent, :ssl + + # Always ignoreimport for agent. It really shouldn't even try to import, + # but this is just a temporary band-aid. + Puppet[:ignoreimport] = true + + # We need to specify a ca location for all of the SSL-related i + # indirected classes to work; in fingerprint mode we just need + # access to the local files and we don't need a ca. + Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote + + Puppet::Transaction::Report.indirection.terminus_class = :rest + # we want the last report to be persisted locally + Puppet::Transaction::Report.indirection.cache_class = :yaml + + # Override the default; puppetd needs this, usually. + # You can still override this on the command-line with, e.g., :compiler. + Puppet[:catalog_terminus] = :rest + + # Override the default. + Puppet[:facts_terminus] = :facter + + Puppet::Resource::Catalog.indirection.cache_class = :yaml + + unless options[:fingerprint] + setup_agent + else + setup_host + end + end +end diff -uNr puppet/lib/puppet/provider/service/init.rb.~1~ puppet-new/lib/puppet/provider/service/init.rb.~1~ --- puppet/lib/puppet/provider/service/init.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/provider/service/init.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,136 @@ +# 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." + + class << self + attr_accessor :defpath + end + + case Facter["operatingsystem"].value + when "FreeBSD" + @defpath = ["/etc/rc.d", "/usr/local/etc/rc.d"] + when "HP-UX" + @defpath = "/sbin/init.d" + when "Archlinux" + @defpath = "/etc/rc.d" + else + @defpath = "/etc/init.d" + end + + # We can't confine this here, because the init path can be overridden. + #confine :exists => @defpath + + # List all services of this type. + def self.instances + get_services(self.defpath) + end + + def self.get_services(defpath, exclude=[]) + 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) + 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 File.exist?(path) and ! File.directory?(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 { |path| + fqname = File.join(path,name) + begin + stat = File.stat(fqname) + rescue + # should probably rescue specific errors... + self.debug("Could not find #{name} in #{path}") + next + end + + # if we've gotten this far, we found a valid script + return fqname + } + + paths.each { |path| + fqname_sh = File.join(path,"#{name}.sh") + begin + stat = File.stat(fqname_sh) + rescue + # should probably rescue specific errors... + self.debug("Could not find #{name}.sh in #{path}") + next + end + + # if we've gotten this far, we found a valid script + return fqname_sh + } + 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 + +end + diff -uNr puppet/lib/puppet/util/pidlock.rb puppet-new/lib/puppet/util/pidlock.rb --- puppet/lib/puppet/util/pidlock.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/lib/puppet/util/pidlock.rb 2012-03-09 09:53:57.874478000 +0000 @@ -1,10 +1,20 @@ require 'fileutils' -require 'puppet/util/anonymous_filelock' -class Puppet::Util::Pidlock < Puppet::Util::AnonymousFilelock +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 @@ -13,36 +23,39 @@ end def anonymous? - false + return false unless File.exists?(@lockfile) + File.read(@lockfile) == "" end - def lock - return mine? if locked? + def lock(opts = {}) + opts = {:anonymous => false}.merge(opts) - File.open(@lockfile, "w") { |fd| fd.write(Process.pid) } - true + 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 = {}) - if mine? - begin - File.unlink(@lockfile) - rescue Errno::ENOENT - # Someone deleted it for us ...and so we do nothing. No point whining - # about a problem that the user can't actually do anything about. - rescue SystemCallError => e - # This one is a real failure though. No idea what went wrong, but it - # is most likely "read only file(system)" or wrong permissions or - # something like that. - Puppet.err "Could not remove PID file #{@lockfile}: #{e}" - puts e.backtrace if Puppet[:trace] - end + 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 @@ -51,7 +64,6 @@ end end - private def clear_if_stale return if lock_pid.nil? @@ -65,4 +77,41 @@ 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 -uNr puppet/lib/puppet/util/pidlock.rb.~1~ puppet-new/lib/puppet/util/pidlock.rb.~1~ --- puppet/lib/puppet/util/pidlock.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/lib/puppet/util/pidlock.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,68 @@ +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 = {}) + if mine? + begin + File.unlink(@lockfile) + rescue Errno::ENOENT + # Someone deleted it for us ...and so we do nothing. No point whining + # about a problem that the user can't actually do anything about. + rescue SystemCallError => e + # This one is a real failure though. No idea what went wrong, but it + # is most likely "read only file(system)" or wrong permissions or + # something like that. + Puppet.err "Could not remove PID file #{@lockfile}: #{e}" + puts e.backtrace if Puppet[:trace] + end + 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 -uNr puppet/.pc/applied-patches puppet-new/.pc/applied-patches --- puppet/.pc/applied-patches 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/.pc/applied-patches 2012-03-09 09:53:57.874478000 +0000 @@ -1,2 +1,3 @@ fix_logcheck debian-changes +revert_pidlock diff -uNr puppet/.pc/.quilt_patches puppet-new/.pc/.quilt_patches --- puppet/.pc/.quilt_patches 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/.quilt_patches 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1 @@ +debian/patches diff -uNr puppet/.pc/.quilt_series puppet-new/.pc/.quilt_series --- puppet/.pc/.quilt_series 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/.quilt_series 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1 @@ +series diff -uNr puppet/.pc/revert_pidlock/lib/puppet/agent/disabler.rb puppet-new/.pc/revert_pidlock/lib/puppet/agent/disabler.rb --- puppet/.pc/revert_pidlock/lib/puppet/agent/disabler.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/lib/puppet/agent/disabler.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,27 @@ +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(msg='') + disable_lockfile.lock(msg) + end + + def disable_lockfile + @disable_lockfile ||= Puppet::Util::AnonymousFilelock.new(lockfile_path+".disabled") + + @disable_lockfile + end + + def disabled? + disable_lockfile.locked? + end + + def disable_message + disable_lockfile.message + end +end diff -uNr puppet/.pc/revert_pidlock/lib/puppet/agent/locker.rb puppet-new/.pc/revert_pidlock/lib/puppet/agent/locker.rb --- puppet/.pc/revert_pidlock/lib/puppet/agent/locker.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/lib/puppet/agent/locker.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +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 + # 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 -uNr puppet/.pc/revert_pidlock/lib/puppet/agent.rb puppet-new/.pc/revert_pidlock/lib/puppet/agent.rb --- puppet/.pc/revert_pidlock/lib/puppet/agent.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/lib/puppet/agent.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +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 "Skipping run of #{client_class}; administratively disabled: #{disable_message}" + 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 -uNr puppet/.pc/revert_pidlock/lib/puppet/application/agent.rb puppet-new/.pc/revert_pidlock/lib/puppet/application/agent.rb --- puppet/.pc/revert_pidlock/lib/puppet/application/agent.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/lib/puppet/application/agent.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,508 @@ +require 'puppet/application' + +class Puppet::Application::Agent < Puppet::Application + + should_parse_config + run_mode :agent + + attr_accessor :args, :agent, :daemon, :host + + def preinit + # Do an initial trap, so that cancels don't get a stack trace. + Signal.trap(:INT) do + $stderr.puts "Cancelling startup" + exit(0) + end + + { + :waitforcert => nil, + :detailed_exitcodes => false, + :verbose => false, + :debug => false, + :centrallogs => false, + :setdest => false, + :enable => false, + :disable => false, + :client => true, + :fqdn => nil, + :serve => [], + :digest => :MD5, + :fingerprint => false, + }.each do |opt,val| + options[opt] = val + end + + @args = {} + require 'puppet/daemon' + @daemon = Puppet::Daemon.new + @daemon.argv = ARGV.dup + end + + option("--centrallogging") + + option("--disable [MESSAGE]") do |message| + options[:disable] = true + options[:disable_message] = message + end + + option("--enable") + option("--debug","-d") + option("--fqdn FQDN","-f") + option("--test","-t") + option("--verbose","-v") + + option("--fingerprint") + option("--digest DIGEST") + + option("--serve HANDLER", "-s") do |arg| + if Puppet::Network::Handler.handler(arg) + options[:serve] << arg.to_sym + else + raise "Could not find handler for #{arg}" + end + end + + option("--no-client") do |arg| + options[:client] = false + end + + option("--detailed-exitcodes") do |arg| + options[:detailed_exitcodes] = true + end + + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + + option("--waitforcert WAITFORCERT", "-w") do |arg| + options[:waitforcert] = arg.to_i + end + + option("--port PORT","-p") do |arg| + @args[:Port] = arg + end + + def help + <<-HELP + +puppet-agent(8) -- The puppet agent daemon +======== + +SYNOPSIS +-------- +Retrieves the client configuration from the puppet master and applies it to +the local host. + +This service may be run as a daemon, run periodically using cron (or something +similar), or run interactively for testing purposes. + + +USAGE +----- +puppet agent [--certname ] [-D|--daemonize|--no-daemonize] + [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [message]] [--enable] + [--fingerprint] [-h|--help] [-l|--logdest syslog||console] + [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] + [-v|--verbose] [-V|--version] [-w|--waitforcert ] + + +DESCRIPTION +----------- +This is the main puppet client. Its job is to retrieve the local +machine's configuration from a remote server and apply it. In order to +successfully communicate with the remote server, the client must have a +certificate signed by a certificate authority that the server trusts; +the recommended method for this, at the moment, is to run a certificate +authority as part of the puppet server (which is the default). The +client will connect and request a signed certificate, and will continue +connecting until it receives one. + +Once the client has a signed certificate, it will retrieve its +configuration and apply it. + + +USAGE NOTES +----------- +'puppet agent' does its best to find a compromise between interactive +use and daemon use. Run with no arguments and no configuration, it will +go into the background, attempt to get a signed certificate, and retrieve +and apply its configuration every 30 minutes. + +Some flags are meant specifically for interactive use -- in particular, +'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose +logging, causes the daemon to stay in the foreground, exits if the +server's configuration is invalid (this happens if, for instance, you've +left a syntax error on the server), and exits after running the +configuration once (rather than hanging around as a long-running +process). + +'tags' allows you to specify what portions of a configuration you want +to apply. Puppet elements are tagged with all of the class or definition +names that contain them, and you can use the 'tags' flag to specify one +of these names, causing only configuration elements contained within +that class or definition to be applied. This is very useful when you are +testing new configurations -- for instance, if you are just starting to +manage 'ntpd', you would put all of the new elements into an 'ntpd' +class, and call puppet with '--tags ntpd', which would only apply that +small portion of the configuration during your testing, rather than +applying the whole thing. + +'fingerprint' is a one-time flag. In this mode 'puppet agent' will run +once and display on the console (and in the log) the current certificate +(or certificate request) fingerprint. Providing the '--digest' option +allows to use a different digest algorithm to generate the fingerprint. +The main use is to verify that before signing a certificate request on +the master, the certificate request the master received is the same as +the one the client sent (to prevent against man-in-the-middle attacks +when signing certificates). + + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration +file is also a valid long argument. For example, 'server' is a valid +configuration parameter, so you can specify '--server ' as +an argument. + +See the configuration file documentation at +http://docs.puppetlabs.com/references/stable/configuration.html for the +full list of acceptable parameters. A commented list of all +configuration options can also be generated by running puppet agent with +'--genconfig'. + +* --certname: + Set the certname (unique ID) of the client. The master reads this + unique identifying string, which is usually set to the node's + fully-qualified domain name, to determine which configurations the + node will receive. Use this option to debug setup problems or + implement unusual node identification schemes. + +* --daemonize: + Send the process into the background. This is the default. + +* --no-daemonize: + Do not send the process into the background. + +* --debug: + Enable full debugging. + +* --detailed-exitcodes: + Provide transaction information via exit codes. If this is enabled, an exit + code of '2' means there were changes, an exit code of '4' means there were + failures during the transaction, and an exit code of '6' means there were both + changes and failures. + +* --digest: + Change the certificate fingerprinting digest algorithm. The default is + MD5. Valid values depends on the version of OpenSSL installed, but + should always at least contain MD5, MD2, SHA1 and SHA256. + +* --disable: + Disable working on the local system. This puts a lock file in place, + causing 'puppet agent' not to work on the system until the lock file + is removed. This is useful if you are testing a configuration and do + not want the central configuration to override the local state until + everything is tested and committed. + + Disable can also take an optional message that will be reported by the + 'puppet agent' at the next disabled run. + + 'puppet agent' uses the same lock file while it is running, so no more + than one 'puppet agent' process is working at a time. + + 'puppet agent' exits after executing this. + +* --enable: + Enable working on the local system. This removes any lock file, + causing 'puppet agent' to start managing the local system again + (although it will continue to use its normal scheduling, so it might + not start for another half hour). + + 'puppet agent' exits after executing this. + +* --fingerprint: + Display the current certificate or certificate signing request + fingerprint and then exit. Use the '--digest' option to change the + digest algorithm used. + +* --help: + Print this help message + +* --logdest: + Where to send messages. Choose between syslog, the console, and a log + file. Defaults to sending messages to syslog, or the console if + debugging or verbosity is enabled. + +* --no-client: + Do not create a config client. This will cause the daemon to run + without ever checking for its configuration automatically, and only + makes sense when puppet agent is being run with listen = true in puppet.conf + or was started with the `--listen` option. + +* --noop: + Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This + is useful for seeing what changes Puppet will make without actually + executing the changes. + +* --onetime: + Run the configuration once. Runs a single (normally daemonized) Puppet + run. Useful for interactively running puppet agent when used in + conjunction with the --no-daemonize option. + +* --serve: + Start another type of server. By default, 'puppet agent' will start a + service handler that allows authenticated and authorized remote nodes + to trigger the configuration to be pulled down and applied. You can + specify any handler here that does not require configuration, e.g., + filebucket, ca, or resource. The handlers are in + 'lib/puppet/network/handler', and the names must match exactly, both + in the call to 'serve' and in 'namespaceauth.conf'. + +* --test: + Enable the most common options used for testing. These are 'onetime', + 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', + 'detailed-exit-codes', 'no-splay', and 'show_diff'. + +* --verbose: + Turn on verbose reporting. + +* --version: + Print the puppet version number and exit. + +* --waitforcert: + This option only matters for daemons that do not yet have certificates + and it is enabled by default, with a value of 120 (seconds). This + causes 'puppet agent' to connect to the server every 2 minutes and ask + it to sign a certificate request. This is useful for the initial setup + of a puppet client. You can turn off waiting for certificates by + specifying a time of 0. + + +EXAMPLE +------- + $ puppet agent --server puppet.domain.com + + +DIAGNOSTICS +----------- + +Puppet agent accepts the following signals: + +* SIGHUP: + Restart the puppet agent daemon. +* SIGINT and SIGTERM: + Shut down the puppet agent daemon. +* SIGUSR1: + Immediately retrieve and apply configurations from the puppet master. + +AUTHOR +------ +Luke Kanies + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License + + HELP + end + + def run_command + return fingerprint if options[:fingerprint] + return onetime if Puppet[:onetime] + main + end + + def fingerprint + unless cert = host.certificate || host.certificate_request + $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" + exit(1) + return + end + unless fingerprint = cert.fingerprint(options[:digest]) + raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" + end + puts fingerprint + end + + def onetime + unless options[:client] + $stderr.puts "onetime is specified but there is no client" + exit(43) + return + end + + @daemon.set_signal_traps + + begin + report = @agent.run + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err detail.to_s + end + + @daemon.stop(:exit => false) + + if not report + exit(1) + elsif options[:detailed_exitcodes] then + exit(report.exit_status) + else + exit(0) + end + end + + def main + Puppet.notice "Starting Puppet client version #{Puppet.version}" + + @daemon.start + end + + # Enable all of the most common test options. + def setup_test + Puppet.settings.handlearg("--ignorecache") + Puppet.settings.handlearg("--no-usecacheonfailure") + Puppet.settings.handlearg("--no-splay") + Puppet.settings.handlearg("--show_diff") + Puppet.settings.handlearg("--no-daemonize") + options[:verbose] = true + Puppet[:onetime] = true + options[:detailed_exitcodes] = true + end + + # Handle the logging settings. + def setup_logs + if options[:debug] or options[:verbose] + Puppet::Util::Log.newdestination(:console) + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + end + + Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] + end + + def enable_disable_client(agent) + if options[:enable] + agent.enable + elsif options[:disable] + agent.disable(options[:disable_message] || 'reason not specified') + end + exit(0) + end + + def setup_listen + unless FileTest.exists?(Puppet[:rest_authconfig]) + Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" + exit(14) + end + + handlers = nil + + if options[:serve].empty? + handlers = [:Runner] + else + handlers = options[:serve] + end + + require 'puppet/network/server' + # No REST handlers yet. + server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) + + @daemon.server = server + end + + def setup_host + @host = Puppet::SSL::Host.new + waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) + cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] + end + + def setup_agent + # We need tomake the client either way, we just don't start it + # if --no-client is set. + require 'puppet/agent' + require 'puppet/configurer' + @agent = Puppet::Agent.new(Puppet::Configurer) + + enable_disable_client(@agent) if options[:enable] or options[:disable] + + @daemon.agent = agent if options[:client] + + # It'd be nice to daemonize later, but we have to daemonize before the + # waitforcert happens. + @daemon.daemonize if Puppet[:daemonize] + + setup_host + + @objects = [] + + # This has to go after the certs are dealt with. + if Puppet[:listen] + unless Puppet[:onetime] + setup_listen + else + Puppet.notice "Ignoring --listen on onetime run" + end + end + end + + def setup + setup_test if options[:test] + + setup_logs + + exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? + + args[:Server] = Puppet[:server] + if options[:fqdn] + args[:FQDN] = options[:fqdn] + Puppet[:certname] = options[:fqdn] + end + + if options[:centrallogs] + logdest = args[:Server] + + logdest += ":" + args[:Port] if args.include?(:Port) + Puppet::Util::Log.newdestination(logdest) + end + + Puppet.settings.use :main, :agent, :ssl + + # Always ignoreimport for agent. It really shouldn't even try to import, + # but this is just a temporary band-aid. + Puppet[:ignoreimport] = true + + # We need to specify a ca location for all of the SSL-related i + # indirected classes to work; in fingerprint mode we just need + # access to the local files and we don't need a ca. + Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote + + Puppet::Transaction::Report.indirection.terminus_class = :rest + # we want the last report to be persisted locally + Puppet::Transaction::Report.indirection.cache_class = :yaml + + # Override the default; puppetd needs this, usually. + # You can still override this on the command-line with, e.g., :compiler. + Puppet[:catalog_terminus] = :rest + + # Override the default. + Puppet[:facts_terminus] = :facter + + Puppet::Resource::Catalog.indirection.cache_class = :yaml + + unless options[:fingerprint] + setup_agent + else + setup_host + end + end +end diff -uNr puppet/.pc/revert_pidlock/lib/puppet/util/pidlock.rb puppet-new/.pc/revert_pidlock/lib/puppet/util/pidlock.rb --- puppet/.pc/revert_pidlock/lib/puppet/util/pidlock.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/lib/puppet/util/pidlock.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,68 @@ +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 = {}) + if mine? + begin + File.unlink(@lockfile) + rescue Errno::ENOENT + # Someone deleted it for us ...and so we do nothing. No point whining + # about a problem that the user can't actually do anything about. + rescue SystemCallError => e + # This one is a real failure though. No idea what went wrong, but it + # is most likely "read only file(system)" or wrong permissions or + # something like that. + Puppet.err "Could not remove PID file #{@lockfile}: #{e}" + puts e.backtrace if Puppet[:trace] + end + 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 -uNr puppet/.pc/revert_pidlock/spec/unit/agent/disabler_spec.rb puppet-new/.pc/revert_pidlock/spec/unit/agent/disabler_spec.rb --- puppet/.pc/revert_pidlock/spec/unit/agent/disabler_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/spec/unit/agent/disabler_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,60 @@ +#!/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 disable with a message" do + @locker.disable_lockfile.expects(:lock).with("disabled because") + + @locker.disable("disabled because") + 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 + + it "should report the disable message when disabled" do + @locker.disable_lockfile.expects(:message).returns("message") + @locker.disable_message.should == "message" + end +end diff -uNr puppet/.pc/revert_pidlock/spec/unit/agent/locker_spec.rb puppet-new/.pc/revert_pidlock/spec/unit/agent/locker_spec.rb --- puppet/.pc/revert_pidlock/spec/unit/agent/locker_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/spec/unit/agent/locker_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +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 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 diff -uNr puppet/.pc/revert_pidlock/spec/unit/agent_spec.rb puppet-new/.pc/revert_pidlock/spec/unit/agent_spec.rb --- puppet/.pc/revert_pidlock/spec/unit/agent_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/spec/unit/agent_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,285 @@ +#!/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 + @agent.stubs(:disabled?).returns(false) + + # 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.run + end + + it "should determine its lock file path by asking the client class" do + AgentTestClient.expects(:lockfile_path).returns "/my/lock" + @agent.lockfile_path.should == "/my/lock" + end + + it "should be considered running if the lock file is locked" do + lockfile = mock 'lockfile' + + @agent.expects(:lockfile).returns lockfile + lockfile.expects(:locked?).returns true + + @agent.should be_running + end + + describe "when being run" do + before do + AgentTestClient.stubs(:lockfile_path).returns "/my/lock" + @agent.stubs(:running?).returns false + end + + it "should splay" do + @agent.expects(:splay) + @agent.stubs(:running?).returns false + + @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 "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 + 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 + + describe "when starting" do + before do + @agent.stubs(:observe_signal) + end + + it "should create a timer with the runinterval, a tolerance of 1, and :start? set to true" do + Puppet.settings.expects(:value).with(:runinterval).returns 5 + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).returns timer + + @agent.stubs(:run) + @agent.start + end + + it "should run once immediately" do + timer = mock 'timer' + EventLoop::Timer.expects(:new).returns timer + + timer.expects(:sound_alarm) + + @agent.start + end + + it "should run within the block passed to the timer" do + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).returns(timer).yields + @agent.expects(:run) + + @agent.start + end + end +end diff -uNr puppet/.pc/revert_pidlock/spec/unit/application/agent_spec.rb puppet-new/.pc/revert_pidlock/spec/unit/application/agent_spec.rb --- puppet/.pc/revert_pidlock/spec/unit/application/agent_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/.pc/revert_pidlock/spec/unit/application/agent_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,631 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/agent' +require 'puppet/application/agent' +require 'puppet/network/server' +require 'puppet/daemon' +require 'puppet/network/handler' + +describe Puppet::Application::Agent do + before :each do + @puppetd = Puppet::Application[:agent] + @puppetd.stubs(:puts) + @daemon = stub_everything 'daemon' + Puppet::Daemon.stubs(:new).returns(@daemon) + Puppet[:daemonize] = false + @agent = stub_everything 'agent' + Puppet::Agent.stubs(:new).returns(@agent) + @puppetd.preinit + Puppet::Util::Log.stubs(:newdestination) + + Puppet::Node.indirection.stubs(:terminus_class=) + Puppet::Node.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + end + + it "should operate in agent run_mode" do + @puppetd.class.run_mode.name.should == :agent + end + + it "should ask Puppet::Application to parse Puppet configuration file" do + @puppetd.should_parse_config?.should be_true + end + + it "should declare a main command" do + @puppetd.should respond_to(:main) + end + + it "should declare a onetime command" do + @puppetd.should respond_to(:onetime) + end + + it "should declare a fingerprint command" do + @puppetd.should respond_to(:fingerprint) + end + + it "should declare a preinit block" do + @puppetd.should respond_to(:preinit) + end + + describe "in preinit" do + it "should catch INT" do + Signal.expects(:trap).with { |arg,block| arg == :INT } + + @puppetd.preinit + end + + it "should init client to true" do + @puppetd.preinit + + @puppetd.options[:client].should be_true + end + + it "should init fqdn to nil" do + @puppetd.preinit + + @puppetd.options[:fqdn].should be_nil + end + + it "should init serve to []" do + @puppetd.preinit + + @puppetd.options[:serve].should == [] + end + + it "should use MD5 as default digest algorithm" do + @puppetd.preinit + + @puppetd.options[:digest].should == :MD5 + end + + it "should not fingerprint by default" do + @puppetd.preinit + + @puppetd.options[:fingerprint].should be_false + end + end + + describe "when handling options" do + before do + @puppetd.command_line.stubs(:args).returns([]) + end + + [:centrallogging, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| + it "should declare handle_#{option} method" do + @puppetd.should respond_to("handle_#{option}".to_sym) + end + + it "should store argument value when calling handle_#{option}" do + @puppetd.options.expects(:[]=).with(option, 'arg') + @puppetd.send("handle_#{option}".to_sym, 'arg') + end + end + + describe "when handling --disable" do + it "should declare handle_disable method" do + @puppetd.should respond_to(:handle_disable) + end + + it "should set disable to true" do + @puppetd.options.stubs(:[]=) + @puppetd.options.expects(:[]=).with(:disable, true) + @puppetd.handle_disable('') + end + + it "should store disable message" do + @puppetd.options.stubs(:[]=) + @puppetd.options.expects(:[]=).with(:disable_message, "message") + @puppetd.handle_disable('message') + end + end + + it "should set an existing handler on server" do + Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) + + @puppetd.handle_serve("handler") + @puppetd.options[:serve].should == [ :handler ] + end + + it "should set client to false with --no-client" do + @puppetd.handle_no_client(nil) + @puppetd.options[:client].should be_false + end + + it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do + Puppet[:onetime] = true + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) + @puppetd.setup_host + end + + it "should use supplied waitforcert when --onetime is specified" do + Puppet[:onetime] = true + @puppetd.handle_waitforcert(60) + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) + @puppetd.setup_host + end + + it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) + @puppetd.setup_host + end + + it "should set the log destination with --logdest" do + @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } + Puppet::Log.expects(:newdestination).with("console") + + @puppetd.handle_logdest("console") + end + + it "should put the setdest options to true" do + @puppetd.options.expects(:[]=).with(:setdest,true) + + @puppetd.handle_logdest("console") + end + + it "should parse the log destination from the command line" do + @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) + + Puppet::Util::Log.expects(:newdestination).with("/my/file") + + @puppetd.parse_options + end + + it "should store the waitforcert options with --waitforcert" do + @puppetd.options.expects(:[]=).with(:waitforcert,42) + + @puppetd.handle_waitforcert("42") + end + + it "should set args[:Port] with --port" do + @puppetd.handle_port("42") + @puppetd.args[:Port].should == "42" + end + + end + + describe "during setup" do + before :each do + @puppetd.options.stubs(:[]) + Puppet.stubs(:info) + FileTest.stubs(:exists?).returns(true) + Puppet[:libdir] = "/dev/null/lib" + Puppet::SSL::Host.stubs(:ca_location=) + Puppet::Transaction::Report.indirection.stubs(:terminus_class=) + Puppet::Transaction::Report.indirection.stubs(:cache_class=) + Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) + Puppet::Resource::Catalog.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + @host = stub_everything 'host' + Puppet::SSL::Host.stubs(:new).returns(@host) + Puppet.stubs(:settraps) + end + + describe "with --test" do + before :each do + #Puppet.settings.stubs(:handlearg) + @puppetd.options.stubs(:[]=) + end + + it "should call setup_test" do + @puppetd.options.stubs(:[]).with(:test).returns(true) + @puppetd.expects(:setup_test) + @puppetd.setup + end + + it "should set options[:verbose] to true" do + @puppetd.options.expects(:[]=).with(:verbose,true) + @puppetd.setup_test + end + it "should set options[:onetime] to true" do + Puppet[:onetime] = false + @puppetd.setup_test + Puppet[:onetime].should == true + end + it "should set options[:detailed_exitcodes] to true" do + @puppetd.options.expects(:[]=).with(:detailed_exitcodes,true) + @puppetd.setup_test + end + end + + it "should call setup_logs" do + @puppetd.expects(:setup_logs) + @puppetd.setup + end + + describe "when setting up logs" do + before :each do + Puppet::Util::Log.stubs(:newdestination) + end + + it "should set log level to debug if --debug was passed" do + @puppetd.options.stubs(:[]).with(:debug).returns(true) + @puppetd.setup_logs + Puppet::Util::Log.level.should == :debug + end + + it "should set log level to info if --verbose was passed" do + @puppetd.options.stubs(:[]).with(:verbose).returns(true) + @puppetd.setup_logs + Puppet::Util::Log.level.should == :info + end + + [:verbose, :debug].each do |level| + it "should set console as the log destination with level #{level}" do + @puppetd.options.stubs(:[]).with(level).returns(true) + + Puppet::Util::Log.expects(:newdestination).with(:console) + + @puppetd.setup_logs + end + end + + it "should set syslog as the log destination if no --logdest" do + @puppetd.options.stubs(:[]).with(:setdest).returns(false) + + Puppet::Util::Log.expects(:newdestination).with(:syslog) + + @puppetd.setup_logs + end + + end + + it "should print puppet config if asked to in Puppet config" do + Puppet[:configprint] = "pluginsync" + Puppet.settings.expects(:print_configs).returns true + expect { @puppetd.setup }.to exit_with 0 + end + + it "should exit after printing puppet config if asked to in Puppet config" do + Puppet[:modulepath] = '/my/path' + Puppet[:configprint] = "modulepath" + Puppet::Util::Settings.any_instance.expects(:puts).with('/my/path') + expect { @puppetd.setup }.to exit_with 0 + end + + it "should set a central log destination with --centrallogs" do + @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) + Puppet[:server] = "puppet.reductivelabs.com" + Puppet::Util::Log.stubs(:newdestination).with(:syslog) + + Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") + + @puppetd.setup + end + + it "should use :main, :puppetd, and :ssl" do + Puppet.settings.expects(:use).with(:main, :agent, :ssl) + + @puppetd.setup + end + + it "should install a remote ca location" do + Puppet::SSL::Host.expects(:ca_location=).with(:remote) + + @puppetd.setup + end + + it "should install a none ca location in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + Puppet::SSL::Host.expects(:ca_location=).with(:none) + + @puppetd.setup + end + + it "should tell the report handler to use REST" do + Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) + + @puppetd.setup + end + + it "should tell the report handler to cache locally as yaml" do + Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) + + @puppetd.setup + end + + it "should change the catalog_terminus setting to 'rest'" do + Puppet[:catalog_terminus] = :foo + @puppetd.setup + Puppet[:catalog_terminus].should == :rest + end + + it "should tell the catalog handler to use cache" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) + + @puppetd.setup + end + + it "should change the facts_terminus setting to 'facter'" do + Puppet[:facts_terminus] = :foo + + @puppetd.setup + Puppet[:facts_terminus].should == :facter + end + + it "should create an agent" do + Puppet::Agent.stubs(:new).with(Puppet::Configurer) + + @puppetd.setup + end + + [:enable, :disable].each do |action| + it "should delegate to enable_disable_client if we #{action} the agent" do + @puppetd.options.stubs(:[]).with(action).returns(true) + @puppetd.expects(:enable_disable_client).with(@agent) + + @puppetd.setup + end + end + + describe "when enabling or disabling agent" do + [:enable, :disable].each do |action| + it "should call client.#{action}" do + @puppetd.options.stubs(:[]).with(action).returns(true) + @agent.expects(action) + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + end + + it "should pass the disable message when disabling" do + @puppetd.options.stubs(:[]).with(:disable).returns(true) + @puppetd.options.stubs(:[]).with(:disable_message).returns("message") + @agent.expects(:disable).with("message") + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + + it "should pass the default disable message when disabling without a message" do + @puppetd.options.stubs(:[]).with(:disable).returns(true) + @puppetd.options.stubs(:[]).with(:disable_message).returns(nil) + @agent.expects(:disable).with("reason not specified") + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + + it "should finally exit" do + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + end + + it "should inform the daemon about our agent if :client is set to 'true'" do + @puppetd.options.expects(:[]).with(:client).returns true + @daemon.expects(:agent=).with(@agent) + @puppetd.setup + end + + it "should not inform the daemon about our agent if :client is set to 'false'" do + @puppetd.options[:client] = false + @daemon.expects(:agent=).never + @puppetd.setup + end + + it "should daemonize if needed" do + Puppet.features.stubs(:microsoft_windows?).returns false + Puppet[:daemonize] = true + + @daemon.expects(:daemonize) + + @puppetd.setup + end + + it "should wait for a certificate" do + @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).with(123) + + @puppetd.setup + end + + it "should not wait for a certificate in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).never + + @puppetd.setup + end + + it "should setup listen if told to and not onetime" do + Puppet[:listen] = true + @puppetd.options.stubs(:[]).with(:onetime).returns(false) + + @puppetd.expects(:setup_listen) + + @puppetd.setup + end + + describe "when setting up listen" do + before :each do + Puppet[:authconfig] = 'auth' + FileTest.stubs(:exists?).with('auth').returns(true) + File.stubs(:exist?).returns(true) + @puppetd.options.stubs(:[]).with(:serve).returns([]) + @server = stub_everything 'server' + Puppet::Network::Server.stubs(:new).returns(@server) + end + + + it "should exit if no authorization file" do + Puppet.stubs(:err) + FileTest.stubs(:exists?).with(Puppet[:rest_authconfig]).returns(false) + expect { @puppetd.setup_listen }.to exit_with 14 + end + + it "should create a server to listen on at least the Runner handler" do + Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Runner] } + + @puppetd.setup_listen + end + + it "should create a server to listen for specific handlers" do + @puppetd.options.stubs(:[]).with(:serve).returns([:handler]) + Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:handler] } + + @puppetd.setup_listen + end + + it "should use puppet default port" do + Puppet[:puppetport] = 32768 + + Puppet::Network::Server.expects(:new).with { |args| args[:port] == 32768 } + + @puppetd.setup_listen + end + end + + describe "when setting up for fingerprint" do + before(:each) do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + end + + it "should not setup as an agent" do + @puppetd.expects(:setup_agent).never + @puppetd.setup + end + + it "should not create an agent" do + Puppet::Agent.stubs(:new).with(Puppet::Configurer).never + @puppetd.setup + end + + it "should not daemonize" do + @daemon.expects(:daemonize).never + @puppetd.setup + end + + it "should setup our certificate host" do + @puppetd.expects(:setup_host) + @puppetd.setup + end + end + end + + + describe "when running" do + before :each do + @puppetd.agent = @agent + @puppetd.daemon = @daemon + @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) + end + + it "should dispatch to fingerprint if --fingerprint is used" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + + @puppetd.stubs(:fingerprint) + @puppetd.run_command + end + + it "should dispatch to onetime if --onetime is used" do + @puppetd.options.stubs(:[]).with(:onetime).returns(true) + + @puppetd.stubs(:onetime) + @puppetd.run_command + end + + it "should dispatch to main if --onetime and --fingerprint are not used" do + @puppetd.options.stubs(:[]).with(:onetime).returns(false) + + @puppetd.stubs(:main) + @puppetd.run_command + end + + describe "with --onetime" do + + before :each do + @agent.stubs(:run).returns(:report) + @puppetd.options.stubs(:[]).with(:client).returns(:client) + @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) + Puppet.stubs(:newservice) + end + + it "should exit if no defined --client" do + $stderr.stubs(:puts) + @puppetd.options.stubs(:[]).with(:client).returns(nil) + expect { @puppetd.onetime }.to exit_with 43 + end + + it "should setup traps" do + @daemon.expects(:set_signal_traps) + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should let the agent run" do + @agent.expects(:run).returns(:report) + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should finish by exiting with 0 error code" do + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should stop the daemon" do + @daemon.expects(:stop).with(:exit => false) + expect { @puppetd.onetime }.to exit_with 0 + end + + describe "and --detailed-exitcodes" do + before :each do + @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(true) + end + + it "should exit with report's computed exit status" do + Puppet[:noop] = false + report = stub 'report', :exit_status => 666 + @agent.stubs(:run).returns(report) + + expect { @puppetd.onetime }.to exit_with 666 + end + + it "should exit with the report's computer exit status, even if --noop is set." do + Puppet[:noop] = true + report = stub 'report', :exit_status => 666 + @agent.stubs(:run).returns(report) + + expect { @puppetd.onetime }.to exit_with 666 + end + end + end + + describe "with --fingerprint" do + before :each do + @cert = stub_everything 'cert' + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) + @host = stub_everything 'host' + @puppetd.stubs(:host).returns(@host) + end + + it "should fingerprint the certificate if it exists" do + @host.expects(:certificate).returns(@cert) + @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" + @puppetd.fingerprint + end + + it "should fingerprint the certificate request if no certificate have been signed" do + @host.expects(:certificate).returns(nil) + @host.expects(:certificate_request).returns(@cert) + @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" + @puppetd.fingerprint + end + + it "should display the fingerprint" do + @host.stubs(:certificate).returns(@cert) + @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") + + @puppetd.expects(:puts).with "DIGEST" + + @puppetd.fingerprint + end + end + + describe "without --onetime and --fingerprint" do + before :each do + Puppet.stubs(:notice) + @puppetd.options.stubs(:[]).with(:client) + end + + it "should start our daemon" do + @daemon.expects(:start) + + @puppetd.main + end + end + end +end diff -uNr puppet/Rakefile.~1~ puppet-new/Rakefile.~1~ --- puppet/Rakefile.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/Rakefile.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,53 @@ +# Rakefile for Puppet -*- ruby -*- + +$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') + +require 'rake' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rspec' +require "rspec/core/rake_task" + +module Puppet + PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" +end + +Dir['tasks/**/*.rake'].each { |t| load t } + +FILES = FileList[ + '[A-Z]*', + 'install.rb', + 'bin/**/*', + 'sbin/**/*', + 'lib/**/*', + 'conf/**/*', + 'man/**/*', + 'examples/**/*', + 'ext/**/*', + 'tasks/**/*', + 'test/**/*', + 'spec/**/*' +] + +Rake::PackageTask.new("puppet", Puppet::PUPPETVERSION) do |pkg| + pkg.package_dir = 'pkg' + pkg.need_tar_gz = true + pkg.package_files = FILES.to_a +end + +task :default do + sh %{rake -T} +end + +desc "Create the tarball and the gem - use when releasing" +task :puppetpackages => [:create_gem, :package] + +RSpec::Core::RakeTask.new do |t| + t.pattern ='spec/{unit,integration}/**/*.rb' + t.fail_on_error = true +end + +desc "Run the unit tests" +task :unit do + Dir.chdir("test") { sh "rake" } +end diff -uNr puppet/spec/unit/agent/disabler_spec.rb puppet-new/spec/unit/agent/disabler_spec.rb --- puppet/spec/unit/agent/disabler_spec.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/spec/unit/agent/disabler_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -8,9 +8,18 @@ end describe Puppet::Agent::Disabler do - before do + before(:all) do + @lockdir = Dir.mktmpdir("disabler_spec_tmpdir") + @lockfile = File.join(@lockdir, "lock") + end + + after(:all) do + FileUtils.rm_rf(@lockdir) + end + + before(:each) do @locker = LockerTester.new - @locker.stubs(:lockfile_path).returns "/my/lock" + @locker.stubs(:lockfile_path).returns @lockfile end it "should use an AnonymousFilelock instance as its disable_lockfile" do @@ -18,9 +27,9 @@ 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.expects(:lockfile_path).returns @lockfile + lock = Puppet::Util::AnonymousFilelock.new(@lockfile) + Puppet::Util::AnonymousFilelock.expects(:new).with(@lockfile + ".disabled").returns lock @locker.disable_lockfile end @@ -57,4 +66,61 @@ @locker.disable_lockfile.expects(:message).returns("message") @locker.disable_message.should == "message" end + + describe "when enabling" do + + # this is for backwards compatibility with puppet versions prior to 2.7.10. + # for more detailed information, see the comments in the "#check_for_old_lockfile" method, + # in disabler.rb --cprice 2012-02-28 + describe "when a lockfile with the old filename already exists" do + let(:warning_prefix) { "Found an agent lock file at path '#{@lockfile}'" } + + after(:each) do + File.delete(@lockfile) if File.exists?(@lockfile) + end + + describe "when the lockfile is empty" do + before (:each) do + FileUtils.touch(@lockfile) + end + + it "should assume it was created by --disable in an old version of puppet, print a warning, and remove it" do + Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*Deleting the empty file/ } + + @locker.enable + + File.exists?(@lockfile).should == false + end + end + + describe "when the lockfile contains a pid" do + before (:each) do + File.open(@lockfile, "w") { |f| f.print(12345) } + end + + it "should assume that there may be a running agent process, and print a warning" do + Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*appears that a puppet agent process is already running/ } + + @locker.enable + + File.exists?(@lockfile).should == true + end + end + + describe "when the lockfile contains something other than a pid" do + before (:each) do + File.open(@lockfile, "w") { |f| f.print("Foo\nbar\n\baz") } + end + + it "should admit that it doesn't know what's going on, and print a warning" do + Puppet.expects(:warning).with { |msg| msg =~ /^#{warning_prefix}.*unable to determine whether an existing agent is running or not/ } + + @locker.enable + + File.exists?(@lockfile).should == true + end + end + end + + end end diff -uNr puppet/spec/unit/agent/disabler_spec.rb.~1~ puppet-new/spec/unit/agent/disabler_spec.rb.~1~ --- puppet/spec/unit/agent/disabler_spec.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/spec/unit/agent/disabler_spec.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,60 @@ +#!/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 disable with a message" do + @locker.disable_lockfile.expects(:lock).with("disabled because") + + @locker.disable("disabled because") + 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 + + it "should report the disable message when disabled" do + @locker.disable_lockfile.expects(:message).returns("message") + @locker.disable_message.should == "message" + end +end diff -uNr puppet/spec/unit/agent/locker_spec.rb puppet-new/spec/unit/agent/locker_spec.rb --- puppet/spec/unit/agent/locker_spec.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/spec/unit/agent/locker_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -29,6 +29,18 @@ @locker.lockfile.should equal(@locker.lockfile) end + it "should use the lock file to anonymously lock the process when disabled" do + @locker.lockfile.expects(:lock).with(:anonymous => true) + + @locker.disable + end + + it "should use the lock file to anonymously unlock the process when enabled" do + @locker.lockfile.expects(:unlock).with(:anonymous => true) + + @locker.enable + end + it "should have a method that yields when a lock is attained" do @locker.lockfile.expects(:lock).returns true diff -uNr puppet/spec/unit/agent/locker_spec.rb.~1~ puppet-new/spec/unit/agent/locker_spec.rb.~1~ --- puppet/spec/unit/agent/locker_spec.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/spec/unit/agent/locker_spec.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +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 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 diff -uNr puppet/spec/unit/agent_backward_compatibility_spec.rb puppet-new/spec/unit/agent_backward_compatibility_spec.rb --- puppet/spec/unit/agent_backward_compatibility_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/spec/unit/agent_backward_compatibility_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,152 @@ +#!/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(:disabled_regex) { /^Skipping run of .*; administratively disabled/ } + + 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 'disabled'" do + agent.should be_disabled + end + + it "should not try to start a new agent run" do + AgentTestClient.expects(:new).never + Puppet.expects(:notice).with(regexp_matches(disabled_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(regexp_matches(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 + agent.should be_disabled + 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(regexp_matches(disabled_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(regexp_matches(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 + agent.should be_disabled + end + + it "should warn and clean up the 2.7.10/2.7.11 lockfile" do + Puppet.expects(:warning).with(regexp_matches(warning_regex)) + + agent.enable + + File.exists?(disabled_lockfile_path).should == false + File.exists?(lockfile_path).should == false + end + end + end + end + end + + +end diff -uNr puppet/spec/unit/agent_spec.rb puppet-new/spec/unit/agent_spec.rb --- puppet/spec/unit/agent_spec.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/spec/unit/agent_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -24,7 +24,6 @@ # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields - @agent.stubs(:disabled?).returns(false) # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. without_warnings { Puppet::Application = Class.new(Puppet::Application) } @@ -58,6 +57,13 @@ client.expects(:run) @agent.stubs(:running?).returns false + @agent.stubs(:disabled?).returns false + @agent.run + end + + it "should do nothing if disabled" do + @agent.expects(:disabled?).returns(true) + AgentTestClient.expects(:new).never @agent.run end @@ -66,24 +72,34 @@ @agent.lockfile_path.should == "/my/lock" end - it "should be considered running if the lock file is locked" do + it "should be considered running if the lock file is locked and not anonymous" do lockfile = mock 'lockfile' - @agent.expects(:lockfile).returns lockfile + @agent.expects(:lockfile).returns(lockfile).twice 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(:lockfile).returns(lockfile).at_least_once + lockfile.expects(:locked?).returns(true).at_least_once + lockfile.expects(:anonymous?).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.stubs(:running?).returns false @agent.run end @@ -100,6 +116,12 @@ @agent.run end + it "should display an informative message if the agent is administratively disabled" do + @agent.expects(:disabled?).returns true + Puppet.expects(:notice).with(regexp_matches(/Skipping run of .*; administratively disabled/)) + @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) diff -uNr puppet/spec/unit/agent_spec.rb.~1~ puppet-new/spec/unit/agent_spec.rb.~1~ --- puppet/spec/unit/agent_spec.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/spec/unit/agent_spec.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,285 @@ +#!/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 + @agent.stubs(:disabled?).returns(false) + + # 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.run + end + + it "should determine its lock file path by asking the client class" do + AgentTestClient.expects(:lockfile_path).returns "/my/lock" + @agent.lockfile_path.should == "/my/lock" + end + + it "should be considered running if the lock file is locked" do + lockfile = mock 'lockfile' + + @agent.expects(:lockfile).returns lockfile + lockfile.expects(:locked?).returns true + + @agent.should be_running + end + + describe "when being run" do + before do + AgentTestClient.stubs(:lockfile_path).returns "/my/lock" + @agent.stubs(:running?).returns false + end + + it "should splay" do + @agent.expects(:splay) + @agent.stubs(:running?).returns false + + @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 "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 + 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 + + describe "when starting" do + before do + @agent.stubs(:observe_signal) + end + + it "should create a timer with the runinterval, a tolerance of 1, and :start? set to true" do + Puppet.settings.expects(:value).with(:runinterval).returns 5 + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).returns timer + + @agent.stubs(:run) + @agent.start + end + + it "should run once immediately" do + timer = mock 'timer' + EventLoop::Timer.expects(:new).returns timer + + timer.expects(:sound_alarm) + + @agent.start + end + + it "should run within the block passed to the timer" do + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).returns(timer).yields + @agent.expects(:run) + + @agent.start + end + end +end diff -uNr puppet/spec/unit/application/agent_spec.rb puppet-new/spec/unit/application/agent_spec.rb --- puppet/spec/unit/application/agent_spec.rb 2012-03-09 09:59:11.969703000 +0000 +++ puppet-new/spec/unit/application/agent_spec.rb 2012-03-09 09:53:57.874478000 +0000 @@ -91,7 +91,7 @@ @puppetd.command_line.stubs(:args).returns([]) end - [:centrallogging, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| + [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end @@ -102,24 +102,6 @@ end end - describe "when handling --disable" do - it "should declare handle_disable method" do - @puppetd.should respond_to(:handle_disable) - end - - it "should set disable to true" do - @puppetd.options.stubs(:[]=) - @puppetd.options.expects(:[]=).with(:disable, true) - @puppetd.handle_disable('') - end - - it "should store disable message" do - @puppetd.options.stubs(:[]=) - @puppetd.options.expects(:[]=).with(:disable_message, "message") - @puppetd.handle_disable('message') - end - end - it "should set an existing handler on server" do Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) @@ -367,20 +349,6 @@ end end - it "should pass the disable message when disabling" do - @puppetd.options.stubs(:[]).with(:disable).returns(true) - @puppetd.options.stubs(:[]).with(:disable_message).returns("message") - @agent.expects(:disable).with("message") - expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 - end - - it "should pass the default disable message when disabling without a message" do - @puppetd.options.stubs(:[]).with(:disable).returns(true) - @puppetd.options.stubs(:[]).with(:disable_message).returns(nil) - @agent.expects(:disable).with("reason not specified") - expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 - end - it "should finally exit" do expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end diff -uNr puppet/spec/unit/application/agent_spec.rb.~1~ puppet-new/spec/unit/application/agent_spec.rb.~1~ --- puppet/spec/unit/application/agent_spec.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/spec/unit/application/agent_spec.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,631 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/agent' +require 'puppet/application/agent' +require 'puppet/network/server' +require 'puppet/daemon' +require 'puppet/network/handler' + +describe Puppet::Application::Agent do + before :each do + @puppetd = Puppet::Application[:agent] + @puppetd.stubs(:puts) + @daemon = stub_everything 'daemon' + Puppet::Daemon.stubs(:new).returns(@daemon) + Puppet[:daemonize] = false + @agent = stub_everything 'agent' + Puppet::Agent.stubs(:new).returns(@agent) + @puppetd.preinit + Puppet::Util::Log.stubs(:newdestination) + + Puppet::Node.indirection.stubs(:terminus_class=) + Puppet::Node.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + end + + it "should operate in agent run_mode" do + @puppetd.class.run_mode.name.should == :agent + end + + it "should ask Puppet::Application to parse Puppet configuration file" do + @puppetd.should_parse_config?.should be_true + end + + it "should declare a main command" do + @puppetd.should respond_to(:main) + end + + it "should declare a onetime command" do + @puppetd.should respond_to(:onetime) + end + + it "should declare a fingerprint command" do + @puppetd.should respond_to(:fingerprint) + end + + it "should declare a preinit block" do + @puppetd.should respond_to(:preinit) + end + + describe "in preinit" do + it "should catch INT" do + Signal.expects(:trap).with { |arg,block| arg == :INT } + + @puppetd.preinit + end + + it "should init client to true" do + @puppetd.preinit + + @puppetd.options[:client].should be_true + end + + it "should init fqdn to nil" do + @puppetd.preinit + + @puppetd.options[:fqdn].should be_nil + end + + it "should init serve to []" do + @puppetd.preinit + + @puppetd.options[:serve].should == [] + end + + it "should use MD5 as default digest algorithm" do + @puppetd.preinit + + @puppetd.options[:digest].should == :MD5 + end + + it "should not fingerprint by default" do + @puppetd.preinit + + @puppetd.options[:fingerprint].should be_false + end + end + + describe "when handling options" do + before do + @puppetd.command_line.stubs(:args).returns([]) + end + + [:centrallogging, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| + it "should declare handle_#{option} method" do + @puppetd.should respond_to("handle_#{option}".to_sym) + end + + it "should store argument value when calling handle_#{option}" do + @puppetd.options.expects(:[]=).with(option, 'arg') + @puppetd.send("handle_#{option}".to_sym, 'arg') + end + end + + describe "when handling --disable" do + it "should declare handle_disable method" do + @puppetd.should respond_to(:handle_disable) + end + + it "should set disable to true" do + @puppetd.options.stubs(:[]=) + @puppetd.options.expects(:[]=).with(:disable, true) + @puppetd.handle_disable('') + end + + it "should store disable message" do + @puppetd.options.stubs(:[]=) + @puppetd.options.expects(:[]=).with(:disable_message, "message") + @puppetd.handle_disable('message') + end + end + + it "should set an existing handler on server" do + Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) + + @puppetd.handle_serve("handler") + @puppetd.options[:serve].should == [ :handler ] + end + + it "should set client to false with --no-client" do + @puppetd.handle_no_client(nil) + @puppetd.options[:client].should be_false + end + + it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do + Puppet[:onetime] = true + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) + @puppetd.setup_host + end + + it "should use supplied waitforcert when --onetime is specified" do + Puppet[:onetime] = true + @puppetd.handle_waitforcert(60) + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) + @puppetd.setup_host + end + + it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) + @puppetd.setup_host + end + + it "should set the log destination with --logdest" do + @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } + Puppet::Log.expects(:newdestination).with("console") + + @puppetd.handle_logdest("console") + end + + it "should put the setdest options to true" do + @puppetd.options.expects(:[]=).with(:setdest,true) + + @puppetd.handle_logdest("console") + end + + it "should parse the log destination from the command line" do + @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) + + Puppet::Util::Log.expects(:newdestination).with("/my/file") + + @puppetd.parse_options + end + + it "should store the waitforcert options with --waitforcert" do + @puppetd.options.expects(:[]=).with(:waitforcert,42) + + @puppetd.handle_waitforcert("42") + end + + it "should set args[:Port] with --port" do + @puppetd.handle_port("42") + @puppetd.args[:Port].should == "42" + end + + end + + describe "during setup" do + before :each do + @puppetd.options.stubs(:[]) + Puppet.stubs(:info) + FileTest.stubs(:exists?).returns(true) + Puppet[:libdir] = "/dev/null/lib" + Puppet::SSL::Host.stubs(:ca_location=) + Puppet::Transaction::Report.indirection.stubs(:terminus_class=) + Puppet::Transaction::Report.indirection.stubs(:cache_class=) + Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) + Puppet::Resource::Catalog.indirection.stubs(:cache_class=) + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + @host = stub_everything 'host' + Puppet::SSL::Host.stubs(:new).returns(@host) + Puppet.stubs(:settraps) + end + + describe "with --test" do + before :each do + #Puppet.settings.stubs(:handlearg) + @puppetd.options.stubs(:[]=) + end + + it "should call setup_test" do + @puppetd.options.stubs(:[]).with(:test).returns(true) + @puppetd.expects(:setup_test) + @puppetd.setup + end + + it "should set options[:verbose] to true" do + @puppetd.options.expects(:[]=).with(:verbose,true) + @puppetd.setup_test + end + it "should set options[:onetime] to true" do + Puppet[:onetime] = false + @puppetd.setup_test + Puppet[:onetime].should == true + end + it "should set options[:detailed_exitcodes] to true" do + @puppetd.options.expects(:[]=).with(:detailed_exitcodes,true) + @puppetd.setup_test + end + end + + it "should call setup_logs" do + @puppetd.expects(:setup_logs) + @puppetd.setup + end + + describe "when setting up logs" do + before :each do + Puppet::Util::Log.stubs(:newdestination) + end + + it "should set log level to debug if --debug was passed" do + @puppetd.options.stubs(:[]).with(:debug).returns(true) + @puppetd.setup_logs + Puppet::Util::Log.level.should == :debug + end + + it "should set log level to info if --verbose was passed" do + @puppetd.options.stubs(:[]).with(:verbose).returns(true) + @puppetd.setup_logs + Puppet::Util::Log.level.should == :info + end + + [:verbose, :debug].each do |level| + it "should set console as the log destination with level #{level}" do + @puppetd.options.stubs(:[]).with(level).returns(true) + + Puppet::Util::Log.expects(:newdestination).with(:console) + + @puppetd.setup_logs + end + end + + it "should set syslog as the log destination if no --logdest" do + @puppetd.options.stubs(:[]).with(:setdest).returns(false) + + Puppet::Util::Log.expects(:newdestination).with(:syslog) + + @puppetd.setup_logs + end + + end + + it "should print puppet config if asked to in Puppet config" do + Puppet[:configprint] = "pluginsync" + Puppet.settings.expects(:print_configs).returns true + expect { @puppetd.setup }.to exit_with 0 + end + + it "should exit after printing puppet config if asked to in Puppet config" do + Puppet[:modulepath] = '/my/path' + Puppet[:configprint] = "modulepath" + Puppet::Util::Settings.any_instance.expects(:puts).with('/my/path') + expect { @puppetd.setup }.to exit_with 0 + end + + it "should set a central log destination with --centrallogs" do + @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) + Puppet[:server] = "puppet.reductivelabs.com" + Puppet::Util::Log.stubs(:newdestination).with(:syslog) + + Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") + + @puppetd.setup + end + + it "should use :main, :puppetd, and :ssl" do + Puppet.settings.expects(:use).with(:main, :agent, :ssl) + + @puppetd.setup + end + + it "should install a remote ca location" do + Puppet::SSL::Host.expects(:ca_location=).with(:remote) + + @puppetd.setup + end + + it "should install a none ca location in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + Puppet::SSL::Host.expects(:ca_location=).with(:none) + + @puppetd.setup + end + + it "should tell the report handler to use REST" do + Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) + + @puppetd.setup + end + + it "should tell the report handler to cache locally as yaml" do + Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) + + @puppetd.setup + end + + it "should change the catalog_terminus setting to 'rest'" do + Puppet[:catalog_terminus] = :foo + @puppetd.setup + Puppet[:catalog_terminus].should == :rest + end + + it "should tell the catalog handler to use cache" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) + + @puppetd.setup + end + + it "should change the facts_terminus setting to 'facter'" do + Puppet[:facts_terminus] = :foo + + @puppetd.setup + Puppet[:facts_terminus].should == :facter + end + + it "should create an agent" do + Puppet::Agent.stubs(:new).with(Puppet::Configurer) + + @puppetd.setup + end + + [:enable, :disable].each do |action| + it "should delegate to enable_disable_client if we #{action} the agent" do + @puppetd.options.stubs(:[]).with(action).returns(true) + @puppetd.expects(:enable_disable_client).with(@agent) + + @puppetd.setup + end + end + + describe "when enabling or disabling agent" do + [:enable, :disable].each do |action| + it "should call client.#{action}" do + @puppetd.options.stubs(:[]).with(action).returns(true) + @agent.expects(action) + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + end + + it "should pass the disable message when disabling" do + @puppetd.options.stubs(:[]).with(:disable).returns(true) + @puppetd.options.stubs(:[]).with(:disable_message).returns("message") + @agent.expects(:disable).with("message") + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + + it "should pass the default disable message when disabling without a message" do + @puppetd.options.stubs(:[]).with(:disable).returns(true) + @puppetd.options.stubs(:[]).with(:disable_message).returns(nil) + @agent.expects(:disable).with("reason not specified") + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + + it "should finally exit" do + expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 + end + end + + it "should inform the daemon about our agent if :client is set to 'true'" do + @puppetd.options.expects(:[]).with(:client).returns true + @daemon.expects(:agent=).with(@agent) + @puppetd.setup + end + + it "should not inform the daemon about our agent if :client is set to 'false'" do + @puppetd.options[:client] = false + @daemon.expects(:agent=).never + @puppetd.setup + end + + it "should daemonize if needed" do + Puppet.features.stubs(:microsoft_windows?).returns false + Puppet[:daemonize] = true + + @daemon.expects(:daemonize) + + @puppetd.setup + end + + it "should wait for a certificate" do + @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).with(123) + + @puppetd.setup + end + + it "should not wait for a certificate in fingerprint mode" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).never + + @puppetd.setup + end + + it "should setup listen if told to and not onetime" do + Puppet[:listen] = true + @puppetd.options.stubs(:[]).with(:onetime).returns(false) + + @puppetd.expects(:setup_listen) + + @puppetd.setup + end + + describe "when setting up listen" do + before :each do + Puppet[:authconfig] = 'auth' + FileTest.stubs(:exists?).with('auth').returns(true) + File.stubs(:exist?).returns(true) + @puppetd.options.stubs(:[]).with(:serve).returns([]) + @server = stub_everything 'server' + Puppet::Network::Server.stubs(:new).returns(@server) + end + + + it "should exit if no authorization file" do + Puppet.stubs(:err) + FileTest.stubs(:exists?).with(Puppet[:rest_authconfig]).returns(false) + expect { @puppetd.setup_listen }.to exit_with 14 + end + + it "should create a server to listen on at least the Runner handler" do + Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Runner] } + + @puppetd.setup_listen + end + + it "should create a server to listen for specific handlers" do + @puppetd.options.stubs(:[]).with(:serve).returns([:handler]) + Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:handler] } + + @puppetd.setup_listen + end + + it "should use puppet default port" do + Puppet[:puppetport] = 32768 + + Puppet::Network::Server.expects(:new).with { |args| args[:port] == 32768 } + + @puppetd.setup_listen + end + end + + describe "when setting up for fingerprint" do + before(:each) do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + end + + it "should not setup as an agent" do + @puppetd.expects(:setup_agent).never + @puppetd.setup + end + + it "should not create an agent" do + Puppet::Agent.stubs(:new).with(Puppet::Configurer).never + @puppetd.setup + end + + it "should not daemonize" do + @daemon.expects(:daemonize).never + @puppetd.setup + end + + it "should setup our certificate host" do + @puppetd.expects(:setup_host) + @puppetd.setup + end + end + end + + + describe "when running" do + before :each do + @puppetd.agent = @agent + @puppetd.daemon = @daemon + @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) + end + + it "should dispatch to fingerprint if --fingerprint is used" do + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + + @puppetd.stubs(:fingerprint) + @puppetd.run_command + end + + it "should dispatch to onetime if --onetime is used" do + @puppetd.options.stubs(:[]).with(:onetime).returns(true) + + @puppetd.stubs(:onetime) + @puppetd.run_command + end + + it "should dispatch to main if --onetime and --fingerprint are not used" do + @puppetd.options.stubs(:[]).with(:onetime).returns(false) + + @puppetd.stubs(:main) + @puppetd.run_command + end + + describe "with --onetime" do + + before :each do + @agent.stubs(:run).returns(:report) + @puppetd.options.stubs(:[]).with(:client).returns(:client) + @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) + Puppet.stubs(:newservice) + end + + it "should exit if no defined --client" do + $stderr.stubs(:puts) + @puppetd.options.stubs(:[]).with(:client).returns(nil) + expect { @puppetd.onetime }.to exit_with 43 + end + + it "should setup traps" do + @daemon.expects(:set_signal_traps) + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should let the agent run" do + @agent.expects(:run).returns(:report) + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should finish by exiting with 0 error code" do + expect { @puppetd.onetime }.to exit_with 0 + end + + it "should stop the daemon" do + @daemon.expects(:stop).with(:exit => false) + expect { @puppetd.onetime }.to exit_with 0 + end + + describe "and --detailed-exitcodes" do + before :each do + @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(true) + end + + it "should exit with report's computed exit status" do + Puppet[:noop] = false + report = stub 'report', :exit_status => 666 + @agent.stubs(:run).returns(report) + + expect { @puppetd.onetime }.to exit_with 666 + end + + it "should exit with the report's computer exit status, even if --noop is set." do + Puppet[:noop] = true + report = stub 'report', :exit_status => 666 + @agent.stubs(:run).returns(report) + + expect { @puppetd.onetime }.to exit_with 666 + end + end + end + + describe "with --fingerprint" do + before :each do + @cert = stub_everything 'cert' + @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) + @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) + @host = stub_everything 'host' + @puppetd.stubs(:host).returns(@host) + end + + it "should fingerprint the certificate if it exists" do + @host.expects(:certificate).returns(@cert) + @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" + @puppetd.fingerprint + end + + it "should fingerprint the certificate request if no certificate have been signed" do + @host.expects(:certificate).returns(nil) + @host.expects(:certificate_request).returns(@cert) + @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" + @puppetd.fingerprint + end + + it "should display the fingerprint" do + @host.stubs(:certificate).returns(@cert) + @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") + + @puppetd.expects(:puts).with "DIGEST" + + @puppetd.fingerprint + end + end + + describe "without --onetime and --fingerprint" do + before :each do + Puppet.stubs(:notice) + @puppetd.options.stubs(:[]).with(:client) + end + + it "should start our daemon" do + @daemon.expects(:start) + + @puppetd.main + end + end + end +end diff -uNr puppet/test/lib/puppettest/fakes.rb.~1~ puppet-new/test/lib/puppettest/fakes.rb.~1~ --- puppet/test/lib/puppettest/fakes.rb.~1~ 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/test/lib/puppettest/fakes.rb.~1~ 2012-03-09 09:53:52.000000000 +0000 @@ -0,0 +1,199 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/puppet/util')) + +module PuppetTest + # A baseclass for the faketypes. + class FakeModel + include Puppet::Util + class << self + attr_accessor :name, :realresource + @name = :fakeresource + end + + def self.key_attributes + @realresource.key_attributes + end + + def self.validproperties + Puppet::Type.type(@name).validproperties + end + + def self.validproperty?(name) + Puppet::Type.type(@name).validproperty?(name) + end + + def self.to_s + "Fake#{@name.to_s.capitalize}" + end + + def [](param) + if @realresource.attrtype(param) == :property + @is[param] + else + @params[param] + end + end + + def []=(param, value) + param = symbolize(param) + raise Puppet::DevError, "Invalid attribute #{param} for #{@realresource.name}" unless @realresource.valid_parameter?(param) + if @realresource.attrtype(param) == :property + @should[param] = value + else + @params[param] = value + end + end + + def initialize(name) + @realresource = Puppet::Type.type(self.class.name) + raise "Could not find type #{self.class.name}" unless @realresource + @is = {} + @should = {} + @params = {} + self[@realresource.key_attributes.first] = name + end + + def inspect + "#{self.class.to_s.sub(/.+::/, '')}(#{super()})" + end + + def is(param) + @is[param] + end + + def should(param) + @should[param] + end + + def to_hash + hash = @params.dup + [@is, @should].each do |h| + h.each do |p, v| + hash[p] = v + end + end + hash + end + + def name + self[:name] + end + end + + class FakeProvider + attr_accessor :resource + class << self + attr_accessor :name, :resource_type, :methods + end + + # A very low number, so these never show up as defaults via the standard + # algorithms. + def self.defaultnum + -50 + end + + # Set up methods to fake things + def self.apimethods(*ary) + @resource_type.validproperties.each do |property| + ary << property unless ary.include? property + end + attr_accessor(*ary) + + @methods = ary + end + + def self.default? + false + end + + def self.initvars + @calls = Hash.new do |hash, key| + hash[key] = 0 + end + end + + def self.source + self.name + end + + def self.supports_parameter?(param) + true + end + + def self.suitable? + true + end + + def clear + @resource = nil + end + + def initialize(resource) + @resource = resource + end + + def properties + self.class.resource_type.validproperties.inject({}) do |props, name| + props[name] = self.send(name) || :absent + props + end + end + end + + class FakeParsedProvider < FakeProvider + def hash + ret = {} + instance_variables.each do |v| + v = v.sub("@", '') + if val = self.send(v) + ret[v.intern] = val + end + end + + ret + end + + def store(hash) + hash.each do |n, v| + method = n.to_s + "=" + send(method, v) if respond_to? method + end + end + end + + @@fakeresources = {} + @@fakeproviders = {} + + def fakeresource(type, name, options = {}) + type = type.intern if type.is_a? String + unless @@fakeresources.include? type + @@fakeresources[type] = Class.new(FakeModel) + @@fakeresources[type].name = type + + resource = Puppet::Type.type(type) + raise("Could not find type #{type}") unless resource + @@fakeresources[type].realresource = resource + end + + obj = @@fakeresources[type].new(name) + options.each do |name, val| + obj[name] = val + end + obj + end + + module_function :fakeresource + + def fakeprovider(type, resource) + type = type.intern if type.is_a? String + unless @@fakeproviders.include? type + @@fakeproviders[type] = Class.new(FakeModel) do + @name = type + end + end + + @@fakeproviders[type].new(resource) + end + + module_function :fakeprovider +end + diff -uNr puppet/test/util/pidlock.rb puppet-new/test/util/pidlock.rb --- puppet/test/util/pidlock.rb 1970-01-01 01:00:00.000000000 +0100 +++ puppet-new/test/util/pidlock.rb 2012-03-09 09:53:57.874478000 +0000 @@ -0,0 +1,125 @@ +require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') + +require 'puppet/util/pidlock' +require 'fileutils' + +# This is *fucked* *up* +Puppet.debug = false + +class TestPuppetUtilPidlock < Test::Unit::TestCase + include PuppetTest + + def setup + super + @workdir = tstdir + end + + def teardown + super + FileUtils.rm_rf(@workdir) + end + + def test_00_basic_create + l = nil + assert_nothing_raised { l = Puppet::Util::Pidlock.new(@workdir + '/nothingmuch') } + + assert_equal Puppet::Util::Pidlock, l.class + + assert_equal @workdir + '/nothingmuch', l.lockfile + end + + def test_10_uncontended_lock + l = Puppet::Util::Pidlock.new(@workdir + '/test_lock') + + assert !l.locked? + assert !l.mine? + assert l.lock + assert l.locked? + assert l.mine? + assert !l.anonymous? + # It's OK to call lock multiple times + assert l.lock + assert l.unlock + assert !l.locked? + assert !l.mine? + end + + def test_20_someone_elses_lock + childpid = nil + l = Puppet::Util::Pidlock.new(@workdir + '/someone_elses_lock') + + # First, we need a PID that's guaranteed to be (a) used, (b) someone + # else's, and (c) around for the life of this test. + childpid = fork { loop do; sleep 10; end } + + File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } + + assert l.locked? + assert !l.mine? + assert !l.lock + assert l.locked? + assert !l.mine? + assert !l.unlock + assert l.locked? + assert !l.mine? + ensure + Process.kill("KILL", childpid) unless childpid.nil? + end + + def test_30_stale_lock + # This is a bit hard to guarantee, but we need a PID that is definitely + # unused, and will stay so for the the life of this test. Our best + # bet is to create a process, get it's PID, let it die, and *then* + # lock on it. + childpid = fork { exit } + + # Now we can't continue until we're sure that the PID is dead + Process.wait(childpid) + + l = Puppet::Util::Pidlock.new(@workdir + '/stale_lock') + + # locked? should clear the lockfile + File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } + assert File.exists?(l.lockfile) + assert !l.locked? + assert !File.exists?(l.lockfile) + + # lock should replace the lockfile with our own + File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } + assert File.exists?(l.lockfile) + assert l.lock + assert l.locked? + assert l.mine? + + # unlock should fail, but should clear the stale lockfile. + File.open(l.lockfile, 'w') { |fd| fd.write(childpid) } + assert File.exists?(l.lockfile) + assert !l.unlock + assert !File.exists?(l.lockfile) + end + + def test_40_not_locked_at_all + l = Puppet::Util::Pidlock.new(@workdir + '/not_locked') + + assert !l.locked? + # We can't unlock if we don't hold the lock + assert !l.unlock + end + + def test_50_anonymous_lock + l = Puppet::Util::Pidlock.new(@workdir + '/anonymous_lock') + + assert !l.locked? + assert l.lock(:anonymous => true) + assert l.locked? + assert l.anonymous? + assert !l.mine? + assert "", File.read(l.lockfile) + assert !l.unlock + assert l.locked? + assert l.anonymous? + assert l.unlock(:anonymous => true) + assert !File.exists?(l.lockfile) + end +end +