diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 22630ffb8..a6945c840 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,130 +1,129 @@ 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? + Puppet::Util::Pidlock.new(pidfile).unlock 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 EventLoop.current.run end end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index e4de07dea..e83259caa 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,165 +1,164 @@ require 'puppet/network/http' require 'puppet/util/pidlock' class Puppet::Network::Server attr_reader :server_type, :protocols, :address, :port # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end # 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::Util.secure_open("/tmp/daemonout", "w") { |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" } raise "Could not start #{Puppet[:name]}: #{detail}" 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 # 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? + Puppet::Util::Pidlock.new(pidfile).unlock end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def initialize(args = {}) valid_args = [:handlers, :xmlrpc_handlers, :port] bad_args = args.keys.find_all { |p| ! valid_args.include?(p) }.collect { |p| p.to_s }.join(",") raise ArgumentError, "Invalid argument(s) #{bad_args}" unless bad_args == "" @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc. http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]") @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") @address = determine_bind_address @protocols = [ :rest, :xmlrpc ] @listening = false @routes = {} @xmlrpc_routes = {} self.register(args[:handlers]) if args[:handlers] self.register_xmlrpc(args[:xmlrpc_handlers]) if args[:xmlrpc_handlers] # Make sure we have all of the directories we need to function. Puppet.settings.use(:main, :ssl, Puppet[:name]) end # Register handlers for REST networking, based on the Indirector. def register(*indirections) raise ArgumentError, "Indirection names are required." if indirections.empty? indirections.flatten.each do |name| Puppet::Indirector::Indirection.model(name) || raise(ArgumentError, "Cannot locate indirection '#{name}'.") @routes[name.to_sym] = true end end # Unregister Indirector handlers. def unregister(*indirections) raise "Cannot unregister indirections while server is listening." if listening? indirections = @routes.keys if indirections.empty? indirections.flatten.each do |i| raise(ArgumentError, "Indirection [#{i}] is unknown.") unless @routes[i.to_sym] end indirections.flatten.each do |i| @routes.delete(i.to_sym) end end # Register xmlrpc handlers for backward compatibility. def register_xmlrpc(*namespaces) raise ArgumentError, "XMLRPC namespaces are required." if namespaces.empty? namespaces.flatten.each do |name| Puppet::Network::Handler.handler(name) || raise(ArgumentError, "Cannot locate XMLRPC handler for namespace '#{name}'.") @xmlrpc_routes[name.to_sym] = true end end # Unregister xmlrpc handlers. def unregister_xmlrpc(*namespaces) raise "Cannot unregister xmlrpc handlers while server is listening." if listening? namespaces = @xmlrpc_routes.keys if namespaces.empty? namespaces.flatten.each do |i| raise(ArgumentError, "XMLRPC handler '#{i}' is unknown.") unless @xmlrpc_routes[i.to_sym] end namespaces.flatten.each do |i| @xmlrpc_routes.delete(i.to_sym) end end def listening? @listening end def listen raise "Cannot listen -- already listening." if listening? @listening = true http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :xmlrpc_handlers => @xmlrpc_routes.keys, :protocols => protocols) end def unlisten raise "Cannot unlisten -- not currently listening." unless listening? http_server.unlisten @listening = false end def http_server_class http_server_class_by_type(@server_type) end def start create_pidfile listen end def stop unlisten remove_pidfile end private def http_server @http_server ||= http_server_class.new end def http_server_class_by_type(kind) Puppet::Network::HTTP.server_class_by_type(kind) end def determine_bind_address tmp = Puppet[:bindaddress] return tmp if tmp != "" server_type.to_s == "webrick" ? "0.0.0.0" : "127.0.0.1" end end diff --git a/lib/puppet/util/pidlock.rb b/lib/puppet/util/pidlock.rb index 9ed86352d..9eb1bd26c 100644 --- a/lib/puppet/util/pidlock.rb +++ b/lib/puppet/util/pidlock.rb @@ -1,57 +1,68 @@ require 'fileutils' require 'puppet/util/anonymous_filelock' class Puppet::Util::Pidlock < Puppet::Util::AnonymousFilelock def locked? clear_if_stale File.exists? @lockfile end def mine? Process.pid == lock_pid end def anonymous? false end def lock return mine? if locked? File.open(@lockfile, "w") { |fd| fd.write(Process.pid) } true end def unlock(opts = {}) if mine? - File.unlink(@lockfile) + begin + File.unlink(@lockfile) + rescue Errno::ENOENT + # Someone deleted it for us ...and so we do nothing. No point whining + # about a problem that the user can't actually do anything about. + rescue SystemCallError => e + # This one is a real failure though. No idea what went wrong, but it + # is most likely "read only file(system)" or wrong permissions or + # something like that. + Puppet.err "Could not remove PID file #{@lockfile}: #{e}" + puts e.backtrace if Puppet[:trace] + end true else false end end def lock_pid if File.exists? @lockfile File.read(@lockfile).to_i else nil end end private def clear_if_stale return if lock_pid.nil? errors = [Errno::ESRCH] # Process::Error can only happen, and is only defined, on Windows errors << Process::Error if defined? Process::Error begin Process.kill(0, lock_pid) rescue *errors File.unlink(@lockfile) end end end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index fc43d93ad..9eedef77c 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -1,291 +1,275 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/daemon' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end 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 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.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) 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 + pidfile = mock 'pidfile', :unlock => false - Puppet.settings.stubs(:value).with(:name).returns "eh" - Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + Puppet[:pidfile] = "/my/file" + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile - 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 + pidfile = mock 'pidfile', :unlock => true - it "should warn if it cannot remove the pidfile" do - pidfile = mock 'pidfile', :locked? => true + Puppet[:pidfile] = "/my/file" 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 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 @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 diff --git a/spec/unit/network/server_spec.rb b/spec/unit/network/server_spec.rb index b38e82b93..80cf18cd8 100755 --- a/spec/unit/network/server_spec.rb +++ b/spec/unit/network/server_spec.rb @@ -1,523 +1,505 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' require 'puppet/network/handler' describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).with(:name).returns("me") Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns(8140) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) end describe "when initializing" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns('') end it 'should fail if an unknown option is provided' do lambda { Puppet::Network::Server.new(:foo => 31337) }.should raise_error(ArgumentError) end it "should allow specifying a listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') @server = Puppet::Network::Server.new(:port => 31337) @server.port.should == 31337 end it "should use the :bindaddress setting to determine the default listening address" do Puppet.settings.stubs(:value).with(:masterport).returns('') Puppet.settings.expects(:value).with(:bindaddress).returns("10.0.0.1") @server = Puppet::Network::Server.new @server.address.should == "10.0.0.1" end it "should set the bind address to '127.0.0.1' if the default address is an empty string and the server type is mongrel" do Puppet.settings.stubs(:value).with(:servertype).returns("mongrel") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '127.0.0.1' end it "should set the bind address to '0.0.0.0' if the default address is an empty string and the server type is webrick" do Puppet.settings.stubs(:value).with(:servertype).returns("webrick") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '0.0.0.0' end it "should use the Puppet configurator to find a default listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') Puppet.settings.expects(:value).with(:masterport).returns(6667) @server = Puppet::Network::Server.new @server.port.should == 6667 end it "should fail to initialize if no listening port can be found" do Puppet.settings.stubs(:value).with(:bindaddress).returns("127.0.0.1") Puppet.settings.stubs(:value).with(:masterport).returns(nil) lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) end it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do Puppet.settings.expects(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) @server.server_type.should == :suparserver end it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do Puppet.settings.expects(:value).with(:servertype).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error end it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class) @server = Puppet::Network::Server.new(:port => 31337) end it "should fail if the HTTP server class is unknown" do Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error(ArgumentError) end it "should allow registering REST handlers" do @server = Puppet::Network::Server.new(:port => 31337, :handlers => [ :foo, :bar, :baz]) lambda { @server.unregister(:foo, :bar, :baz) }.should_not raise_error end it "should allow registering XMLRPC handlers" do @server = Puppet::Network::Server.new(:port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) lambda { @server.unregister_xmlrpc(:foo, :bar, :baz) }.should_not raise_error end it "should not be listening after initialization" do Puppet::Network::Server.new(:port => 31337).should_not be_listening end it "should use the :main setting section" do Puppet.settings.expects(:use).with { |*args| args.include?(:main) } @server = Puppet::Network::Server.new(:port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) end it "should use the Puppet[:name] setting section" do Puppet.settings.expects(:value).with(:name).returns "me" Puppet.settings.expects(:use).with { |*args| args.include?("me") } @server = Puppet::Network::Server.new(:port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) end end # We don't test the method, because it's too much of a Unix-y pain. it "should be able to daemonize" do @server.should respond_to(:daemonize) end describe "when being started" do before do @server.stubs(:listen) @server.stubs(:create_pidfile) end it "should listen" do @server.expects(:listen) @server.start end it "should create its PID file" do @server.expects(:create_pidfile) @server.start end end describe "when being stopped" do before do @server.stubs(:unlisten) @server.stubs(:remove_pidfile) end it "should unlisten" do @server.expects(:unlisten) @server.stop end it "should remove its PID file" do @server.expects(:remove_pidfile) @server.stop 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) @server.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 @server.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 { @server.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) @server.remove_pidfile end it "should do nothing if the pidfile is not present" do - pidfile = mock 'pidfile', :locked? => false + pidfile = mock 'pidfile', :unlock => 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 @server.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" - - @server.remove_pidfile - end - - it "should warn if it cannot remove the pidfile" do - pidfile = mock 'pidfile', :locked? => true + pidfile = mock 'pidfile', :unlock => 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 @server.remove_pidfile end end describe "when managing indirection registrations" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') end it "should allow registering an indirection for client access by specifying its indirection name" do lambda { @server.register(:foo) }.should_not raise_error end it "should require that the indirection be valid" do Puppet::Indirector::Indirection.expects(:model).with(:foo).returns nil lambda { @server.register(:foo) }.should raise_error(ArgumentError) end it "should require at least one indirection name when registering indirections for client access" do lambda { @server.register }.should raise_error(ArgumentError) end it "should allow for numerous indirections to be registered at once for client access" do lambda { @server.register(:foo, :bar, :baz) }.should_not raise_error end it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do @server.register(:foo) lambda { @server.unregister(:foo) }.should_not raise_error end it "should leave other indirections accessible to clients when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:bar)}.should_not raise_error end it "should allow specifying numerous indirections which are to be no longer accessible to clients" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not turn off any indirections if given unknown indirection names to turn off" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not allow turning off unknown indirection names" do @server.register(:foo, :bar) lambda { @server.unregister(:baz) }.should raise_error(ArgumentError) end it "should disable client access immediately when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:foo) }.should raise_error(ArgumentError) end it "should allow turning off all indirections at once" do @server.register(:foo, :bar) @server.unregister [ :foo, :bar, :baz].each do |indirection| lambda { @server.unregister(indirection) }.should raise_error(ArgumentError) end end end it "should provide a means of determining whether it is listening" do @server.should respond_to(:listening?) end it "should provide a means of determining which HTTP server will be used to provide access to clients" do @server.server_type.should == :suparserver end it "should provide a means of determining which protocols are in use" do @server.should respond_to(:protocols) end it "should set the protocols to :rest and :xmlrpc" do @server.protocols.should == [ :rest, :xmlrpc ] end it "should provide a means of determining the listening address" do @server.address.should == "127.0.0.1" end it "should provide a means of determining the listening port" do @server.port.should == 31337 end it "should allow for multiple configurations, each handling different indirections" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server2 = Puppet::Network::Server.new(:port => 31337) @server.register(:foo, :bar) @server2.register(:foo, :xyzzy) @server.unregister(:foo, :bar) @server2.unregister(:foo, :xyzzy) lambda { @server.unregister(:xyzzy) }.should raise_error(ArgumentError) lambda { @server2.unregister(:bar) }.should raise_error(ArgumentError) end describe "when managing xmlrpc registrations" do before do Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') end it "should allow registering an xmlrpc handler by specifying its namespace" do lambda { @server.register_xmlrpc(:foo) }.should_not raise_error end it "should require that the xmlrpc namespace be valid" do Puppet::Network::Handler.stubs(:handler).returns nil lambda { @server.register_xmlrpc(:foo) }.should raise_error(ArgumentError) end it "should require at least one namespace" do lambda { @server.register_xmlrpc }.should raise_error(ArgumentError) end it "should allow multiple namespaces to be registered at once" do lambda { @server.register_xmlrpc(:foo, :bar) }.should_not raise_error end it "should allow the use of namespaces to specify which are no longer accessible to clients" do @server.register_xmlrpc(:foo, :bar) end it "should leave other namespaces accessible to clients when turning off xmlrpc namespaces" do @server.register_xmlrpc(:foo, :bar) @server.unregister_xmlrpc(:foo) lambda { @server.unregister_xmlrpc(:bar)}.should_not raise_error end it "should allow specifying numerous namespaces which are to be no longer accessible to clients" do @server.register_xmlrpc(:foo, :bar) lambda { @server.unregister_xmlrpc(:foo, :bar) }.should_not raise_error end it "should not turn off any indirections if given unknown namespaces to turn off" do @server.register_xmlrpc(:foo, :bar) lambda { @server.unregister_xmlrpc(:foo, :bar, :baz) }.should raise_error(ArgumentError) lambda { @server.unregister_xmlrpc(:foo, :bar) }.should_not raise_error end it "should not allow turning off unknown namespaces" do @server.register_xmlrpc(:foo, :bar) lambda { @server.unregister_xmlrpc(:baz) }.should raise_error(ArgumentError) end it "should disable client access immediately when turning off namespaces" do @server.register_xmlrpc(:foo, :bar) @server.unregister_xmlrpc(:foo) lambda { @server.unregister_xmlrpc(:foo) }.should raise_error(ArgumentError) end it "should allow turning off all namespaces at once" do @server.register_xmlrpc(:foo, :bar) @server.unregister_xmlrpc [ :foo, :bar, :baz].each do |indirection| lambda { @server.unregister_xmlrpc(indirection) }.should raise_error(ArgumentError) end end end describe "when listening is off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) end it "should indicate that it is not listening" do @server.should_not be_listening end it "should not allow listening to be turned off" do lambda { @server.unlisten }.should raise_error(RuntimeError) end it "should allow listening to be turned on" do lambda { @server.listen }.should_not raise_error end end describe "when listening is on" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @mock_http_server.stubs(:unlisten) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should indicate that it is listening" do @server.should be_listening end it "should not allow listening to be turned on" do lambda { @server.listen }.should raise_error(RuntimeError) end it "should allow listening to be turned off" do lambda { @server.unlisten }.should_not raise_error end end describe "when listening is being turned on" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') @server = Puppet::Network::Server.new(:port => 31337, :handlers => [:node], :xmlrpc_handlers => [:master]) @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) end it "should fetch an instance of an HTTP server" do @server.stubs(:http_server_class).returns(@mock_http_server_class) @mock_http_server_class.expects(:new).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to listen" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen) @server.listen end it "should pass the listening address to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:address] == '127.0.0.1' end @server.listen end it "should pass the listening port to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:port] == 31337 end @server.listen end it "should pass a list of REST handlers to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:handlers] == [ :node ] end @server.listen end it "should pass a list of XMLRPC handlers to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:xmlrpc_handlers] == [ :master ] end @server.listen end it "should pass a list of protocols to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:protocols] == [ :rest, :xmlrpc ] end @server.listen end end describe "when listening is being turned off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to stop listening" do @mock_http_server.expects(:unlisten) @server.unlisten end it "should not allow for indirections to be turned off" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server.register(:foo) lambda { @server.unregister(:foo) }.should raise_error(RuntimeError) end end end diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb old mode 100644 new mode 100755 index 37f3ca4f4..962745bd4 --- a/spec/unit/util/pidlock_spec.rb +++ b/spec/unit/util/pidlock_spec.rb @@ -1,182 +1,208 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/pidlock' describe Puppet::Util::Pidlock do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Pidlock.new(@lockfile) end it "should not be anonymous" do @lock.should_not be_anonymous end describe "#lock" do it "should not be locked at start" do @lock.should_not be_locked end it "should not be mine at start" do @lock.should_not be_mine end it "should become locked" do @lock.lock @lock.should be_locked end it "should become mine" do @lock.lock @lock.should be_mine end it "should be possible to lock multiple times" do @lock.lock lambda { @lock.lock }.should_not raise_error end it "should return true when locking" do @lock.lock.should be_true end it "should return true if locked by me" do @lock.lock @lock.lock.should be_true end it "should return false if locked by someone else" do Process.stubs(:kill) File.open(@lockfile, "w") { |fd| fd.print('0') } @lock.lock.should be_false end it "should create a lock file" do @lock.lock File.should be_exists(@lockfile) end it "should create a lock file containing our pid" do @lock.lock File.read(@lockfile).to_i.should == Process.pid.to_i end end describe "#unlock" do it "should not be locked anymore" do @lock.lock @lock.unlock @lock.should_not be_locked end it "should return false if not locked" do @lock.unlock.should be_false end it "should return true if properly unlocked" do @lock.lock @lock.unlock.should be_true end it "should get rid of the lock file" do @lock.lock @lock.unlock File.should_not be_exists(@lockfile) end + + it "should not warn if the lockfile was deleted by someone else" do + @lock.lock + File.unlink(@lockfile) + + Puppet.expects(:err).never # meh + @lock.unlock + end + + it "should warn if the lockfile can't be deleted" do + @lock.lock + File.expects(:unlink).with(@lockfile).raises(Errno::EIO) + Puppet.expects(:err).with do |argument| + argument.should =~ /Input\/output error/ + end + @lock.unlock + + # This is necessary because our cleanup code uses File.unlink + File.unstub(:unlink) + @lock.unlock + end end describe "#locked?" do it "should return true if locked" do @lock.lock @lock.should be_locked end end describe "with a stale lock" do before(:each) do Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234).raises(Errno::ESRCH) Process.stubs(:pid).returns(6789) File.open(@lockfile, 'w') { |fd| fd.write("1234") } end it "should not be locked" do @lock.should_not be_locked end describe "#lock" do it "should clear stale locks" do @lock.locked? File.should_not be_exists(@lockfile) end it "should replace with new locks" do @lock.lock File.should be_exists(@lockfile) @lock.lock_pid.should == 6789 @lock.should be_mine @lock.should be_locked end end describe "#unlock" do it "should not be allowed" do @lock.unlock.should be_false end it "should not remove the lock file" do @lock.unlock File.should be_exists(@lockfile) end end end describe "with another process lock" do before(:each) do Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234) Process.stubs(:pid).returns(6789) File.open(@lockfile, 'w') { |fd| fd.write("1234") } end it "should be locked" do @lock.should be_locked end it "should not be mine" do @lock.should_not be_mine end describe "#lock" do it "should not be possible" do @lock.lock.should be_false end it "should not overwrite the lock" do @lock.lock @lock.should_not be_mine end end describe "#unlock" do it "should not be possible" do @lock.unlock.should be_false end it "should not remove the lock file" do @lock.unlock File.should be_exists(@lockfile) end it "should still not be our lock" do @lock.unlock @lock.should_not be_mine end + + it "should not warn" do + Puppet.expects(:err).never + @lock.unlock + end end end -end \ No newline at end of file +end