diff options
-rw-r--r-- | lib/puppet/network/server.rb | 56 | ||||
-rw-r--r-- | spec/unit/network/server.rb | 496 |
2 files changed, 357 insertions, 195 deletions
diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index 6ea2943ea..de32db02f 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,8 +1,59 @@ 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 + File.open("/tmp/daemonout", "w") { |f| + f.puts "Could not start %s: %s" % [Puppet[:name], detail] + } + raise "Could not start %s: %s" % [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.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 + + # 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] + end + end + end + + # Provide the path to our pidfile. + def pidfile + Puppet[:pidfile] + end + def initialize(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}]") @@ -16,6 +67,9 @@ class Puppet::Network::Server @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. @@ -85,11 +139,13 @@ class Puppet::Network::Server end def start + create_pidfile listen end def stop unlisten + remove_pidfile end private diff --git a/spec/unit/network/server.rb b/spec/unit/network/server.rb index 941474bc5..5332964c6 100644 --- a/spec/unit/network/server.rb +++ b/spec/unit/network/server.rb @@ -6,106 +6,229 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/server' -describe Puppet::Network::Server, "when initializing" do +describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:name).returns("me") + Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - - Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') - Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) end - it "should allow specifying a listening address" do - Puppet.stubs(:[]).with(:masterport).returns('') - @server = Puppet::Network::Server.new(:address => "127.0.0.1") - @server.address.should == "127.0.0.1" - end + describe "when initializing" do + before do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') + Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + end - it "should allow specifying a listening port" do - Puppet.stubs(:[]).with(:bindaddress).returns('') - @server = Puppet::Network::Server.new(:port => 31337) - @server.port.should == 31337 - end + it "should allow specifying a listening address" do + Puppet.settings.stubs(:value).with(:masterport).returns('') + @server = Puppet::Network::Server.new(:address => "127.0.0.1") + @server.address.should == "127.0.0.1" + end - it "should use the Puppet configurator to find a default listening address" do - Puppet.stubs(:[]).with(:masterport).returns('') - Puppet.expects(:[]).with(:bindaddress).returns("10.0.0.1") - @server = Puppet::Network::Server.new - @server.address.should == "10.0.0.1" - 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 Puppet configurator to find a default listening port" do - Puppet.stubs(:[]).with(:bindaddress).returns('') - Puppet.expects(:[]).with(:masterport).returns(6667) - @server = Puppet::Network::Server.new - @server.port.should == 6667 - end + it "should use the Puppet configurator to find a 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 fail to initialize if no listening address can be found" do - Puppet.stubs(:[]).with(:masterport).returns(6667) - Puppet.stubs(:[]).with(:bindaddress).returns(nil) - lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) - 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.stubs(:[]).with(:bindaddress).returns("127.0.0.1") - Puppet.stubs(:[]).with(:masterport).returns(nil) - lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) - end + it "should fail to initialize if no listening address can be found" do + Puppet.settings.stubs(:value).with(:masterport).returns(6667) + Puppet.settings.stubs(:value).with(:bindaddress).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.expects(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @server.server_type.should == :suparserver - 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 fail to initialize if there is no HTTP server known to the Puppet configurator" do - Puppet.expects(:[]).with(:servertype).returns(nil) - lambda { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error - 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(:address => "127.0.0.1", :port => 31337) + @server.server_type.should == :suparserver + 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(:address => "127.0.0.1", :port => 31337) - 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(:address => "127.0.0.1", :port => 31337) }.should raise_error + 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(:address => "127.0.0.1", :port => 31337) }.should raise_error(ArgumentError) - 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(:address => "127.0.0.1", :port => 31337) + end - it "should allow registering REST handlers" do - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [ :foo, :bar, :baz]) - lambda { @server.unregister(:foo, :bar, :baz) }.should_not raise_error + 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(:address => "127.0.0.1", :port => 31337) }.should raise_error(ArgumentError) + end + + it "should allow registering REST handlers" do + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :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(:address => "127.0.0.1", :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(:address => "127.0.0.1", :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(:address => "127.0.0.1", :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(:address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + end end - it "should allow registering XMLRPC handlers" do - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) - lambda { @server.unregister_xmlrpc(:foo, :bar, :baz) }.should_not raise_error + # 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 - it "should not be listening after initialization" do - Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337).should_not be_listening + 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 -end -describe Puppet::Network::Server do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) + 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 - it "should listen when started" do - @server.expects(:listen) - @server.start + 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) + @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 - it "should unlisten when stopped" do - @server.expects(:unlisten) - @server.stop + 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) + @server.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 + @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 + 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 @@ -271,156 +394,139 @@ describe Puppet::Network::Server do end end end -end -describe Puppet::Network::Server, "when listening is off" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - @server.stubs(:http_server).returns(@mock_http_server) - 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 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 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 - 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 -describe Puppet::Network::Server, "when listening is on" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @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 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 not allow listening to be turned on" do - lambda { @server.listen }.should raise_error(RuntimeError) + it "should allow listening to be turned off" do + lambda { @server.unlisten }.should_not raise_error + end 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') -describe Puppet::Network::Server, "when listening is being turned on" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [:node], :xmlrpc_handlers => [:master]) + @mock_http_server = mock('http server') + @mock_http_server.stubs(:listen) + end - Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') - Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + 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 - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [:node], :xmlrpc_handlers => [:master]) - @mock_http_server = mock('http server') - @mock_http_server.stubs(: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 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 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 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 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 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 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 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 + 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 - @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 ] + 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 - @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| - p args - args[:xmlrpc_handlers] == [ :master ] + 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 - @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 ] + it "should cause the HTTP server to stop listening" do + @mock_http_server.expects(:unlisten) + @server.unlisten end - @server.listen - end -end -describe Puppet::Network::Server, "when listening is being turned off" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - @server.stubs(:http_server).returns(@mock_http_server) - @server.listen - end + it "should not allow for indirections to be turned off" do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') - it "should cause the HTTP server to stop listening" do - @mock_http_server.expects(:unlisten) - @server.unlisten + @server.register(:foo) + lambda { @server.unregister(:foo) }.should raise_error(RuntimeError) + end 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) + describe Class.new, "put these somewhere" do + it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object" + it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save" + it "should prepend a rest version number on the path (w00t)" + it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)" end end - - -describe Class.new, "put these somewhere" do - it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object" - it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save" - it "should prepend a rest version number on the path (w00t)" - it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)" -end |