diff options
| author | Luke Kanies <luke@madstop.com> | 2009-02-02 17:19:07 -0600 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2009-02-06 18:08:43 -0600 |
| commit | c0fcb2137e66af8ba60a959faa221034c6832b69 (patch) | |
| tree | 10f62605333979ea64445dca5d4d66d58237de97 | |
| parent | 700e823f7c33eb3c5b4d9e467742fd24f63bbeef (diff) | |
Creating and using a new Puppet::Daemon class
This replaces the short-lived EventManager class, all of
the service- and timer-related code in puppet.rb, and moves
code from agent.rb, server.rb, and other places into one
class responsible for starting, stopping, pids, and more.
The Daemon module is no longer in existence, so it's been
removed from the classes that were using it.
Signed-off-by: Luke Kanies <luke@madstop.com>
| -rwxr-xr-x | bin/puppetd | 58 | ||||
| -rwxr-xr-x | bin/puppetmasterd | 19 | ||||
| -rw-r--r-- | lib/puppet.rb | 204 | ||||
| -rw-r--r-- | lib/puppet/agent.rb | 39 | ||||
| -rw-r--r-- | lib/puppet/agent/locker.rb | 1 | ||||
| -rwxr-xr-x | lib/puppet/daemon.rb | 102 | ||||
| -rw-r--r-- | lib/puppet/event_manager.rb | 182 | ||||
| -rw-r--r-- | lib/puppet/network/http_server/mongrel.rb | 2 | ||||
| -rw-r--r-- | lib/puppet/network/http_server/webrick.rb | 2 | ||||
| -rw-r--r-- | lib/puppet/util/settings.rb | 19 | ||||
| -rwxr-xr-x | spec/unit/agent.rb | 77 | ||||
| -rwxr-xr-x | spec/unit/daemon.rb | 287 | ||||
| -rwxr-xr-x | spec/unit/event_manager.rb | 266 | ||||
| -rwxr-xr-x | spec/unit/network/client.rb | 4 | ||||
| -rwxr-xr-x | spec/unit/util/settings.rb | 42 | ||||
| -rwxr-xr-x | test/network/daemon.rb | 70 |
16 files changed, 520 insertions, 854 deletions
diff --git a/bin/puppetd b/bin/puppetd index 2a71a9a08..4c7f1d131 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -163,9 +163,14 @@ end require 'puppet' require 'puppet/agent' +require 'puppet/daemon' require 'puppet/configurer' require 'getoptlong' +# Create this now, so it has access to ARGV +daemon = Puppet::Daemon.new +daemon.argv = ARGV.dup + options = [ [ "--centrallogging", GetoptLong::NO_ARGUMENT ], [ "--disable", GetoptLong::NO_ARGUMENT ], @@ -214,7 +219,7 @@ begin options[:disable] = true when "--serve" if Puppet::Network::Handler.handler(arg) - options[:serve][arg.to_sym] = {} + options[:serve] << arg.to_sym else raise "Could not find handler for %s" % arg end @@ -349,12 +354,14 @@ if options[:enable] or options[:disable] exit(0) end +daemon.agent = agent + server = nil # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. if Puppet[:daemonize] - agent.daemonize + daemon.daemonize end host = Puppet::SSL::Host.new @@ -380,40 +387,20 @@ if Puppet[:listen] and ! options[:onetime] handlers = nil if options[:serve].empty? - handlers = {:Runner => {}} + handlers = [:Runner] else handlers = options[:serve] end - handlers.each do |name, hash| - Puppet.info "Starting handler for %s" % name - end - - args[:Handlers] = handlers - args[:Port] = Puppet[:puppetport] - - require 'puppet/network/http_server/webrick' - - begin - server = Puppet::Network::HTTPServer::WEBrick.new(args) - rescue => detail - $stderr.puts detail - puts detail.backtrace - exit(1) - end + require 'puppet/network/server' + # No REST handlers yet. + server = Puppet::Network::Server.new(:handlers => [:facts], :xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) - objects << server + daemon.server = server elsif options[:onetime] and Puppet[:listen] Puppet.notice "Ignoring --listen on onetime run" end -if options[:client] - objects << agent -end - -# Set traps for INT and TERM -Puppet.settraps - # If --onetime is specified, we don't run 'start', which means we don't # create a pidfile. if options[:onetime] @@ -422,8 +409,7 @@ if options[:onetime] exit(43) end - # Add the service, so the traps work correctly. - Puppet.newservice(agent) + daemon.set_signal_traps begin agent.run @@ -435,17 +421,7 @@ if options[:onetime] end exit(0) else - if server - Puppet.newservice(server) - end + Puppet.notice "Starting Puppet client version %s" % [Puppet.version] - if options[:client] - Puppet.notice "Starting Puppet client version %s" % [Puppet.version] - Puppet.newservice(agent) - end - - Puppet.settraps - - Puppet.start + daemon.start end - diff --git a/bin/puppetmasterd b/bin/puppetmasterd index 74ec821d4..3abdb77ea 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -70,8 +70,13 @@ end require 'getoptlong' require 'puppet' +require 'puppet/daemon' require 'puppet/network/server' +# Create this first-off, so we have ARGV +daemon = Puppet::Daemon.new +daemon.argv = ARGV.dup + options = [ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], @@ -197,7 +202,7 @@ if Puppet[:ca] xmlrpc_handlers << :CA end -server = Puppet::Network::Server.new(:handlers => rest_handlers, :xmlrpc_handlers => xmlrpc_handlers) +daemon.server = Puppet::Network::Server.new(:handlers => rest_handlers, :xmlrpc_handlers => xmlrpc_handlers) # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost @@ -218,16 +223,8 @@ if Process.uid == 0 end end -# Tell Puppet to manage this service for us, which has it starting and stopping -# as appropriate. -Puppet.newservice(server) - -Puppet.settraps - -if Puppet[:daemonize] - server.daemonize -end +daemon.daemonize if Puppet[:daemonize] Puppet.notice "Starting Puppet server version %s" % [Puppet.version] -Puppet.start +daemon.start diff --git a/lib/puppet.rb b/lib/puppet.rb index dad8936d2..c2d108d7f 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -8,7 +8,6 @@ end require 'singleton' require 'facter' require 'puppet/error' -require 'puppet/external/event-loop' require 'puppet/util' require 'puppet/util/log' require 'puppet/util/autoload' @@ -32,14 +31,7 @@ module Puppet end class << self - # So we can monitor signals and such. - include SignalObserver - include Puppet::Util - - # To keep a copy of arguments. Set within Config#addargs, because I'm - # lazy. - attr_accessor :args attr_reader :features attr_writer :name end @@ -117,7 +109,6 @@ module Puppet # Load all of the configuration parameters. require 'puppet/defaults' - def self.genmanifest if Puppet[:genmanifest] puts Puppet.settings.to_manifest @@ -125,42 +116,6 @@ module Puppet end end - # Run all threads to their ends - def self.join - defined? @threads and @threads.each do |t| t.join end - end - - # Create a new service that we're supposed to run - def self.newservice(service) - @services ||= [] - - @services << service - end - - def self.newthread(&block) - @threads ||= [] - - @threads << Thread.new do - yield - end - end - - def self.newtimer(hash, &block) - timer = nil - threadlock(:timers) do - @timers ||= [] - timer = EventLoop::Timer.new(hash) - @timers << timer - - if block_given? - observe_signal(timer, :alarm, &block) - end - end - - # In case they need it for something else. - timer - end - # Parse the config file for this process. def self.parse_config if Puppet[:config] and File.exists? Puppet[:config] @@ -169,165 +124,6 @@ module Puppet end end - # Relaunch the executable. - def self.restart - command = $0 + " " + self.args.join(" ") - Puppet.notice "Restarting with '%s'" % command - Puppet.shutdown(false) - Puppet::Util::Log.reopen - exec(command) - end - - # Trap a couple of the main signals. This should probably be handled - # in a way that anyone else can register callbacks for traps, but, eh. - def self.settraps - [:INT, :TERM].each do |signal| - trap(signal) do - Puppet.notice "Caught #{signal}; shutting down" - Puppet.debug "Signal caught here:" - caller.each { |l| Puppet.debug l } - Puppet.shutdown - end - end - - # Handle restarting. - trap(:HUP) do - if client = @services.find { |s| s.is_a? Puppet::Network::Client.master } and client.running? - client.restart - else - Puppet.restart - end - end - - # Provide a hook for running clients where appropriate - trap(:USR1) do - done = 0 - Puppet.notice "Caught USR1; triggering client run" - @services.find_all { |s| s.is_a? Puppet::Network::Client }.each do |client| - if client.respond_to? :running? - if client.running? - Puppet.info "Ignoring running %s" % client.class - else - done += 1 - begin - client.runnow - rescue => detail - Puppet.err "Could not run client: %s" % detail - end - end - else - Puppet.info "Ignoring %s; cannot test whether it is running" % - client.class - end - end - - unless done > 0 - Puppet.notice "No clients were run" - end - end - - trap(:USR2) do - Puppet::Util::Log.reopen - end - end - - # Shutdown our server process, meaning stop all services and all threads. - # Optionally, exit. - def self.shutdown(leave = true) - Puppet.notice "Shutting down" - # Unmonitor our timers - defined? @timers and @timers.each do |timer| - EventLoop.current.ignore_timer timer - end - - # This seems to exit the process, although I can't find where it does - # so. Leaving it out doesn't seem to hurt anything. - #if EventLoop.current.running? - # EventLoop.current.quit - #end - - # Stop our services - defined? @services and @services.each do |svc| - next unless svc.respond_to?(:shutdown) - begin - timeout(20) do - svc.shutdown - end - rescue TimeoutError - Puppet.err "%s could not shut down within 20 seconds" % svc.class - end - end - - # And wait for them all to die, giving a decent amount of time - defined? @threads and @threads.each do |thr| - begin - timeout(20) do - thr.join - end - rescue TimeoutError - # Just ignore this, since we can't intelligently provide a warning - end - end - - if leave - exit(0) - end - end - - # Start all of our services and optionally our event loop, which blocks, - # waiting for someone, somewhere, to generate events of some kind. - def self.start(block = true) - # Starting everything in its own thread, fwiw - defined? @services and @services.dup.each do |svc| - newthread do - begin - svc.start - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - @services.delete svc - Puppet.err "Could not start %s: %s" % [svc.class, detail] - end - end - end - - # We need to give the services a chance to register their timers before - # we try to start monitoring them. - sleep 0.5 - - unless @services.length > 0 - Puppet.notice "No remaining services; exiting" - exit(1) - end - - if defined? @timers and ! @timers.empty? - @timers.each do |timer| - EventLoop.current.monitor_timer timer - end - end - - if block - EventLoop.current.run - end - end - - # Create the timer that our different objects (uh, mostly the client) - # check. - def self.timer - unless defined? @timer - #Puppet.info "Interval is %s" % Puppet[:runinterval] - #@timer = EventLoop::Timer.new(:interval => Puppet[:runinterval]) - @timer = EventLoop::Timer.new( - :interval => Puppet[:runinterval], - :tolerance => 1, - :start? => true - ) - EventLoop.current.monitor_timer @timer - end - @timer - end - # XXX this should all be done using puppet objects, not using # normal mkdir def self.recmkdir(dir,mode = 0755) diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index c91552b16..e7d40f68c 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,17 +1,19 @@ require 'sync' -require 'puppet/daemon' +require 'puppet/external/event-loop' # A general class for triggering a run of another # class. class Puppet::Agent - include Puppet::Daemon - require 'puppet/agent/locker' include Puppet::Agent::Locker - attr_reader :client_class, :client + attr_reader :client_class, :client, :needing_restart, :splayed attr_accessor :stopping + def configure_delayed_restart + @needing_restart = true + end + # Just so we can specify that we are "the" instance. def initialize(client_class) @splayed = false @@ -23,6 +25,16 @@ class Puppet::Agent client_class.lockfile_path end + def needing_restart? + @needing_restart + end + + def restart + configure_delayed_restart and return if running? + Process.kill(:HUP, $$) + @needing_restart = false + end + # Perform a run with our client. def run if client @@ -44,7 +56,12 @@ class Puppet::Agent end end - def shutdown + # If the client instance is set, we're mid-run. + def running? + ! client.nil? + end + + def stop if self.stopping? Puppet.notice "Already in shutdown" return @@ -58,8 +75,6 @@ class Puppet::Agent Puppet.err "Could not stop %s: %s" % [client_class, detail] end end - - super ensure self.stopping = false end @@ -70,7 +85,7 @@ class Puppet::Agent # Have we splayed already? def splayed? - @splayed + splayed end # Sleep when splay is enabled; else just return. @@ -88,16 +103,12 @@ class Puppet::Agent # 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 + timer = EventLoop::Timer.new(:interval => Puppet[:runinterval], :tolerance => 1, :start? => true) do run() end # Run once before we start following the timer - run() + timer.sound_alarm end def sync diff --git a/lib/puppet/agent/locker.rb b/lib/puppet/agent/locker.rb index c24fdad64..eaf19177a 100644 --- a/lib/puppet/agent/locker.rb +++ b/lib/puppet/agent/locker.rb @@ -30,7 +30,6 @@ module Puppet::Agent::Locker def lockfile unless defined?(@lockfile) - #@lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]) @lockfile = Puppet::Util::Pidlock.new(lockfile_path) end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index b0576124e..5f811d897 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,10 +1,11 @@ require 'puppet' require 'puppet/util/pidlock' +require 'puppet/external/event-loop' # A module that handles operations common to all daemons. This is included # into the Server and Client base classes. -module Puppet::Daemon - include Puppet::Util +class Puppet::Daemon + attr_accessor :agent, :server, :argv def daemonname Puppet[:name] @@ -17,7 +18,7 @@ module Puppet::Daemon exit(0) end - setpidfile() + create_pidfile() # Get rid of console logging Puppet::Util::Log.close(:console) @@ -38,18 +39,42 @@ module Puppet::Daemon end end - # The path to the pid file for this server + # Create a pidfile for our daemon, so we can be stopped and others + # don't try to start. + def create_pidfile + Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do + unless Puppet::Util::Pidlock.new(pidfile).lock + raise "Could not create PID file: %s" % [pidfile] + end + end + end + + # Provide the path to our pidfile. def pidfile - if Puppet[:pidfile] != "" - Puppet[:pidfile] - else - File.join(Puppet[:rundir], daemonname() + ".pid") + Puppet[:pidfile] + end + + def reexec + raise Puppet::DevError, "Cannot reexec unless ARGV arguments are set" unless argv + command = $0 + " " + argv.join(" ") + Puppet.notice "Restarting with '%s'" % command + stop(:exit => false) + exec(command) + end + + def reload + return unless agent + if agent.running? + Puppet.notice "Not triggering already-running agent" + return end + + agent.run end - # Remove the pid file - def rmpidfile - threadlock(:pidfile) do + # Remove the pid file for our daemon. + def remove_pidfile + Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do locker = Puppet::Util::Pidlock.new(pidfile) if locker.locked? locker.unlock or Puppet.err "Could not remove PID file %s" % [pidfile] @@ -57,25 +82,52 @@ module Puppet::Daemon end end - # Create the pid file. - def setpidfile - threadlock(:pidfile) do - unless Puppet::Util::Pidlock.new(pidfile).lock - Puppet.err("Could not create PID file: %s" % [pidfile]) - exit(74) - end + def restart + if agent and agent.running? + agent.configure_delayed_restart + else + reexec end end - # Shut down our server - def shutdown - # Remove our pid file - rmpidfile() + def reopen_logs + Puppet::Util::Log.reopen + end - # And close all logs except the console. - Puppet::Util::Log.destinations.reject { |d| d == :console }.each do |dest| - Puppet::Util::Log.close(dest) + # Trap a couple of the main signals. This should probably be handled + # in a way that anyone else can register callbacks for traps, but, eh. + def set_signal_traps + {:INT => :stop, :TERM => :stop, :HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method| + trap(signal) do + Puppet.notice "Caught #{signal}; calling #{method}" + send(method) + end end end + + # Stop everything + def stop(args = {:exit => true}) + server.stop if server + + agent.stop if agent + + remove_pidfile() + + Puppet::Util::Log.close_all + + exit if args[:exit] + end + + def start + set_signal_traps + + create_pidfile + + raise Puppet::DevError, "Daemons must have an agent, server, or both" unless agent or server + agent.start if agent + server.start if server + + EventLoop.current.run + end end diff --git a/lib/puppet/event_manager.rb b/lib/puppet/event_manager.rb deleted file mode 100644 index 50c489673..000000000 --- a/lib/puppet/event_manager.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'puppet/external/event-loop' - -# Manage events related to starting, stopping, and restarting -# contained services. -class Puppet::EventManager - include SignalObserver - - attr_reader :services, :threads, :timers - - def initialize - @services = [] - @threads = [] - @timers = [] - end - - # Create a new service that we're supposed to run - def add_service(service) - @services << service - end - - def newthread(&block) - @threads << Thread.new { yield } - end - - # Add a timer we need to pay attention to. - # This is only used by Puppet::Agent at the moment. - def newtimer(hash, &block) - timer = EventLoop::Timer.new(hash) - @timers << timer - - if block_given? - observe_signal(timer, :alarm, &block) - end - - # In case they need it for something else. - timer - end - - # Reload any services that can be reloaded. Really, this is just - # meant to trigger an Agent run. - def reload - done = 0 - services.find_all { |service| service.respond_to?(:run) }.each do |service| - if service.running? - Puppet.notice "Not triggering already-running %s" % service.class - next - end - - Puppet.notice "Triggering a run of %s" % service.class - - done += 1 - begin - service.run - rescue => detail - Puppet.err "Could not run service %s: %s" % [service.class, detail] - end - end - - unless done > 0 - Puppet.notice "No services were reloaded" - end - end - - def reopen_logs - Puppet::Util::Log.reopen - end - - # Relaunch the executable. - def restart - if client = @services.find { |s| s.is_a? Puppet::Network::Client.master } and client.running? - client.restart - else - command = $0 + " " + self.args.join(" ") - Puppet.notice "Restarting with '%s'" % command - Puppet.shutdown(false) - Puppet::Util::Log.reopen - exec(command) - end - end - - # Trap a couple of the main signals. This should probably be handled - # in a way that anyone else can register callbacks for traps, but, eh. - def set_traps - {:INT => :shutdown, :TERM => :shutdown, :HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method| - trap(signal) do - Puppet.notice "Caught #{signal}; calling #{method}" - send(method) - end - end - end - - # Shutdown our server process, meaning stop all services and all threads. - # Optionally, exit. - def shutdown(leave = true) - Puppet.notice "Shutting down" - stop_timers - - stop_services - - stop_threads - - if leave - exit(0) - end - end - - # Start all of our services and optionally our event loop, which blocks, - # waiting for someone, somewhere, to generate events of some kind. - def start - start_services - - start_timers - - EventLoop.current.run - end - - def start_services - # Starting everything in its own thread. Otherwise - # we might have one service stop another service from - # doing things like registering timers. - @services.dup.each do |svc| - begin - svc.start - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - @services.delete svc - Puppet.err "Could not start %s: %s" % [svc.class, detail] - end - end - - # We need to give the services a chance to register their timers before - # we try to start monitoring them. - sleep 0.5 - - unless @services.length > 0 - Puppet.notice "No remaining services; exiting" - exit(1) - end - end - - def stop_services - # Stop our services - services.each do |svc| - begin - timeout(20) do - svc.shutdown - end - rescue TimeoutError - Puppet.err "%s could not shut down within 20 seconds" % svc.class - end - end - end - - # Monitor all of the timers that have been set up. - def start_timers - timers.each do |timer| - EventLoop.current.monitor_timer timer - end - end - - def stop_timers - # Unmonitor our timers - timers.each do |timer| - EventLoop.current.ignore_timer timer - end - end - - def stop_threads - # And wait for them all to die, giving a decent amount of time - threads.each do |thr| - begin - timeout(20) do - thr.join - end - rescue TimeoutError - # Just ignore this, since we can't intelligently provide a warning - end - end - end -end diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb index e9421c781..924c11728 100644 --- a/lib/puppet/network/http_server/mongrel.rb +++ b/lib/puppet/network/http_server/mongrel.rb @@ -34,7 +34,6 @@ require 'puppet/network/xmlrpc/server' require 'puppet/network/http_server' require 'puppet/network/client_request' require 'puppet/network/handler' -require 'puppet/daemon' require 'resolv' @@ -51,7 +50,6 @@ require 'resolv' # </pre> module Puppet::Network class HTTPServer::Mongrel < ::Mongrel::HttpHandler - include Puppet::Daemon attr_reader :xmlrpc_server def initialize(handlers) diff --git a/lib/puppet/network/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb index 568b4e798..0e835d057 100644 --- a/lib/puppet/network/http_server/webrick.rb +++ b/lib/puppet/network/http_server/webrick.rb @@ -1,5 +1,4 @@ require 'puppet' -require 'puppet/daemon' require 'webrick' require 'webrick/https' require 'fcntl' @@ -16,7 +15,6 @@ module Puppet # The old-school, pure ruby webrick server, which is the default serving # mechanism. class HTTPServer::WEBrick < WEBrick::HTTPServer - include Puppet::Daemon include Puppet::SSLCertificates::Support # Read the CA cert and CRL and populate an OpenSSL::X509::Store diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index b9736f5ac..0af842c8d 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -3,6 +3,7 @@ require 'sync' require 'puppet/transportable' require 'getoptlong' +require 'puppet/external/event-loop' # The class for handling configuration files. class Puppet::Util::Settings @@ -46,9 +47,6 @@ class Puppet::Util::Settings # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) - # Hackish, but acceptable. Copy the current ARGV for restarting. - Puppet.args = ARGV.dup - # Add all of the config parameters as valid options. self.each { |name, element| element.getopt_args.each { |args| options << args } @@ -496,16 +494,9 @@ class Puppet::Util::Settings end # Create a timer to check whether the file should be reparsed. - def settimer - if Puppet[:filetimeout] > 0 - @timer = Puppet.newtimer( - :interval => Puppet[:filetimeout], - :tolerance => 1, - :start? => true - ) do - self.reparse() - end - end + def set_filetimeout_timer + return unless timeout = self[:filetimeout] and timeout > 0 + EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse() } end # Convert the settings we manage into a catalog full of resources that model those settings. @@ -822,7 +813,7 @@ Generated on #{Time.now}. # Create a timer so that this file will get checked automatically # and reparsed if necessary. - settimer() + set_filetimeout_timer() result = Hash.new { |names, name| names[name] = {} diff --git a/spec/unit/agent.rb b/spec/unit/agent.rb index 5622af4a6..03136c484 100755 --- a/spec/unit/agent.rb +++ b/spec/unit/agent.rb @@ -45,14 +45,22 @@ describe Puppet::Agent do @agent.lockfile_path.should == "/my/lock" end - describe "when running" do + it "should be considered running if a client instance is available" do + client = AgentTestClient.new + AgentTestClient.expects(:new).returns client + + client.expects(:run).with { @agent.should be_running } + @agent.run + end + + describe "when being run" do it "should splay" do @agent.expects(:splay) @agent.run end - it "should do nothing if a client instance exists" do + it "should do nothing if already running" do @agent.expects(:client).returns "eh" AgentTestClient.expects(:new).never @agent.run @@ -147,10 +155,10 @@ describe Puppet::Agent do end end - describe "when shutting down" do + describe "when stopping" do it "should do nothing if already stopping" do @agent.expects(:stopping?).returns true - @agent.shutdown + @agent.stop end it "should stop the client if one is available and it responds to 'stop'" do @@ -158,12 +166,7 @@ describe Puppet::Agent do @agent.stubs(:client).returns client client.expects(:stop) - @agent.shutdown - end - - it "should remove its pid file" do - @agent.expects(:rmpidfile) - @agent.shutdown + @agent.stop end it "should mark itself as stopping while waiting for the client to stop" do @@ -172,34 +175,68 @@ describe Puppet::Agent do @agent.stubs(:client).returns client client.expects(:stop).with { @agent.should be_stopping; true } - @agent.shutdown + @agent.stop end end describe "when starting" do + before do + @agent.stubs(:observe_signal) + end + it "should create a timer with the runinterval, a tolerance of 1, and :start? set to true" do Puppet.settings.expects(:value).with(:runinterval).returns 5 - Puppet.expects(:newtimer).with(:interval => 5, :start? => true, :tolerance => 1) + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).returns timer + @agent.stubs(:run) @agent.start end it "should run once immediately" do - Puppet.stubs(:newtimer) - @agent.expects(:run) + timer = mock 'timer' + EventLoop::Timer.expects(:new).returns timer + + timer.expects(:sound_alarm) + @agent.start end it "should run within the block passed to the timer" do - Puppet.stubs(:newtimer).yields - @agent.expects(:run).times(2) + timer = stub 'timer', :sound_alarm => nil + EventLoop::Timer.expects(:new).returns(timer).yields @agent.start end + end - it "should run within the block passed to the timer" do - Puppet.stubs(:newtimer).yields - @agent.expects(:run).times(2) - @agent.start + describe "when restarting" do + it "should configure itself for a delayed restart if currently running" do + @agent.expects(:running?).returns true + + @agent.restart + + @agent.should be_needing_restart + end + + it "should hup itself if not running" do + @agent.expects(:running?).returns false + + Process.expects(:kill).with(:HUP, $$) + + @agent.restart + end + + it "should turn off the needing_restart switch" do + @agent.expects(:running?).times(2).returns(true).then.returns false + + Process.stubs(:kill) + + # First call sets up the switch + @agent.restart + + # Second call should disable it + @agent.restart + @agent.should_not be_needing_restart end end end diff --git a/spec/unit/daemon.rb b/spec/unit/daemon.rb new file mode 100755 index 000000000..0477decb1 --- /dev/null +++ b/spec/unit/daemon.rb @@ -0,0 +1,287 @@ +#!/usr/bin/env ruby" + +require File.dirname(__FILE__) + '/../spec_helper' +require 'puppet/daemon' + +describe Puppet::Daemon do + before do + @daemon = Puppet::Daemon.new + end + + it "should be able to manage an agent" do + @daemon.should respond_to(:agent) + end + + it "should be able to manage a network server" do + @daemon.should respond_to(:server) + end + + it "should reopen the Log logs when told to reopen logs" do + Puppet::Util::Log.expects(:reopen) + @daemon.reopen_logs + end + + describe "when setting signal traps" do + before do + @daemon.stubs(:trap) + end + + {:INT => :stop, :TERM => :stop, :HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method| + it "should log and call #{method} when it receives #{signal}" do + @daemon.expects(:trap).with(signal).yields + + Puppet.expects(:notice) + + @daemon.expects(method) + + @daemon.set_signal_traps + end + end + end + + describe "when starting" do + before do + @daemon.stubs(:create_pidfile) + @daemon.stubs(:set_signal_traps) + EventLoop.current.stubs(:run) + end + + it "should fail if it has neither agent nor server" do + lambda { @daemon.start }.should raise_error(Puppet::DevError) + end + + it "should create its pidfile" do + @daemon.stubs(:agent).returns stub('agent', :start => nil) + + @daemon.expects(:create_pidfile) + @daemon.start + end + + it "should start the agent if the agent is configured" do + agent = mock 'agent' + agent.expects(:start) + @daemon.stubs(:agent).returns agent + + @daemon.start + end + + it "should start its server if one is configured" do + server = mock 'server' + server.expects(:start) + @daemon.stubs(:server).returns server + + @daemon.start + end + + it "should let the current EventLoop run" do + @daemon.stubs(:agent).returns stub('agent', :start => nil) + EventLoop.current.expects(:run) + + @daemon.start + end + end + + describe "when stopping" do + before do + @daemon.stubs(:remove_pidfile) + @daemon.stubs(:exit) + Puppet::Util::Log.stubs(:close_all) + end + + it "should stop its server if one is configured" do + server = mock 'server' + server.expects(:stop) + @daemon.stubs(:server).returns server + + @daemon.stop + end + + it "should stop its agent if one is configured" do + agent = mock 'agent' + agent.expects(:stop) + @daemon.stubs(:agent).returns agent + + @daemon.stop + end + + it "should remove its pidfile" do + @daemon.expects(:remove_pidfile) + + @daemon.stop + end + + it "should close all logs" do + Puppet::Util::Log.expects(:close_all) + + @daemon.stop + end + + it "should exit unless called with ':exit => false'" do + @daemon.expects(:exit) + @daemon.stop + end + + it "should not exit if called with ':exit => false'" do + @daemon.expects(:exit).never + @daemon.stop :exit => false + end + end + + describe "when creating its pidfile" do + it "should use an exclusive mutex" do + Puppet.settings.expects(:value).with(:name).returns "me" + + sync = mock 'sync' + Puppet::Util.expects(:sync).with("me").returns sync + + sync.expects(:synchronize).with(Sync::EX) + @daemon.create_pidfile + end + + it "should lock the pidfile using the Pidlock class" do + pidfile = mock 'pidfile' + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.expects(:value).with(:pidfile).returns "/my/file" + + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + pidfile.expects(:lock).returns true + @daemon.create_pidfile + end + + it "should fail if it cannot lock" do + pidfile = mock 'pidfile' + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + pidfile.expects(:lock).returns false + + lambda { @daemon.create_pidfile }.should raise_error + end + end + + describe "when removing its pidfile" do + it "should use an exclusive mutex" do + Puppet.settings.expects(:value).with(:name).returns "me" + + sync = mock 'sync' + Puppet::Util.expects(:sync).with("me").returns sync + + sync.expects(:synchronize).with(Sync::EX) + @daemon.remove_pidfile + end + + it "should do nothing if the pidfile is not present" do + pidfile = mock 'pidfile', :locked? => false + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + pidfile.expects(:unlock).never + @daemon.remove_pidfile + end + + it "should unlock the pidfile using the Pidlock class" do + pidfile = mock 'pidfile', :locked? => true + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + pidfile.expects(:unlock).returns true + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + @daemon.remove_pidfile + end + + it "should warn if it cannot remove the pidfile" do + pidfile = mock 'pidfile', :locked? => true + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + pidfile.expects(:unlock).returns false + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + Puppet.expects :err + @daemon.remove_pidfile + end + end + + describe "when reloading" do + it "should do nothing if no agent is configured" do + @daemon.reload + end + + it "should do nothing if the agent is running" do + agent = mock 'agent' + agent.expects(:running?).returns true + + @daemon.stubs(:agent).returns agent + + @daemon.reload + end + + it "should run the agent if one is available and it is not running" do + agent = mock 'agent' + agent.expects(:running?).returns false + agent.expects :run + + @daemon.stubs(:agent).returns agent + + @daemon.reload + end + end + + describe "when restarting" do + it "should reexec itself if no agent is available" do + @daemon.expects(:reexec) + + @daemon.restart + end + + it "should reexec itself if the agent is not running" do + agent = mock 'agent' + agent.expects(:running?).returns false + @daemon.stubs(:agent).returns agent + @daemon.expects(:reexec) + + @daemon.restart + end + + it "should configure the agent for later restart if the agent is running" do + agent = mock 'agent' + agent.expects(:running?).returns true + @daemon.stubs(:agent).returns agent + @daemon.expects(:reexec).never + + agent.expects(:configure_delayed_restart) + + @daemon.restart + end + end + + describe "when reexecing it self" do + it "should fail if no argv values are available" do + @daemon.expects(:argv).returns nil + lambda { @daemon.reexec }.should raise_error(Puppet::DevError) + end + + it "should shut down without exiting" do + @daemon.argv = %w{foo} + @daemon.expects(:stop).with(:exit => false) + + @daemon.stubs(:exec) + @daemon.reexec + end + + it "should call 'exec' with the original executable and arguments" do + @daemon.argv = %w{foo} + @daemon.expects(:exec).with($0 + " " + "foo") + + @daemon.reexec + end + end +end diff --git a/spec/unit/event_manager.rb b/spec/unit/event_manager.rb deleted file mode 100755 index e15461c98..000000000 --- a/spec/unit/event_manager.rb +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'puppet/event_manager' -require 'puppet/agent' - -describe Puppet::EventManager do - before do - @manager = Puppet::EventManager.new - end - - it "should include SignalObserver" do - Puppet::EventManager.ancestors.should be_include(SignalObserver) - end - - it "should should add the provided service to its list of services when a new service is added" do - @manager.add_service("foo") - @manager.services.should be_include("foo") - end - - it "should create a new thread and add it to its thread list when a new thread is added" do - Thread.expects(:new).returns "foo" - @manager.newthread {} - @manager.threads.should be_include("foo") - end - - it "should stop all timers, services, and threads, then exit, when asked to shutdown" do - @manager.expects(:stop_services) - @manager.expects(:stop_timers) - @manager.expects(:stop_threads) - - @manager.expects(:exit) - - @manager.shutdown - end - - it "should tell the event loop to monitor each timer when told to start timers" do - timer1 = mock 'timer1' - timer2 = mock 'timer2' - - @manager.expects(:timers).returns [timer1, timer2] - - EventLoop.current.expects(:monitor_timer).with timer1 - EventLoop.current.expects(:monitor_timer).with timer2 - - @manager.start_timers - end - - it "should tell the event loop to stop monitoring each timer when told to stop timers" do - timer1 = mock 'timer1' - timer2 = mock 'timer2' - - @manager.expects(:timers).returns [timer1, timer2] - - EventLoop.current.expects(:ignore_timer).with timer1 - EventLoop.current.expects(:ignore_timer).with timer2 - - @manager.stop_timers - end - - it "should start all services, monitor all timers, and let the current event loop run when told to start" do - @manager.expects(:start_services) - @manager.expects(:start_timers) - - EventLoop.current.expects(:run) - - @manager.start - end - - it "should reopen the Log logs when told to reopen logs" do - Puppet::Util::Log.expects(:reopen) - @manager.reopen_logs - end - - describe "when adding a timer" do - before do - @timer = mock("timer") - EventLoop::Timer.stubs(:new).returns @timer - - @manager.stubs(:observe_signal) - end - - it "should create and return a new timer with the provided arguments" do - timer = mock("timer") - EventLoop::Timer.expects(:new).with(:foo => :bar).returns @timer - - @manager.newtimer(:foo => :bar) {}.should equal(@timer) - end - - it "should add the timer to the list of timers" do - @manager.newtimer(:foo => :bar) {} - - @manager.timers.should be_include(@timer) - end - - it "should set up a signal observer for the timer" do - @manager.expects(:observe_signal).with { |timer, signal, block| timer == @timer and signal == :alarm } - - @manager.newtimer(:foo => :bar) {} - end - end - - describe "when starting services" do - before do - @service = stub 'service', :start => nil - @manager.stubs(:sleep) - end - - it "should start each service" do - service = mock 'service' - service.expects(:start) - - @manager.add_service service - - @manager.start_services - end - - it "should not fail if a service fails to start" do - service = mock 'service' - service.expects(:start).raises "eh" - - @manager.add_service @service - @manager.add_service service - - lambda { @manager.start_services }.should_not raise_error - end - - it "should delete failed services from its service list" do - service = mock 'service' - service.expects(:start).raises "eh" - - @manager.add_service @service - @manager.add_service service - - @manager.start_services - - @manager.services.should_not be_include(service) - end - -# it "should start each service in a separate thread" do -# # They don't expect 'start', because we're stubbing 'newthread' -# service1 = mock 'service1' -# service2 = mock 'service2' -# -# @manager.add_service service1 -# @manager.add_service service2 -# -# @manager.expects(:newthread).times(2) -# -# @manager.start_services -# end - - it "should exit if no services were able to be started" do - service = mock 'service' - service.expects(:start).raises "eh" - - @manager.add_service service - - @manager.expects(:exit).with(1) - - lambda { @manager.start_services }.should_not raise_error - end - end - - describe "when stopping services" do - it "should use a timeout" do - @manager.expects(:timeout).with(20) - @manager.expects(:services).returns %w{foo} - - @manager.stop_services - end - - it "should stop each service" do - service = mock 'service' - service.expects(:shutdown) - @manager.expects(:services).returns [service] - - @manager.stop_services - end - - it "should log if a timeout is encountered" do - service = mock 'service' - service.expects(:shutdown).raises(TimeoutError) - @manager.expects(:services).returns [service] - - Puppet.expects(:err) - - @manager.stop_services - end - end - - describe "when stopping threads" do - it "should use a timeout" do - @manager.expects(:timeout).with(20) - @manager.expects(:threads).returns %w{foo} - - @manager.stop_threads - end - - it "should join each thread" do - thread = mock 'thread' - thread.expects(:join) - @manager.expects(:threads).returns [thread] - - @manager.stop_threads - end - - it "should not fail if a timeout is encountered" do - thread = mock 'thread' - thread.expects(:join).raises(TimeoutError) - @manager.expects(:threads).returns [thread] - - @manager.stop_threads - end - end - - describe "when setting traps" do - before do - @manager.stubs(:trap) - end - - {:INT => :shutdown, :TERM => :shutdown, :HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method| - it "should log and call #{method} when it receives #{signal}" do - @manager.expects(:trap).with(signal).yields - - Puppet.expects(:notice) - - @manager.expects(method) - - @manager.set_traps - end - end - end - - describe "when reloading" do - it "should run all services that can be run but are not currently running" do - service = Puppet::Agent.new(String) - - @manager.add_service service - - service.expects(:running?).returns false - service.expects(:run) - - @manager.reload - end - - it "should not run services that are already running" do - service = Puppet::Agent.new(String) - - @manager.add_service service - - service.expects(:running?).returns true - service.expects(:run).never - - @manager.reload - end - - it "should not try to run services that cannot be run" do - service = "string" - @manager.add_service service - - @manager.reload - end - end -end diff --git a/spec/unit/network/client.rb b/spec/unit/network/client.rb index e9467aad0..cea71d1e5 100755 --- a/spec/unit/network/client.rb +++ b/spec/unit/network/client.rb @@ -24,7 +24,7 @@ describe Puppet::Network::Client do Net::HTTP.stubs(:new).returns http # Pick a random subclass... - Puppet::Network::Client.master.new :Server => Puppet[:server] + Puppet::Network::Client.runner.new :Server => Puppet[:server] end end @@ -39,7 +39,7 @@ describe Puppet::Network::Client do Net::HTTP.stubs(:new).returns http # Pick a random subclass... - Puppet::Network::Client.master.new :Server => Puppet[:server] + Puppet::Network::Client.runner.new :Server => Puppet[:server] end end end diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb index a18dd340a..f84c467e2 100755 --- a/spec/unit/util/settings.rb +++ b/spec/unit/util/settings.rb @@ -287,6 +287,14 @@ describe Puppet::Util::Settings do @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] end + it "should set a timer that triggers reparsing" do + @settings.expects(:set_filetimeout_timer) + file = "/some/file" + @settings.expects(:read_file).with(file).returns("[main]") + + @settings.parse(file) + end + it "should return values set in the configuration file" do text = "[main] one = fileval @@ -844,6 +852,40 @@ describe Puppet::Util::Settings do end end end + + describe "when setting a timer to trigger configuration file reparsing" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :foo, :filetimeout => [5, "eh"] + end + + it "should do nothing if no filetimeout setting is available" do + @settings.expects(:value).with(:filetimeout).returns nil + EventLoop::Timer.expects(:new).never + @settings.set_filetimeout_timer + end + + it "should do nothing if the filetimeout setting is not greater than 0" do + @settings.expects(:value).with(:filetimeout).returns -2 + EventLoop::Timer.expects(:new).never + @settings.set_filetimeout_timer + end + + it "should create a timer with its interval set to the filetimeout, start? set to true, and a tolerance of 1" do + @settings.expects(:value).with(:filetimeout).returns 5 + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1) + + @settings.set_filetimeout_timer + end + + it "should reparse when the timer goes off" do + EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).yields + + @settings.expects(:reparse) + + @settings.set_filetimeout_timer + end + end end describe Puppet::Util::Settings::CFile do diff --git a/test/network/daemon.rb b/test/network/daemon.rb deleted file mode 100755 index 5105c6e4c..000000000 --- a/test/network/daemon.rb +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../lib/puppettest' - -require 'puppettest' -require 'puppet/daemon' - -class TestDaemon < Test::Unit::TestCase - include PuppetTest - - class FakeDaemon - include Puppet::Daemon - end - - def test_pidfile - daemon = FakeDaemon.new - - assert_nothing_raised("removing non-existent file failed") do - daemon.rmpidfile - end - - Puppet[:pidfile] = tempfile() - assert_nothing_raised "could not lock" do - daemon.setpidfile - end - - assert(FileTest.exists?(daemon.pidfile), - "did not create pidfile") - - assert_nothing_raised("removing non-existent file failed") do - daemon.rmpidfile - end - - assert(! FileTest.exists?(daemon.pidfile), - "did not remove pidfile") - end - - def test_daemonize - daemon = FakeDaemon.new - Puppet[:pidfile] = tempfile() - - exiter = tempfile() - - assert_nothing_raised("Could not fork and daemonize") do - fork do - daemon.send(:daemonize) - # Wait a max of 5 secs - 50.times do - if FileTest.exists?(exiter) - daemon.rmpidfile - exit(0) - end - sleep 0.1 - end - exit(0) - end - end - sleep(0.1) - assert(FileTest.exists?(Puppet[:pidfile]), - "did not create pidfile on daemonize") - - File.open(exiter, "w") { |f| f.puts "" } - - sleep(0.2) - assert(! FileTest.exists?(Puppet[:pidfile]), - "did not remove pidfile on process death") - end -end - - |
