diff options
-rwxr-xr-x | bin/puppetd | 26 | ||||
-rw-r--r-- | lib/puppet/agent.rb | 249 | ||||
-rw-r--r-- | lib/puppet/agent/locker.rb | 7 | ||||
-rw-r--r-- | lib/puppet/configurer.rb | 185 | ||||
-rw-r--r-- | lib/puppet/configurer/downloader.rb (renamed from lib/puppet/agent/downloader.rb) | 2 | ||||
-rw-r--r-- | lib/puppet/configurer/fact_handler.rb (renamed from lib/puppet/agent/fact_handler.rb) | 4 | ||||
-rw-r--r-- | lib/puppet/configurer/plugin_handler.rb (renamed from lib/puppet/agent/plugin_handler.rb) | 4 | ||||
-rwxr-xr-x | lib/puppet/daemon.rb | 2 | ||||
-rwxr-xr-x | spec/unit/agent.rb | 407 | ||||
-rwxr-xr-x | spec/unit/agent/locker.rb | 14 | ||||
-rwxr-xr-x | spec/unit/configurer.rb | 225 | ||||
-rwxr-xr-x | spec/unit/configurer/downloader.rb (renamed from spec/unit/agent/downloader.rb) | 24 | ||||
-rwxr-xr-x | spec/unit/configurer/fact_handler.rb (renamed from spec/unit/agent/fact_handler.rb) | 12 | ||||
-rwxr-xr-x | spec/unit/configurer/plugin_handler.rb (renamed from spec/unit/agent/plugin_handler.rb) | 14 | ||||
-rwxr-xr-x | test/agent.rb | 62 |
15 files changed, 733 insertions, 504 deletions
diff --git a/bin/puppetd b/bin/puppetd index ecea2d894..2a71a9a08 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -162,7 +162,8 @@ trap(:INT) do end require 'puppet' -require 'puppet/network/client' +require 'puppet/agent' +require 'puppet/configurer' require 'getoptlong' options = [ @@ -329,13 +330,19 @@ Puppet::SSL::Host.ca_location = :remote Puppet::Transaction::Report.terminus_class = :rest +Puppet::Resource::Catalog.terminus_class = :rest +Puppet::Resource::Catalog.cache_class = :yaml + +Puppet::Node::Facts.terminus_class = :facter +Puppet::Node::Facts.cache_class = :rest + # We need tomake the client either way, we just don't start it # if --no-client is set. -client = Puppet::Network::Client.master.new(args) +agent = Puppet::Agent.new(Puppet::Configurer) if options[:enable] - client.enable + agent.enable elsif options[:disable] - client.disable + agent.disable end if options[:enable] or options[:disable] @@ -347,12 +354,11 @@ server = nil # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. if Puppet[:daemonize] - client.daemonize + agent.daemonize end host = Puppet::SSL::Host.new cert = host.wait_for_cert(options[:waitforcert]) -client.recycle_connection objects = [] @@ -402,7 +408,7 @@ elsif options[:onetime] and Puppet[:listen] end if options[:client] - objects << client + objects << agent end # Set traps for INT and TERM @@ -417,10 +423,10 @@ if options[:onetime] end # Add the service, so the traps work correctly. - Puppet.newservice(client) + Puppet.newservice(agent) begin - client.run + agent.run rescue => detail if Puppet[:trace] puts detail.backtrace @@ -435,7 +441,7 @@ else if options[:client] Puppet.notice "Starting Puppet client version %s" % [Puppet.version] - Puppet.newservice(client) + Puppet.newservice(agent) end Puppet.settraps diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index e2b4ebdc7..c91552b16 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,201 +1,74 @@ -# The client for interacting with the puppetmaster config server. require 'sync' -require 'timeout' -require 'puppet/network/http_pool' -require 'puppet/util' +require 'puppet/daemon' +# A general class for triggering a run of another +# class. class Puppet::Agent - require 'puppet/agent/fact_handler' - require 'puppet/agent/plugin_handler' - require 'puppet/agent/locker' + include Puppet::Daemon - include Puppet::Agent::FactHandler - include Puppet::Agent::PluginHandler + require 'puppet/agent/locker' include Puppet::Agent::Locker - # For benchmarking - include Puppet::Util - - unless defined? @@sync - @@sync = Sync.new - end - - attr_accessor :catalog - attr_reader :compile_time - - class << self - # Puppetd should only have one instance running, and we need a way - # to retrieve it. - attr_accessor :instance - include Puppet::Util - end - - def clear - @catalog.clear(true) if @catalog - @catalog = nil - end + attr_reader :client_class, :client + attr_accessor :stopping - # Initialize and load storage - def dostorage - begin - Puppet::Util::Storage.load - @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Corrupt state file %s: %s" % [Puppet[:statefile], detail] - begin - ::File.unlink(Puppet[:statefile]) - retry - rescue => detail - raise Puppet::Error.new("Cannot remove %s: %s" % - [Puppet[:statefile], detail]) - end - end - end - # Just so we can specify that we are "the" instance. - def initialize - Puppet.settings.use(:main, :ssl, :puppetd) - - self.class.instance = self - @running = false + def initialize(client_class) @splayed = false - end - - # Prepare for catalog retrieval. Downloads everything necessary, etc. - def prepare - dostorage() - - download_plugins() - download_fact_plugins() - - upload_facts() + @client_class = client_class end - # Mark that we should restart. The Puppet module checks whether we're running, - # so this only gets called if we're in the middle of a run. - def restart - # If we're currently running, then just mark for later - Puppet.notice "Received signal to restart; waiting until run is complete" - @restart = true + def lockfile_path + client_class.lockfile_path end - # Should we restart? - def restart? - if defined? @restart - @restart - else - false + # Perform a run with our client. + def run + if client + Puppet.notice "Run of %s already in progress; skipping" % client_class + return end - end - - # Get the remote catalog, yo. Returns nil if no catalog can be found. - def retrieve_catalog - name = Facter.value("hostname") - catalog_class = Puppet::Resource::Catalog - - # First try it with no cache, then with the cache. - result = nil - begin - duration = thinmark do - result = catalog_class.find(name, :use_cache => false) - end - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not retrieve catalog from remote server: %s" % detail + if stopping? + Puppet.notice "In shutdown progress; skipping run" + return end - - unless result + splay + with_client do |client| begin - duration = thinmark do - result = catalog_class.find(name, :use_cache => true) - end + sync.synchronize { lock { client.run } } rescue => detail puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not retrieve catalog from cache: %s" % detail + Puppet.err "Could not run %s: %s" % [client_class, detail] end end - - return nil unless result - - convert_catalog(result, duration) end - # Convert a plain resource catalog into our full host catalog. - def convert_catalog(result, duration) - catalog = result.to_ral - catalog.retrieval_duration = duration - catalog.host_config = true - catalog.write_class_file - return catalog - end - - # The code that actually runs the catalog. - # This just passes any options on to the catalog, - # which accepts :tags and :ignoreschedules. - def run(options = {}) - got_lock = false - splay - Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do - got_lock = lock do - unless catalog = retrieve_catalog - Puppet.err "Could not retrieve catalog; skipping run" - return - end - - begin - benchmark(:notice, "Finished catalog run") do - catalog.apply(options) - end - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Failed to apply catalog: %s" % detail - end - end - - unless got_lock - Puppet.notice "Lock file %s exists; skipping catalog run" % lockfile.lockfile - return + def shutdown + if self.stopping? + Puppet.notice "Already in shutdown" + return + end + self.stopping = true + if client and client.respond_to?(:stop) + begin + client.stop + rescue + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not stop %s: %s" % [client_class, detail] end - - # Now close all of our existing http connections, since there's no - # reason to leave them lying open. - Puppet::Network::HttpPool.clear_http_instances - - lockfile.unlock - - # Did we get HUPped during the run? If so, then restart now that we're - # done with the run. - Process.kill(:HUP, $$) if self.restart? end - end - def running? - lockfile.locked? + super + ensure + self.stopping = false end - private - - def self.timeout - timeout = Puppet[:configtimeout] - case timeout - when String: - if timeout =~ /^\d+$/ - timeout = Integer(timeout) - else - raise ArgumentError, "Configuration timeout must be an integer" - end - when Integer: # nothing - else - raise ArgumentError, "Configuration timeout must be an integer" - end - - return timeout + def stopping? + stopping end + # Have we splayed already? def splayed? @splayed end @@ -210,4 +83,44 @@ class Puppet::Agent 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. + Puppet.newtimer( + :interval => Puppet[:runinterval], + :tolerance => 1, + :start? => true + ) do + run() + end + + # Run once before we start following the timer + run() + end + + def sync + unless defined?(@sync) and @sync + @sync = Sync.new + end + @sync + 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 => details + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not create instance of %s: %s" % [client_class, details] + return + end + yield @client + ensure + @client = nil + end end diff --git a/lib/puppet/agent/locker.rb b/lib/puppet/agent/locker.rb index 03736b278..c24fdad64 100644 --- a/lib/puppet/agent/locker.rb +++ b/lib/puppet/agent/locker.rb @@ -30,9 +30,14 @@ module Puppet::Agent::Locker def lockfile unless defined?(@lockfile) - @lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]) + #@lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]) + @lockfile = Puppet::Util::Pidlock.new(lockfile_path) end @lockfile end + + def running? + lockfile.locked? + end end diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb new file mode 100644 index 000000000..df531f494 --- /dev/null +++ b/lib/puppet/configurer.rb @@ -0,0 +1,185 @@ +# The client for interacting with the puppetmaster config server. +require 'sync' +require 'timeout' +require 'puppet/network/http_pool' +require 'puppet/util' + +class Puppet::Configurer + require 'puppet/configurer/fact_handler' + require 'puppet/configurer/plugin_handler' + + include Puppet::Configurer::FactHandler + include Puppet::Configurer::PluginHandler + + # For benchmarking + include Puppet::Util + + attr_accessor :catalog + attr_reader :compile_time + + # Provide more helpful strings to the logging that the Agent does + def self.to_s + "Puppet configuration client" + end + + class << self + # Puppetd should only have one instance running, and we need a way + # to retrieve it. + attr_accessor :instance + include Puppet::Util + end + + # How to lock instances of this class. + def self.lockfile_path + Puppet[:puppetdlockfile] + end + + def clear + @catalog.clear(true) if @catalog + @catalog = nil + end + + # Initialize and load storage + def dostorage + begin + Puppet::Util::Storage.load + @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Corrupt state file %s: %s" % [Puppet[:statefile], detail] + begin + ::File.unlink(Puppet[:statefile]) + retry + rescue => detail + raise Puppet::Error.new("Cannot remove %s: %s" % + [Puppet[:statefile], detail]) + end + end + end + + # Just so we can specify that we are "the" instance. + def initialize + Puppet.settings.use(:main, :ssl, :puppetd) + + self.class.instance = self + @running = false + @splayed = false + end + + # Prepare for catalog retrieval. Downloads everything necessary, etc. + def prepare + dostorage() + + download_plugins() + + download_fact_plugins() + + upload_facts() + end + + # Mark that we should restart. The Puppet module checks whether we're running, + # so this only gets called if we're in the middle of a run. + def restart + # If we're currently running, then just mark for later + Puppet.notice "Received signal to restart; waiting until run is complete" + @restart = true + end + + # Should we restart? + def restart? + if defined? @restart + @restart + else + false + end + end + + # Get the remote catalog, yo. Returns nil if no catalog can be found. + def retrieve_catalog + name = Facter.value("hostname") + catalog_class = Puppet::Resource::Catalog + + # First try it with no cache, then with the cache. + result = nil + begin + duration = thinmark do + result = catalog_class.find(name, :use_cache => false) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not retrieve catalog from remote server: %s" % detail + end + + unless result + begin + duration = thinmark do + result = catalog_class.find(name, :use_cache => true) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not retrieve catalog from cache: %s" % detail + end + end + + return nil unless result + + convert_catalog(result, duration) + end + + # Convert a plain resource catalog into our full host catalog. + def convert_catalog(result, duration) + catalog = result.to_ral + catalog.retrieval_duration = duration + catalog.host_config = true + catalog.write_class_file + return catalog + end + + # The code that actually runs the catalog. + # This just passes any options on to the catalog, + # which accepts :tags and :ignoreschedules. + def run(options = {}) + unless catalog = retrieve_catalog + Puppet.err "Could not retrieve catalog; skipping run" + return + end + + begin + benchmark(:notice, "Finished catalog run") do + catalog.apply(options) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Failed to apply catalog: %s" % detail + end + + # Now close all of our existing http connections, since there's no + # reason to leave them lying open. + Puppet::Network::HttpPool.clear_http_instances + + # Did we get HUPped during the run? If so, then restart now that we're + # done with the run. + Process.kill(:HUP, $$) if self.restart? + end + + private + + def self.timeout + timeout = Puppet[:configtimeout] + case timeout + when String: + if timeout =~ /^\d+$/ + timeout = Integer(timeout) + else + raise ArgumentError, "Configuration timeout must be an integer" + end + when Integer: # nothing + else + raise ArgumentError, "Configuration timeout must be an integer" + end + + return timeout + end +end diff --git a/lib/puppet/agent/downloader.rb b/lib/puppet/configurer/downloader.rb index edc5931c3..f1c4c03b1 100644 --- a/lib/puppet/agent/downloader.rb +++ b/lib/puppet/configurer/downloader.rb @@ -1,7 +1,7 @@ require 'puppet/agent' require 'puppet/resource/catalog' -class Puppet::Agent::Downloader +class Puppet::Configurer::Downloader attr_reader :name, :path, :source, :ignore # Determine the timeout value to use. diff --git a/lib/puppet/agent/fact_handler.rb b/lib/puppet/configurer/fact_handler.rb index 266ae1815..9435cb22a 100644 --- a/lib/puppet/agent/fact_handler.rb +++ b/lib/puppet/configurer/fact_handler.rb @@ -3,7 +3,7 @@ require 'puppet/indirector/facts/facter' # Break out the code related to facts. This module is # just included into the agent, but having it here makes it # easier to test. -module Puppet::Agent::FactHandler +module Puppet::Configurer::FactHandler def download_fact_plugins? Puppet[:factsync] end @@ -23,7 +23,7 @@ module Puppet::Agent::FactHandler def download_fact_plugins return unless download_fact_plugins? - Puppet::Agent::Downloader.new("fact", Puppet[:factsource], Puppet[:factdest], Puppet[:factsignore]).evaluate + Puppet::Configurer::Downloader.new("fact", Puppet[:factsource], Puppet[:factdest], Puppet[:factsignore]).evaluate end # Clear out all of the loaded facts and reload them from disk. diff --git a/lib/puppet/agent/plugin_handler.rb b/lib/puppet/configurer/plugin_handler.rb index 306b8b6df..cadf300fd 100644 --- a/lib/puppet/agent/plugin_handler.rb +++ b/lib/puppet/configurer/plugin_handler.rb @@ -1,7 +1,7 @@ # Break out the code related to plugins. This module is # just included into the agent, but having it here makes it # easier to test. -module Puppet::Agent::PluginHandler +module Puppet::Configurer::PluginHandler def download_plugins? Puppet[:pluginsync] end @@ -9,7 +9,7 @@ module Puppet::Agent::PluginHandler # Retrieve facts from the central server. def download_plugins return nil unless download_plugins? - Puppet::Agent::Downloader.new("plugin", Puppet[:pluginsource], Puppet[:plugindest], Puppet[:pluginsignore]).evaluate.each { |file| load_plugin(file) } + Puppet::Configurer::Downloader.new("plugin", Puppet[:pluginsource], Puppet[:plugindest], Puppet[:pluginsignore]).evaluate.each { |file| load_plugin(file) } end def load_plugin(file) diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 24d743764..b0576124e 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -76,8 +76,6 @@ module Puppet::Daemon Puppet::Util::Log.destinations.reject { |d| d == :console }.each do |dest| Puppet::Util::Log.close(dest) end - - super end end diff --git a/spec/unit/agent.rb b/spec/unit/agent.rb index c4f5793d2..5622af4a6 100755 --- a/spec/unit/agent.rb +++ b/spec/unit/agent.rb @@ -6,255 +6,200 @@ require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/agent' -describe Puppet::Agent do - it "should include the Plugin Handler module" do - Puppet::Agent.ancestors.should be_include(Puppet::Agent::PluginHandler) +class AgentTestClient + def run + # no-op end - - it "should include the Fact Handler module" do - Puppet::Agent.ancestors.should be_include(Puppet::Agent::FactHandler) - end - - it "should include the Locker module" do - Puppet::Agent.ancestors.should be_include(Puppet::Agent::Locker) + def stop + # no-op end end -describe Puppet::Agent, "when executing a catalog run" do +describe Puppet::Agent do before do - Puppet.settings.stubs(:use).returns(true) - @agent = Puppet::Agent.new - @agent.stubs(:splay) - @agent.stubs(:lock).yields.then.returns true - end + @agent = Puppet::Agent.new(AgentTestClient) - it "should splay" do - Puppet::Util.sync(:puppetrun).stubs(:synchronize) - @agent.expects(:splay) - @agent.run + # So we don't actually try to hit the filesystem. + @agent.stubs(:lock).yields end - it "should use a global mutex to make sure no other thread is executing the catalog" do - sync = mock 'sync' - Puppet::Util.expects(:sync).with(:puppetrun).returns sync - - sync.expects(:synchronize) - - @agent.expects(:retrieve_config).never # i.e., if we don't yield, we don't retrieve the config - @agent.run + it "should set its client class at initialization" do + Puppet::Agent.new("foo").client_class.should == "foo" end - it "should retrieve the catalog if a lock is attained" do - @agent.expects(:lock).yields.then.returns true - - @agent.expects(:retrieve_catalog) - - @agent.run - end - - it "should log and do nothing if the lock cannot be acquired" do - @agent.expects(:lock).returns false - - @agent.expects(:retrieve_catalog).never - - Puppet.expects(:notice) - - @agent.run - end - - it "should retrieve the catalog" do - @agent.expects(:retrieve_catalog) - - @agent.run + it "should include the Locker module" do + Puppet::Agent.ancestors.should be_include(Puppet::Agent::Locker) end - it "should log a failure and do nothing if no catalog can be retrieved" do - @agent.expects(:retrieve_catalog).returns nil + 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 - Puppet.expects(:err) + client.expects(:run) @agent.run end - it "should apply the catalog with all options to :run" do - catalog = stub 'catalog', :retrieval_duration= => nil - @agent.expects(:retrieve_catalog).returns catalog - - catalog.expects(:apply).with(:one => true) - @agent.run :one => true + 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 + + describe "when running" do + it "should splay" do + @agent.expects(:splay) + + @agent.run + end + + it "should do nothing if a client instance exists" do + @agent.expects(:client).returns "eh" + AgentTestClient.expects(:new).never + @agent.run + end + + it "should do nothing if it is in the process of stopping" do + @agent.expects(:stopping?).returns true + AgentTestClient.expects(:new).never + @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 + 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 - it "should benchmark how long it takes to apply the catalog" do - @agent.expects(:benchmark).with(:notice, "Finished catalog run") - - catalog = stub 'catalog', :retrieval_duration= => nil - @agent.expects(:retrieve_catalog).returns catalog - - catalog.expects(:apply).never # because we're not yielding - @agent.run - end - - it "should HUP itself if it should be restarted" do - catalog = stub 'catalog', :retrieval_duration= => nil, :apply => nil - @agent.expects(:retrieve_catalog).returns catalog - - Process.expects(:kill).with(:HUP, $$) - - @agent.expects(:restart?).returns true - - @agent.run - end - - it "should not HUP itself if it should not be restarted" do - catalog = stub 'catalog', :retrieval_duration= => nil, :apply => nil - @agent.expects(:retrieve_catalog).returns catalog - - Process.expects(:kill).never - - @agent.expects(:restart?).returns false - - @agent.run - end -end - -describe Puppet::Agent, "when retrieving a catalog" do - before do - Puppet.settings.stubs(:use).returns(true) - @agent = Puppet::Agent.new - - @catalog = Puppet::Resource::Catalog.new - - @agent.stubs(:convert_catalog).returns @catalog - end - - it "should use the Catalog class to get its catalog" do - Puppet::Resource::Catalog.expects(:find).returns @catalog - - @agent.retrieve_catalog - end - - it "should use its Facter name to retrieve the catalog" do - Facter.stubs(:value).returns "eh" - Facter.expects(:value).with("hostname").returns "myhost" - Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost" }.returns @catalog - - @agent.retrieve_catalog - end - - it "should default to returning a catalog retrieved directly from the server, skipping the cache" do - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns @catalog - - @agent.retrieve_catalog.should == @catalog - end - - it "should return the cached catalog when no catalog can be retrieved from the server" do - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns nil - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns @catalog - - @agent.retrieve_catalog.should == @catalog - end - - it "should not look in the cache for a catalog if one is returned from the server" do - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns @catalog - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.never - - @agent.retrieve_catalog.should == @catalog - end - - it "should return the cached catalog when retrieving the remote catalog throws an exception" do - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.raises "eh" - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns @catalog - - @agent.retrieve_catalog.should == @catalog - end - - it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns nil - Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns nil - - @agent.retrieve_catalog.should be_nil - end - - it "should convert the catalog before returning" do - Puppet::Resource::Catalog.stubs(:find).returns @catalog - - @agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog" - @agent.retrieve_catalog.should == "converted catalog" - end - - it "should return nil if there is an error while retrieving the catalog" do - Puppet::Resource::Catalog.expects(:find).raises "eh" - - @agent.retrieve_catalog.should be_nil - end -end - -describe Puppet::Agent, "when converting the catalog" do - before do - Puppet.settings.stubs(:use).returns(true) - @agent = Puppet::Agent.new - - @catalog = Puppet::Resource::Catalog.new - @oldcatalog = stub 'old_catalog', :to_ral => @catalog - end - - it "should convert the catalog to a RAL-formed catalog" do - @oldcatalog.expects(:to_ral).returns @catalog - - @agent.convert_catalog(@oldcatalog, 10).should equal(@catalog) - end - - it "should record the passed retrieval time with the RAL catalog" do - @catalog.expects(:retrieval_duration=).with 10 - - @agent.convert_catalog(@oldcatalog, 10) - end - - it "should write the RAL catalog's class file" do - @catalog.expects(:write_class_file) - - @agent.convert_catalog(@oldcatalog, 10) - end - - it "should mark the RAL catalog as a host catalog" do - @catalog.expects(:host_config=).with true - - @agent.convert_catalog(@oldcatalog, 10) - end -end - -describe Puppet::Agent, "when preparing for a run" do - before do - Puppet.settings.stubs(:use).returns(true) - @agent = Puppet::Agent.new - @agent.stubs(:dostorage) - @agent.stubs(:upload_facts) - @facts = {"one" => "two", "three" => "four"} - end - - it "should initialize the metadata store" do - @agent.class.stubs(:facts).returns(@facts) - @agent.expects(:dostorage) - @agent.prepare - end - - it "should download fact plugins" do - @agent.stubs(:dostorage) - @agent.expects(:download_fact_plugins) - - @agent.prepare - end - - it "should download plugins" do - @agent.stubs(:dostorage) - @agent.expects(:download_plugins) - - @agent.prepare - end - - it "should upload facts to use for catalog retrieval" do - @agent.stubs(:dostorage) - @agent.expects(:upload_facts) - @agent.prepare + describe "when shutting down" do + it "should do nothing if already stopping" do + @agent.expects(:stopping?).returns true + @agent.shutdown + end + + it "should stop the client if one is available and it responds to 'stop'" do + client = AgentTestClient.new + + @agent.stubs(:client).returns client + client.expects(:stop) + @agent.shutdown + end + + it "should remove its pid file" do + @agent.expects(:rmpidfile) + @agent.shutdown + end + + it "should mark itself as stopping while waiting for the client to stop" do + client = AgentTestClient.new + + @agent.stubs(:client).returns client + client.expects(:stop).with { @agent.should be_stopping; true } + + @agent.shutdown + end + end + + describe "when starting" do + 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 + Puppet.expects(:newtimer).with(:interval => 5, :start? => true, :tolerance => 1) + @agent.stubs(:run) + @agent.start + end + + it "should run once immediately" do + Puppet.stubs(:newtimer) + @agent.expects(:run) + @agent.start + end + + it "should run within the block passed to the timer" do + Puppet.stubs(:newtimer).yields + @agent.expects(:run).times(2) + @agent.start + end + + it "should run within the block passed to the timer" do + Puppet.stubs(:newtimer).yields + @agent.expects(:run).times(2) + @agent.start + end end end diff --git a/spec/unit/agent/locker.rb b/spec/unit/agent/locker.rb index aae7c0c7b..1477c824e 100755 --- a/spec/unit/agent/locker.rb +++ b/spec/unit/agent/locker.rb @@ -11,12 +11,21 @@ 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 @@ -83,4 +92,9 @@ describe Puppet::Agent::Locker do 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 --git a/spec/unit/configurer.rb b/spec/unit/configurer.rb new file mode 100755 index 000000000..996d67282 --- /dev/null +++ b/spec/unit/configurer.rb @@ -0,0 +1,225 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-11-12. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' +require 'puppet/configurer' + +describe Puppet::Configurer do + it "should include the Plugin Handler module" do + Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler) + end + + it "should include the Fact Handler module" do + Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler) + end + + it "should use the puppetdlockfile as its lockfile path" do + Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock") + Puppet::Configurer.lockfile_path.should == "/my/lock" + end +end + +describe Puppet::Configurer, "when executing a catalog run" do + before do + Puppet.settings.stubs(:use).returns(true) + @agent = Puppet::Configurer.new + end + + it "should retrieve the catalog" do + @agent.expects(:retrieve_catalog) + + @agent.run + end + + it "should log a failure and do nothing if no catalog can be retrieved" do + @agent.expects(:retrieve_catalog).returns nil + + Puppet.expects(:err) + + @agent.run + end + + it "should apply the catalog with all options to :run" do + catalog = stub 'catalog', :retrieval_duration= => nil + @agent.expects(:retrieve_catalog).returns catalog + + catalog.expects(:apply).with(:one => true) + @agent.run :one => true + end + + it "should benchmark how long it takes to apply the catalog" do + @agent.expects(:benchmark).with(:notice, "Finished catalog run") + + catalog = stub 'catalog', :retrieval_duration= => nil + @agent.expects(:retrieve_catalog).returns catalog + + catalog.expects(:apply).never # because we're not yielding + @agent.run + end + + it "should HUP itself if it should be restarted" do + catalog = stub 'catalog', :retrieval_duration= => nil, :apply => nil + @agent.expects(:retrieve_catalog).returns catalog + + Process.expects(:kill).with(:HUP, $$) + + @agent.expects(:restart?).returns true + + @agent.run + end + + it "should not HUP itself if it should not be restarted" do + catalog = stub 'catalog', :retrieval_duration= => nil, :apply => nil + @agent.expects(:retrieve_catalog).returns catalog + + Process.expects(:kill).never + + @agent.expects(:restart?).returns false + + @agent.run + end +end + +describe Puppet::Configurer, "when retrieving a catalog" do + before do + Puppet.settings.stubs(:use).returns(true) + @agent = Puppet::Configurer.new + + @catalog = Puppet::Resource::Catalog.new + + @agent.stubs(:convert_catalog).returns @catalog + end + + it "should use the Catalog class to get its catalog" do + Puppet::Resource::Catalog.expects(:find).returns @catalog + + @agent.retrieve_catalog + end + + it "should use its Facter name to retrieve the catalog" do + Facter.stubs(:value).returns "eh" + Facter.expects(:value).with("hostname").returns "myhost" + Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost" }.returns @catalog + + @agent.retrieve_catalog + end + + it "should default to returning a catalog retrieved directly from the server, skipping the cache" do + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns @catalog + + @agent.retrieve_catalog.should == @catalog + end + + it "should return the cached catalog when no catalog can be retrieved from the server" do + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns nil + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns @catalog + + @agent.retrieve_catalog.should == @catalog + end + + it "should not look in the cache for a catalog if one is returned from the server" do + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns @catalog + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.never + + @agent.retrieve_catalog.should == @catalog + end + + it "should return the cached catalog when retrieving the remote catalog throws an exception" do + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.raises "eh" + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns @catalog + + @agent.retrieve_catalog.should == @catalog + end + + it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == false }.returns nil + Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:use_cache] == true }.returns nil + + @agent.retrieve_catalog.should be_nil + end + + it "should convert the catalog before returning" do + Puppet::Resource::Catalog.stubs(:find).returns @catalog + + @agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog" + @agent.retrieve_catalog.should == "converted catalog" + end + + it "should return nil if there is an error while retrieving the catalog" do + Puppet::Resource::Catalog.expects(:find).raises "eh" + + @agent.retrieve_catalog.should be_nil + end +end + +describe Puppet::Configurer, "when converting the catalog" do + before do + Puppet.settings.stubs(:use).returns(true) + @agent = Puppet::Configurer.new + + @catalog = Puppet::Resource::Catalog.new + @oldcatalog = stub 'old_catalog', :to_ral => @catalog + end + + it "should convert the catalog to a RAL-formed catalog" do + @oldcatalog.expects(:to_ral).returns @catalog + + @agent.convert_catalog(@oldcatalog, 10).should equal(@catalog) + end + + it "should record the passed retrieval time with the RAL catalog" do + @catalog.expects(:retrieval_duration=).with 10 + + @agent.convert_catalog(@oldcatalog, 10) + end + + it "should write the RAL catalog's class file" do + @catalog.expects(:write_class_file) + + @agent.convert_catalog(@oldcatalog, 10) + end + + it "should mark the RAL catalog as a host catalog" do + @catalog.expects(:host_config=).with true + + @agent.convert_catalog(@oldcatalog, 10) + end +end + +describe Puppet::Configurer, "when preparing for a run" do + before do + Puppet.settings.stubs(:use).returns(true) + @agent = Puppet::Configurer.new + @agent.stubs(:dostorage) + @agent.stubs(:upload_facts) + @facts = {"one" => "two", "three" => "four"} + end + + it "should initialize the metadata store" do + @agent.class.stubs(:facts).returns(@facts) + @agent.expects(:dostorage) + @agent.prepare + end + + it "should download fact plugins" do + @agent.stubs(:dostorage) + @agent.expects(:download_fact_plugins) + + @agent.prepare + end + + it "should download plugins" do + @agent.stubs(:dostorage) + @agent.expects(:download_plugins) + + @agent.prepare + end + + it "should upload facts to use for catalog retrieval" do + @agent.stubs(:dostorage) + @agent.expects(:upload_facts) + @agent.prepare + end +end diff --git a/spec/unit/agent/downloader.rb b/spec/unit/configurer/downloader.rb index 28c5d030f..efd280d93 100755 --- a/spec/unit/agent/downloader.rb +++ b/spec/unit/configurer/downloader.rb @@ -2,36 +2,36 @@ require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/agent/downloader' +require 'puppet/configurer/downloader' -describe Puppet::Agent::Downloader do +describe Puppet::Configurer::Downloader do it "should require a name" do - lambda { Puppet::Agent::Downloader.new }.should raise_error(ArgumentError) + lambda { Puppet::Configurer::Downloader.new }.should raise_error(ArgumentError) end it "should require a path and a source at initialization" do - lambda { Puppet::Agent::Downloader.new("name") }.should raise_error(ArgumentError) + lambda { Puppet::Configurer::Downloader.new("name") }.should raise_error(ArgumentError) end it "should set the name, path and source appropriately" do - dler = Puppet::Agent::Downloader.new("facts", "path", "source") + dler = Puppet::Configurer::Downloader.new("facts", "path", "source") dler.name.should == "facts" dler.path.should == "path" dler.source.should == "source" end it "should be able to provide a timeout value" do - Puppet::Agent::Downloader.should respond_to(:timeout) + Puppet::Configurer::Downloader.should respond_to(:timeout) end it "should use the configtimeout, converted to an integer, as its timeout" do Puppet.settings.expects(:value).with(:configtimeout).returns "50" - Puppet::Agent::Downloader.timeout.should == 50 + Puppet::Configurer::Downloader.timeout.should == 50 end describe "when creating the file that does the downloading" do before do - @dler = Puppet::Agent::Downloader.new("foo", "path", "source") + @dler = Puppet::Configurer::Downloader.new("foo", "path", "source") end it "should create a file instance with the right path and source" do @@ -83,14 +83,14 @@ describe Puppet::Agent::Downloader do it "should support providing an 'ignore' parameter" do Puppet::Type.type(:file).expects(:create).with { |opts| opts[:ignore] == ".svn" } - @dler = Puppet::Agent::Downloader.new("foo", "path", "source", ".svn") + @dler = Puppet::Configurer::Downloader.new("foo", "path", "source", ".svn") @dler.file end end describe "when creating the catalog to do the downloading" do before do - @dler = Puppet::Agent::Downloader.new("foo", "path", "source") + @dler = Puppet::Configurer::Downloader.new("foo", "path", "source") end it "should create a catalog and add the file to it" do @@ -108,7 +108,7 @@ describe Puppet::Agent::Downloader do describe "when downloading" do before do - @dler = Puppet::Agent::Downloader.new("foo", "path", "source") + @dler = Puppet::Configurer::Downloader.new("foo", "path", "source") end it "should log that it is downloading" do @@ -119,7 +119,7 @@ describe Puppet::Agent::Downloader do end it "should set a timeout for the download" do - Puppet::Agent::Downloader.expects(:timeout).returns 50 + Puppet::Configurer::Downloader.expects(:timeout).returns 50 Timeout.expects(:timeout).with(50) @dler.evaluate diff --git a/spec/unit/agent/fact_handler.rb b/spec/unit/configurer/fact_handler.rb index 9622b0649..ed9446c93 100755 --- a/spec/unit/agent/fact_handler.rb +++ b/spec/unit/configurer/fact_handler.rb @@ -1,14 +1,14 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/agent' -require 'puppet/agent/fact_handler' +require 'puppet/configurer' +require 'puppet/configurer/fact_handler' class FactHandlerTester - include Puppet::Agent::FactHandler + include Puppet::Configurer::FactHandler end -describe Puppet::Agent::FactHandler do +describe Puppet::Configurer::FactHandler do before do @facthandler = FactHandlerTester.new end @@ -32,7 +32,7 @@ describe Puppet::Agent::FactHandler do end it "should not download fact plugins when downloading is disabled" do - Puppet::Agent::Downloader.expects(:new).never + Puppet::Configurer::Downloader.expects(:new).never @facthandler.expects(:download_fact_plugins?).returns false @facthandler.download_fact_plugins end @@ -44,7 +44,7 @@ describe Puppet::Agent::FactHandler do Puppet.settings.expects(:value).with(:factdest).returns "fdest" Puppet.settings.expects(:value).with(:factsignore).returns "fignore" - Puppet::Agent::Downloader.expects(:new).with("fact", "fsource", "fdest", "fignore").returns downloader + Puppet::Configurer::Downloader.expects(:new).with("fact", "fsource", "fdest", "fignore").returns downloader downloader.expects(:evaluate) diff --git a/spec/unit/agent/plugin_handler.rb b/spec/unit/configurer/plugin_handler.rb index 44603bc6c..23d9c11d3 100755 --- a/spec/unit/agent/plugin_handler.rb +++ b/spec/unit/configurer/plugin_handler.rb @@ -1,14 +1,14 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/agent' -require 'puppet/agent/plugin_handler' +require 'puppet/configurer' +require 'puppet/configurer/plugin_handler' class PluginHandlerTester - include Puppet::Agent::PluginHandler + include Puppet::Configurer::PluginHandler end -describe Puppet::Agent::PluginHandler do +describe Puppet::Configurer::PluginHandler do before do @pluginhandler = PluginHandlerTester.new end @@ -32,7 +32,7 @@ describe Puppet::Agent::PluginHandler do end it "should not download plugins when downloading is disabled" do - Puppet::Agent::Downloader.expects(:new).never + Puppet::Configurer::Downloader.expects(:new).never @pluginhandler.expects(:download_plugins?).returns false @pluginhandler.download_plugins end @@ -44,7 +44,7 @@ describe Puppet::Agent::PluginHandler do Puppet.settings.expects(:value).with(:plugindest).returns "pdest" Puppet.settings.expects(:value).with(:pluginsignore).returns "pignore" - Puppet::Agent::Downloader.expects(:new).with("plugin", "psource", "pdest", "pignore").returns downloader + Puppet::Configurer::Downloader.expects(:new).with("plugin", "psource", "pdest", "pignore").returns downloader downloader.expects(:evaluate).returns [] @@ -59,7 +59,7 @@ describe Puppet::Agent::PluginHandler do it "should load each downloaded file" do downloader = mock 'downloader' - Puppet::Agent::Downloader.expects(:new).returns downloader + Puppet::Configurer::Downloader.expects(:new).returns downloader downloader.expects(:evaluate).returns %w{one two} diff --git a/test/agent.rb b/test/agent.rb deleted file mode 100755 index 3ead157be..000000000 --- a/test/agent.rb +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/lib/puppettest' - -require 'puppettest' -require 'puppet/agent' -require 'mocha' - -class TestAgent < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - super - @agent_class = Puppet::Agent - end - - # Make sure we get a value for timeout - def test_config_timeout - master = Puppet::Agent - time = Integer(Puppet[:configtimeout]) - assert_equal(time, master.timeout, "Did not get default value for timeout") - assert_equal(time, master.timeout, "Did not get default value for timeout on second run") - - # Reset it - Puppet[:configtimeout] = "50" - assert_equal(50, master.timeout, "Did not get changed default value for timeout") - assert_equal(50, master.timeout, "Did not get changed default value for timeout on second run") - - # Now try an integer - Puppet[:configtimeout] = 100 - assert_equal(100, master.timeout, "Did not get changed integer default value for timeout") - assert_equal(100, master.timeout, "Did not get changed integer default value for timeout on second run") - end - - def test_splay - client = Puppet::Agent.new - - # Make sure we default to no splay - client.expects(:sleep).never - - assert_nothing_raised("Failed to call splay") do - client.send(:splay) - end - - # Now set it to true and make sure we get the right value - client = Puppet::Agent.new - client.expects(:sleep) - - Puppet[:splay] = true - assert_nothing_raised("Failed to call sleep when splay is true") do - client.send(:splay) - end - - # Now try it again - client = Puppet::Agent.new - client.expects(:sleep) - - assert_nothing_raised("Failed to call sleep when splay is true with a cached value") do - client.send(:splay) - end - end -end |