diff options
author | Luke Kanies <luke@madstop.com> | 2009-01-28 17:11:19 -0600 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2009-02-06 18:08:42 -0600 |
commit | fc14b81f99adc9c9308a26d322adaa59a7b7716d (patch) | |
tree | dc731383d3195e37ea3658b9cbc7b0c428579d9f | |
parent | e8be6dcad2150769b51bf81e95c57491921e68c1 (diff) | |
download | puppet-fc14b81f99adc9c9308a26d322adaa59a7b7716d.tar.gz |
Splitting the Agent class into Agent and Configurer
Once I went to add runinterval support to the Agent class,
I realized it's really two classes: One that handles starting,
stopping, running, et al (still called Agent), and one that
handles downloading the catalog, running it, etc. (now
called Configurer).
This commit includes some additional code, but 95% of it is just moving code around.
Signed-off-by: Luke Kanies <luke@madstop.com>
-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 |