diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 18e45cb5f..d51729397 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,114 +1,140 @@ require 'sync' require 'puppet/external/event-loop' require 'puppet/application' # A general class for triggering a run of another # class. class Puppet::Agent require 'puppet/agent/locker' include Puppet::Agent::Locker require 'puppet/agent/disabler' include Puppet::Agent::Disabler attr_reader :client_class, :client, :splayed + attr_accessor :should_fork # Just so we can specify that we are "the" instance. def initialize(client_class) @splayed = false @client_class = client_class end def lockfile_path client_class.lockfile_path end def needing_restart? Puppet::Application.restart_requested? end # Perform a run with our client. def run(*args) if running? Puppet.notice "Run of #{client_class} already in progress; skipping" return end if disabled? Puppet.notice "Skipping run of #{client_class}; administratively disabled: #{disable_message}" return end result = nil block_run = Puppet::Application.controlled_run do splay - result = with_client do |client| - begin - sync.synchronize { lock { client.run(*args) } } - rescue SystemExit,NoMemoryError - raise - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not run #{client_class}: #{detail}" + result = run_in_fork(should_fork) do + with_client do |client| + begin + sync.synchronize { lock { client.run(*args) } } + rescue SystemExit,NoMemoryError + raise + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not run #{client_class}: #{detail}" + end end end true end Puppet.notice "Shutdown/restart in progress; skipping run" unless block_run result end def stopping? Puppet::Application.stop_requested? end # Have we splayed already? def splayed? splayed end # Sleep when splay is enabled; else just return. def splay return unless Puppet[:splay] return if splayed? time = rand(Integer(Puppet[:splaylimit]) + 1) Puppet.info "Sleeping for #{time} seconds (splay is enabled)" sleep(time) @splayed = true end # Start listening for events. We're pretty much just listening for # timer events here. def start # Create our timer. Puppet will handle observing it and such. timer = EventLoop::Timer.new(:interval => Puppet[:runinterval], :tolerance => 1, :start? => true) do run end # Run once before we start following the timer timer.sound_alarm end def sync @sync ||= Sync.new end + def run_in_fork(forking = true) + return yield unless forking or Puppet.features.windows? + + child_pid = Kernel.fork do + $0 = "puppet agent: applying configuration" + begin + exit(yield) + rescue SystemExit + exit(-1) + rescue NoMemoryError + exit(-2) + end + end + exit_code = Process.waitpid2(child_pid) + case exit_code[1].exitstatus + when -1 + raise SystemExit + when -2 + raise NoMemoryError + end + exit_code[1].exitstatus + end + private # Create and yield a client instance, keeping a reference # to it during the yield. def with_client begin @client = client_class.new rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not create instance of #{client_class}: #{detail}" return end yield @client ensure @client = nil end end diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 676954e77..465b26863 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -1,483 +1,484 @@ require 'puppet/application' class Puppet::Application::Agent < Puppet::Application should_parse_config run_mode :agent attr_accessor :args, :agent, :daemon, :host def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :enable => false, :disable => false, :client => true, :fqdn => nil, :serve => [], :digest => :MD5, :fingerprint => false, }.each do |opt,val| options[opt] = val end @args = {} require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end option("--centrallogging") option("--disable [MESSAGE]") do |message| options[:disable] = true options[:disable_message] = message end option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--fingerprint") option("--digest DIGEST") option("--no-client") do |arg| options[:client] = false end option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--port PORT","-p") do |arg| @args[:Port] = arg end def help <<-HELP puppet-agent(8) -- The puppet agent daemon ======== SYNOPSIS -------- Retrieves the client configuration from the puppet master and applies it to the local host. This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes. USAGE ----- puppet agent [--certname ] [-D|--daemonize|--no-daemonize] [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [message]] [--enable] [--fingerprint] [-h|--help] [-l|--logdest syslog||console] [--no-client] [--noop] [-o|--onetime] [-t|--test] [-v|--verbose] [-V|--version] [-w|--waitforcert ] DESCRIPTION ----------- This is the main puppet client. Its job is to retrieve the local machine's configuration from a remote server and apply it. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default). The client will connect and request a signed certificate, and will continue connecting until it receives one. Once the client has a signed certificate, it will retrieve its configuration and apply it. USAGE NOTES ----------- 'puppet agent' does its best to find a compromise between interactive use and daemon use. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes. Some flags are meant specifically for interactive use -- in particular, 'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose logging, causes the daemon to stay in the foreground, exits if the server's configuration is invalid (this happens if, for instance, you've left a syntax error on the server), and exits after running the configuration once (rather than hanging around as a long-running process). 'tags' allows you to specify what portions of a configuration you want to apply. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the 'tags' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied. This is very useful when you are testing new configurations -- for instance, if you are just starting to manage 'ntpd', you would put all of the new elements into an 'ntpd' class, and call puppet with '--tags ntpd', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing. 'fingerprint' is a one-time flag. In this mode 'puppet agent' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint. Providing the '--digest' option allows to use a different digest algorithm to generate the fingerprint. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man-in-the-middle attacks when signing certificates). OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet agent with '--genconfig'. * --certname: Set the certname (unique ID) of the client. The master reads this unique identifying string, which is usually set to the node's fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --digest: Change the certificate fingerprinting digest algorithm. The default is MD5. Valid values depends on the version of OpenSSL installed, but should always at least contain MD5, MD2, SHA1 and SHA256. * --disable: Disable working on the local system. This puts a lock file in place, causing 'puppet agent' not to work on the system until the lock file is removed. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed. Disable can also take an optional message that will be reported by the 'puppet agent' at the next disabled run. 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. 'puppet agent' exits after executing this. * --enable: Enable working on the local system. This removes any lock file, causing 'puppet agent' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour). 'puppet agent' exits after executing this. * --fingerprint: Display the current certificate or certificate signing request fingerprint and then exit. Use the '--digest' option to change the digest algorithm used. * --help: Print this help message * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --no-client: Do not create a config client. This will cause the daemon to run without ever checking for its configuration automatically, and only makes sense when puppet agent is being run with listen = true in puppet.conf or was started with the `--listen` option. * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', 'detailed-exit-codes', 'no-splay', and 'show_diff'. * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. * --waitforcert: This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes 'puppet agent' to connect to the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. EXAMPLE ------- $ puppet agent --server puppet.domain.com DIAGNOSTICS ----------- Puppet agent accepts the following signals: * SIGHUP: Restart the puppet agent daemon. * SIGINT and SIGTERM: Shut down the puppet agent daemon. * SIGUSR1: Immediately retrieve and apply configurations from the puppet master. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command return fingerprint if options[:fingerprint] return onetime if Puppet[:onetime] main end def fingerprint unless cert = host.certificate || host.certificate_request $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" exit(1) return end unless fingerprint = cert.fingerprint(options[:digest]) raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" end puts fingerprint end def onetime unless options[:client] $stderr.puts "onetime is specified but there is no client" exit(43) return end @daemon.set_signal_traps begin + @agent.should_fork = false exitstatus = @agent.run rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail.to_s end @daemon.stop(:exit => false) if not exitstatus exit(1) elsif options[:detailed_exitcodes] then exit(exitstatus) else exit(0) end end def main Puppet.notice "Starting Puppet client version #{Puppet.version}" @daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true Puppet[:onetime] = true options[:detailed_exitcodes] = true end # Handle the logging settings. def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable(options[:disable_message] || 'reason not specified') end exit(0) end def setup_listen unless FileTest.exists?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end require 'puppet/network/server' # No REST handlers yet. server = Puppet::Network::Server.new(:port => Puppet[:puppetport]) @daemon.server = server end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] end def setup_agent # We need tomake the client either way, we just don't start it # if --no-client is set. require 'puppet/agent' require 'puppet/configurer' @agent = Puppet::Agent.new(Puppet::Configurer) enable_disable_client(@agent) if options[:enable] or options[:disable] @daemon.agent = agent if options[:client] # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. @daemon.daemonize if Puppet[:daemonize] setup_host @objects = [] # This has to go after the certs are dealt with. if Puppet[:listen] unless Puppet[:onetime] setup_listen else Puppet.notice "Ignoring --listen on onetime run" end end end def setup setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? args[:Server] = Puppet[:server] if options[:fqdn] args[:FQDN] = options[:fqdn] Puppet[:certname] = options[:fqdn] end if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :ssl # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true # We need to specify a ca location for all of the SSL-related i # indirected classes to work; in fingerprint mode we just need # access to the local files and we don't need a ca. Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.indirection.terminus_class = :rest # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml # Override the default; puppetd needs this, usually. # You can still override this on the command-line with, e.g., :compiler. Puppet[:catalog_terminus] = :rest # Override the default. Puppet[:facts_terminus] = :facter Puppet::Resource::Catalog.indirection.cache_class = :yaml unless options[:fingerprint] setup_agent else setup_host end end end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 22630ffb8..03c21a282 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,130 +1,133 @@ require 'puppet' require 'puppet/util/pidlock' require 'puppet/external/event-loop' require 'puppet/application' # A module that handles operations common to all daemons. This is included # into the Server and Client base classes. class Puppet::Daemon attr_accessor :agent, :server, :argv def daemonname Puppet[:name] end # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end create_pidfile # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen rescue => detail Puppet.err "Could not start #{Puppet[:name]}: #{detail}" Puppet::Util::secure_open("/tmp/daemonout", "w") { |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" } exit(12) end end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Provide the path to our pidfile. def pidfile 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 '#{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 for our daemon. def remove_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do locker = Puppet::Util::Pidlock.new(pidfile) locker.unlock or Puppet.err "Could not remove PID file #{pidfile}" if locker.locked? end end def restart Puppet::Application.restart! reexec unless agent and agent.running? end def reopen_logs Puppet::Util::Log.reopen 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_signal_traps signals = {:INT => :stop, :TERM => :stop } # extended signals not supported under windows signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs }) unless Puppet.features.microsoft_windows? signals.each do |signal, method| Signal.trap(signal) do Puppet.notice "Caught #{signal}; calling #{method}" send(method) end end end # Stop everything def stop(args = {:exit => true}) Puppet::Application.stop! server.stop if server 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 server.start if server - agent.start if agent + if agent + agent.should_fork = true + agent.start + end EventLoop.current.run end end diff --git a/spec/unit/agent_spec.rb b/spec/unit/agent_spec.rb index 17f1da618..48cc76006 100755 --- a/spec/unit/agent_spec.rb +++ b/spec/unit/agent_spec.rb @@ -1,293 +1,346 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' class AgentTestClient def run # no-op end def stop # no-op end end def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Agent do before do @agent = Puppet::Agent.new(AgentTestClient) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields @agent.stubs(:disabled?).returns(false) # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. without_warnings { Puppet::Application = Class.new(Puppet::Application) } Puppet::Application.stubs(:clear?).returns(true) Puppet::Application.class_eval do class << self def controlled_run(&block) block.call end end end end after do # restore Puppet::Application from stub-safe subclass, and silence warnings without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should set its client class at initialization" do Puppet::Agent.new("foo").client_class.should == "foo" end it "should include the Locker module" do Puppet::Agent.ancestors.should be_include(Puppet::Agent::Locker) end it "should create an instance of its client class and run it when asked to run" do client = mock 'client' AgentTestClient.expects(:new).returns client client.expects(:run) @agent.stubs(:running?).returns false @agent.run end it "should determine its lock file path by asking the client class" do AgentTestClient.expects(:lockfile_path).returns "/my/lock" @agent.lockfile_path.should == "/my/lock" end it "should be considered running if the lock file is locked" do lockfile = mock 'lockfile' @agent.expects(:lockfile).returns lockfile lockfile.expects(:locked?).returns true @agent.should be_running end describe "when being run" do before do AgentTestClient.stubs(:lockfile_path).returns "/my/lock" @agent.stubs(:running?).returns false end it "should splay" do @agent.expects(:splay) @agent.stubs(:running?).returns false @agent.run end it "should do nothing if already running" do @agent.expects(:running?).returns true AgentTestClient.expects(:new).never @agent.run end it "should do nothing if disabled" do @agent.expects(:disabled?).returns(true) AgentTestClient.expects(:new).never @agent.run end it "should use Puppet::Application.controlled_run to manage process state behavior" do calls = sequence('calls') Puppet::Application.expects(:controlled_run).yields.in_sequence(calls) AgentTestClient.expects(:new).once.in_sequence(calls) @agent.run end it "should not fail if a client class instance cannot be created" do AgentTestClient.expects(:new).raises "eh" Puppet.expects(:err) @agent.run end it "should not fail if there is an exception while running its client" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises "eh" Puppet.expects(:err) @agent.run end it "should use a mutex to restrict multi-threading" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client mutex = mock 'mutex' @agent.expects(:sync).returns mutex mutex.expects(:synchronize) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should use a filesystem lock to restrict multiple processes running the agent" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should make its client instance available while running" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with { @agent.client.should equal(client); true } @agent.run end it "should run the client instance with any arguments passed to it" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with("testargs") @agent.run("testargs") end it "should return the agent result" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock).returns(:result) @agent.run.should == :result end + + describe "when should_fork is true" do + before do + @agent.should_fork = true + Kernel.stubs(:fork) + Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 0)] + @agent.stubs(:exit) + end + + it "should run the agent in a forked process" do + client = AgentTestClient.new + AgentTestClient.expects(:new).returns client + + client.expects(:run) + + Kernel.expects(:fork).yields + @agent.run + end + + it "should exit child process if child exit" do + client = AgentTestClient.new + AgentTestClient.expects(:new).returns client + + client.expects(:run).raises(SystemExit) + + Kernel.expects(:fork).yields + @agent.expects(:exit).with(-1) + @agent.run + end + + it "should re-raise exit happening in the child" do + Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -1)] + lambda { @agent.run }.should raise_error(SystemExit) + end + + it "should re-raise NoMoreMemory happening in the child" do + Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -2)] + lambda { @agent.run }.should raise_error(NoMemoryError) + end + + it "should return the child exit code" do + Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 777)] + @agent.run.should == 777 + end + + it "should return the block exit code as the child exit code" do + Kernel.expects(:fork).yields + @agent.expects(:exit).with(777) + @agent.run_in_fork { + 777 + } + end + end end describe "when splaying" do before do Puppet.settings.stubs(:value).with(:splay).returns true Puppet.settings.stubs(:value).with(:splaylimit).returns "10" end it "should do nothing if splay is disabled" do Puppet.settings.expects(:value).returns false @agent.expects(:sleep).never @agent.splay end it "should do nothing if it has already splayed" do @agent.expects(:splayed?).returns true @agent.expects(:sleep).never @agent.splay end it "should log that it is splaying" do @agent.stubs :sleep Puppet.expects :info @agent.splay end it "should sleep for a random portion of the splaylimit plus 1" do Puppet.settings.expects(:value).with(:splaylimit).returns "50" @agent.expects(:rand).with(51).returns 10 @agent.expects(:sleep).with(10) @agent.splay end it "should mark that it has splayed" do @agent.stubs(:sleep) @agent.splay @agent.should be_splayed end end describe "when checking execution state" do describe 'with regular run status' do before :each do Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(false) Puppet::Application.stubs(:clear?).returns(true) end it 'should be false for :stopping?' do @agent.stopping?.should be_false end it 'should be false for :needing_restart?' do @agent.needing_restart?.should be_false end end describe 'with a stop requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(true) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be true for :stopping?' do @agent.stopping?.should be_true end it 'should be false for :needing_restart?' do @agent.needing_restart?.should be_false end end describe 'with a restart requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(true) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be false for :stopping?' do @agent.stopping?.should be_false end it 'should be true for :needing_restart?' do @agent.needing_restart?.should be_true end end end describe "when starting" do before do @agent.stubs(:observe_signal) end it "should create a timer with the runinterval, a tolerance of 1, and :start? set to true" do Puppet.settings.expects(:value).with(:runinterval).returns 5 timer = stub 'timer', :sound_alarm => nil EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).returns timer @agent.stubs(:run) @agent.start end it "should run once immediately" do timer = mock 'timer' EventLoop::Timer.expects(:new).returns timer timer.expects(:sound_alarm) @agent.start end it "should run within the block passed to the timer" do timer = stub 'timer', :sound_alarm => nil EventLoop::Timer.expects(:new).returns(timer).yields @agent.expects(:run) @agent.start end end end diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index 7c14bb693..e10f10a95 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -1,590 +1,595 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' require 'puppet/application/agent' require 'puppet/network/server' require 'puppet/daemon' describe Puppet::Application::Agent do before :each do @puppetd = Puppet::Application[:agent] @puppetd.stubs(:puts) @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet[:daemonize] = false @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.preinit Puppet::Util::Log.stubs(:newdestination) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) end it "should operate in agent run_mode" do @puppetd.class.run_mode.name.should == :agent end it "should ask Puppet::Application to parse Puppet configuration file" do @puppetd.should_parse_config?.should be_true end it "should declare a main command" do @puppetd.should respond_to(:main) end it "should declare a onetime command" do @puppetd.should respond_to(:onetime) end it "should declare a fingerprint command" do @puppetd.should respond_to(:fingerprint) end it "should declare a preinit block" do @puppetd.should respond_to(:preinit) end describe "in preinit" do it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @puppetd.preinit end it "should init client to true" do @puppetd.preinit @puppetd.options[:client].should be_true end it "should init fqdn to nil" do @puppetd.preinit @puppetd.options[:fqdn].should be_nil end it "should init serve to []" do @puppetd.preinit @puppetd.options[:serve].should == [] end it "should use MD5 as default digest algorithm" do @puppetd.preinit @puppetd.options[:digest].should == :MD5 end it "should not fingerprint by default" do @puppetd.preinit @puppetd.options[:fingerprint].should be_false end end describe "when handling options" do before do @puppetd.command_line.stubs(:args).returns([]) end [:centrallogging, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.options.expects(:[]=).with(option, 'arg') @puppetd.send("handle_#{option}".to_sym, 'arg') end end it "should set client to false with --no-client" do @puppetd.handle_no_client(nil) @puppetd.options[:client].should be_false end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do Puppet[:onetime] = true Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) @puppetd.setup_host end it "should use supplied waitforcert when --onetime is specified" do Puppet[:onetime] = true @puppetd.handle_waitforcert(60) Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) @puppetd.setup_host end it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) @puppetd.setup_host end it "should set the log destination with --logdest" do @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.options.expects(:[]=).with(:setdest,true) @puppetd.handle_logdest("console") end it "should parse the log destination from the command line" do @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @puppetd.parse_options end it "should store the waitforcert options with --waitforcert" do @puppetd.options.expects(:[]=).with(:waitforcert,42) @puppetd.handle_waitforcert("42") end it "should set args[:Port] with --port" do @puppetd.handle_port("42") @puppetd.args[:Port].should == "42" end end describe "during setup" do before :each do @puppetd.options.stubs(:[]) Puppet.stubs(:info) FileTest.stubs(:exists?).returns(true) Puppet[:libdir] = "/dev/null/lib" Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Transaction::Report.indirection.stubs(:cache_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) Puppet.stubs(:settraps) end describe "with --test" do before :each do #Puppet.settings.stubs(:handlearg) @puppetd.options.stubs(:[]=) end it "should call setup_test" do @puppetd.options.stubs(:[]).with(:test).returns(true) @puppetd.expects(:setup_test) @puppetd.setup end it "should set options[:verbose] to true" do @puppetd.options.expects(:[]=).with(:verbose,true) @puppetd.setup_test end it "should set options[:onetime] to true" do Puppet[:onetime] = false @puppetd.setup_test Puppet[:onetime].should == true end it "should set options[:detailed_exitcodes] to true" do @puppetd.options.expects(:[]=).with(:detailed_exitcodes,true) @puppetd.setup_test end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options.stubs(:[]).with(:debug).returns(true) @puppetd.setup_logs Puppet::Util::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @puppetd.options.stubs(:[]).with(:verbose).returns(true) @puppetd.setup_logs Puppet::Util::Log.level.should == :info end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @puppetd.setup_logs end end it "should set syslog as the log destination if no --logdest" do @puppetd.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do Puppet[:configprint] = "pluginsync" Puppet.settings.expects(:print_configs).returns true expect { @puppetd.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet[:modulepath] = '/my/path' Puppet[:configprint] = "modulepath" Puppet::Util::Settings.any_instance.expects(:puts).with('/my/path') expect { @puppetd.setup }.to exit_with 0 end it "should set a central log destination with --centrallogs" do @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) Puppet[:server] = "puppet.reductivelabs.com" Puppet::Util::Log.stubs(:newdestination).with(:syslog) Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") @puppetd.setup end it "should use :main, :puppetd, and :ssl" do Puppet.settings.expects(:use).with(:main, :agent, :ssl) @puppetd.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.setup end it "should install a none ca location in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetd.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) @puppetd.setup end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should change the catalog_terminus setting to 'rest'" do Puppet[:catalog_terminus] = :foo @puppetd.setup Puppet[:catalog_terminus].should == :rest end it "should tell the catalog handler to use cache" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should change the facts_terminus setting to 'facter'" do Puppet[:facts_terminus] = :foo @puppetd.setup Puppet[:facts_terminus].should == :facter end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options.stubs(:[]).with(action).returns(true) @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.options.stubs(:[]).with(action).returns(true) @agent.expects(action) expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end it "should pass the disable message when disabling" do @puppetd.options.stubs(:[]).with(:disable).returns(true) @puppetd.options.stubs(:[]).with(:disable_message).returns("message") @agent.expects(:disable).with("message") expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end it "should pass the default disable message when disabling without a message" do @puppetd.options.stubs(:[]).with(:disable).returns(true) @puppetd.options.stubs(:[]).with(:disable_message).returns(nil) @agent.expects(:disable).with("reason not specified") expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end it "should finally exit" do expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end it "should inform the daemon about our agent if :client is set to 'true'" do @puppetd.options.expects(:[]).with(:client).returns true @daemon.expects(:agent=).with(@agent) @puppetd.setup end it "should not inform the daemon about our agent if :client is set to 'false'" do @puppetd.options[:client] = false @daemon.expects(:agent=).never @puppetd.setup end it "should daemonize if needed" do Puppet.features.stubs(:microsoft_windows?).returns false Puppet[:daemonize] = true @daemon.expects(:daemonize) @puppetd.setup end it "should wait for a certificate" do @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).with(123) @puppetd.setup end it "should not wait for a certificate in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).never @puppetd.setup end it "should setup listen if told to and not onetime" do Puppet[:listen] = true @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.expects(:setup_listen) @puppetd.setup end describe "when setting up listen" do before :each do Puppet[:authconfig] = 'auth' FileTest.stubs(:exists?).with('auth').returns(true) File.stubs(:exist?).returns(true) @puppetd.options.stubs(:[]).with(:serve).returns([]) @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) end it "should exit if no authorization file" do Puppet.stubs(:err) FileTest.stubs(:exists?).with(Puppet[:rest_authconfig]).returns(false) expect { @puppetd.setup_listen }.to exit_with 14 end it "should use puppet default port" do Puppet[:puppetport] = 32768 Puppet::Network::Server.expects(:new).with { |args| args[:port] == 32768 } @puppetd.setup_listen end end describe "when setting up for fingerprint" do before(:each) do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) end it "should not setup as an agent" do @puppetd.expects(:setup_agent).never @puppetd.setup end it "should not create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer).never @puppetd.setup end it "should not daemonize" do @daemon.expects(:daemonize).never @puppetd.setup end it "should setup our certificate host" do @puppetd.expects(:setup_host) @puppetd.setup end end end describe "when running" do before :each do @puppetd.agent = @agent @puppetd.daemon = @daemon @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) end it "should dispatch to fingerprint if --fingerprint is used" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.stubs(:fingerprint) @puppetd.run_command end it "should dispatch to onetime if --onetime is used" do @puppetd.options.stubs(:[]).with(:onetime).returns(true) @puppetd.stubs(:onetime) @puppetd.run_command end it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.stubs(:main) @puppetd.run_command end describe "with --onetime" do before :each do @agent.stubs(:run).returns(:report) @puppetd.options.stubs(:[]).with(:client).returns(:client) @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) Puppet.stubs(:newservice) end it "should exit if no defined --client" do $stderr.stubs(:puts) @puppetd.options.stubs(:[]).with(:client).returns(nil) expect { @puppetd.onetime }.to exit_with 43 end it "should setup traps" do @daemon.expects(:set_signal_traps) expect { @puppetd.onetime }.to exit_with 0 end + it "should not let the agent fork" do + @agent.expects(:should_fork=).with(false) + expect { @puppetd.onetime }.to exit_with 0 + end + it "should let the agent run" do @agent.expects(:run).returns(:report) expect { @puppetd.onetime }.to exit_with 0 end it "should finish by exiting with 0 error code" do expect { @puppetd.onetime }.to exit_with 0 end it "should stop the daemon" do @daemon.expects(:stop).with(:exit => false) expect { @puppetd.onetime }.to exit_with 0 end describe "and --detailed-exitcodes" do before :each do @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(true) end it "should exit with agent computed exit status" do Puppet[:noop] = false @agent.stubs(:run).returns(666) expect { @puppetd.onetime }.to exit_with 666 end it "should exit with the agent's exit status, even if --noop is set." do Puppet[:noop] = true @agent.stubs(:run).returns(666) expect { @puppetd.onetime }.to exit_with 666 end end end describe "with --fingerprint" do before :each do @cert = stub_everything 'cert' @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) @host = stub_everything 'host' @puppetd.stubs(:host).returns(@host) end it "should fingerprint the certificate if it exists" do @host.expects(:certificate).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should fingerprint the certificate request if no certificate have been signed" do @host.expects(:certificate).returns(nil) @host.expects(:certificate_request).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should display the fingerprint" do @host.stubs(:certificate).returns(@cert) @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") @puppetd.expects(:puts).with "DIGEST" @puppetd.fingerprint end end describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) @puppetd.options.stubs(:[]).with(:client) end it "should start our daemon" do @daemon.expects(:start) @puppetd.main end end end end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index fc43d93ad..da7953738 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -1,291 +1,302 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/daemon' +require 'puppet/agent' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end +class TestClient + def lockfile_path + "/dev/null" + end +end + describe Puppet::Daemon do before do + @agent = Puppet::Agent.new(TestClient.new) @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 signals = {:INT => :stop, :TERM => :stop } signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}) unless Puppet.features.microsoft_windows? signals.each do |signal, method| it "should log and call #{method} when it receives #{signal}" do Signal.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.agent = @agent @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 + @agent.expects(:start) + @daemon.agent = @agent + + @daemon.start + end + + it "should start the agent with should_fork at true" do + @agent.expects(:should_fork=).with(true) + @daemon.agent = @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) + @daemon.agent = @agent EventLoop.current.expects(:run) @daemon.start end end describe "when stopping" do before do @daemon.stubs(:remove_pidfile) Puppet::Util::Log.stubs(:close_all) # to make the global safe to mock, set it to a subclass of itself, # then restore it in an after pass without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do # restore from the superclass so we lose the stub garbage without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should stop its server if one is configured" do server = mock 'server' server.expects(:stop) @daemon.stubs(:server).returns server expect { @daemon.stop }.to exit_with 0 end it 'should request a stop from Puppet::Application' do Puppet::Application.expects(:stop!) expect { @daemon.stop }.to exit_with 0 end it "should remove its pidfile" do @daemon.expects(:remove_pidfile) expect { @daemon.stop }.to exit_with 0 end it "should close all logs" do Puppet::Util::Log.expects(:close_all) expect { @daemon.stop }.to exit_with 0 end it "should exit unless called with ':exit => false'" do expect { @daemon.stop }.to exit_with 0 end it "should not exit if called with ':exit => false'" do @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" Puppet::Util.expects(:synchronize_on).with("me",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" Puppet::Util.expects(:synchronize_on).with("me",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 + @agent.expects(:running?).returns true - @daemon.stubs(:agent).returns agent + @daemon.agent = @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 + @agent.expects(:running?).returns false + @agent.expects :run - @daemon.stubs(:agent).returns agent + @daemon.agent = @agent @daemon.reload end end describe "when restarting" do before do without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do without_warnings { Puppet::Application = Puppet::Application.superclass } end it 'should set Puppet::Application.restart!' do Puppet::Application.expects(:restart!) @daemon.stubs(:reexec) @daemon.restart end 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 + @agent.expects(:running?).returns false + @daemon.agent = @agent @daemon.expects(:reexec) @daemon.restart end end describe "when reexecing it self" do before do @daemon.stubs(:exec) @daemon.stubs(:stop) end 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.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