diff options
author | Seth Vargo <sethvargo@gmail.com> | 2013-12-15 18:24:21 -0500 |
---|---|---|
committer | Seth Vargo <sethvargo@gmail.com> | 2013-12-17 12:17:04 -0500 |
commit | 0aaabe6b325fee332422bd03c94eab3eacefbc60 (patch) | |
tree | 0bdb656722c1b92bb89d38732372d7a3cc7d74a2 /lib/chef_zero/server.rb | |
parent | 0d5e08883fa9608c328cf57c12a77b492ddf8500 (diff) | |
download | chef-zero-0aaabe6b325fee332422bd03c94eab3eacefbc60.tar.gz |
Remove puma and clean up threading
This commit removes all instances of the Puma webserver, since it has known
issues on a number of supported platforms and adds significant branching
logic to the code.
This commit also re-defines what it means when the server is "running". In
the past, "running" has meant the web server is up. Testing has revealed that
web servers actually lie and say they are running, even if they are not
accepting requests. The new `running?` method uses OpenURI to access a known
URL on the server.
WEBrick does not support running on a socket, so the `--socket` option has
also been removed.
Diffstat (limited to 'lib/chef_zero/server.rb')
-rw-r--r-- | lib/chef_zero/server.rb | 236 |
1 files changed, 130 insertions, 106 deletions
diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb index b5c4bbf..417bd8a 100644 --- a/lib/chef_zero/server.rb +++ b/lib/chef_zero/server.rb @@ -17,10 +17,13 @@ # require 'openssl' +require 'open-uri' require 'rubygems' require 'timeout' require 'stringio' -require 'webrick/version' + +require 'rack' +require 'webrick' require 'chef_zero' require 'chef_zero/cookbook_data' @@ -61,138 +64,155 @@ module ChefZero DEFAULT_OPTIONS = { :host => '127.0.0.1', :port => 8889, - :socket => nil, :log_level => :info, :generate_real_keys => true }.freeze def initialize(options = {}) - options = DEFAULT_OPTIONS.merge(options) - @options = options - uri_safe_host = options[:host].include?(":") ? "[#{options[:host]}]" : options[:host] - @url = "http://#{uri_safe_host}:#{options[:port]}" - @generate_real_keys = options[:generate_real_keys] - - ChefZero::Log.level = options[:log_level].to_sym - - begin - require 'puma' - @server = Puma::Server.new(make_app, Puma::Events.new(STDERR, STDOUT)) - if options[:socket] - @server.add_unix_listener(options[:socket]) - else - @server.add_tcp_listener(options[:host], options[:port]) - end - @server_type = :puma - rescue LoadError - require 'rack' - @server_type = :webrick - end + @options = DEFAULT_OPTIONS.merge(options) + @options[:host] = "[#{@options[:host]}]" if @options[:host].include?(':') + @options.freeze - @data_store = options[:data_store] || DataStore::MemoryStore.new + ChefZero::Log.level = @options[:log_level].to_sym end + # @return [Hash] attr_reader :options + + # @return [WEBrick::HTTPServer] attr_reader :server - attr_reader :data_store - attr_reader :url include ChefZero::Endpoints - def start(start_options = {}) - if start_options[:publish] - puts ">> Starting Chef Zero (v#{ChefZero::VERSION})..." - case @server_type - when :puma - puts ">> Puma (v#{Puma::Const::PUMA_VERSION}) is listening at #{url}" - when :webrick - puts ">> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url}" - end - puts ">> Press CTRL+C to stop" + # + # The URL for this Chef Zero server. + # + # @return [String] + # + def url + "http://#{@options[:host]}:#{@options[:port]}" + end + + # + # The data store for this server (default is in-memory). + # + # @return [~ChefZero::DataStore] + # + def data_store + @data_store ||= @options[:data_store] || DataStore::MemoryStore.new + end + + # + # Boolean method to determine if real Public/Private keys should be + # generated. + # + # @return [Boolean] + # true if real keys should be created, false otherwise + # + def generate_real_keys? + !!@options[:generate_real_keys] + end + + # + # Start a Chef Zero server in the current thread. You can stop this server + # by canceling the current thread. + # + # @param [Boolean] publish + # publish the server information to STDOUT + # + # @return [nil] + # this method will block the main thread until interrupted + # + def start(publish = true) + publish = publish[:publish] if publish.is_a?(Hash) # Legacy API + + if publish + puts <<-EOH.gsub(/^ {10}/, '') + >> Starting Chef Zero (v#{ChefZero::VERSION})... + >> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url} + >> Press CTRL+C to stop + + EOH end - begin - case @server_type - when :puma - server.run.join - when :webrick - Rack::Handler::WEBrick.run( - make_app, - :BindAddress => @options[:host], - :Port => @options[:port], - :AccessLog => [], - :Logger => WEBrick::Log::new(StringIO.new, 7) - ) do |server| - @server = server - end - end - rescue Object, Interrupt - if running? - puts "\n>> Stopping Chef Zero ..." - case @server_type - when :puma - server.stop(true) - when :webrick - server.shutdown - end - end - ensure - case @server_type - when :webrick - @server = nil - else + thread = start_background + + %w[INT TERM].each do |signal| + Signal.trap(signal) do + puts "\n>> Stopping Chef Zero..." + @server.shutdown end end - end - def start_background(wait = 5) - @thread = Thread.new { - begin - start - rescue - @server_error = $! - ChefZero::Log.error("#{$!.message}\n#{$!.backtrace.join("\n")}") - end - } + # Move the background process to the main thread + thread.join + end - # Wait x seconds to make sure the server actually started - Timeout::timeout(wait) { - sleep(0.01) until running? || @server_error - raise @server_error if @server_error - } - # Give the user the thread, just in case they want it + # + # Start a Chef Zero server in a forked process. This method returns the PID + # to the forked process. + # + # @param [Fixnum] wait + # the number of seconds to wait for the server to start + # + # @return [Thread] + # the thread the background process is running in + # + def start_background(wait = 5) + @server = WEBrick::HTTPServer.new( + :BindAddress => @options[:host], + :Port => @options[:port], + :AccessLog => [], + :Logger => WEBrick::Log.new(StringIO.new, 7) + ) + @server.mount('/', Rack::Handler::WEBrick, app) + + @thread = Thread.new { @server.start } + @thread.abort_on_exception = true @thread end + # + # Boolean method to determine if the server is currently ready to accept + # requests. This method will attempt to make an HTTP request against the + # server. If this method returns true, you are safe to make a request. + # + # @return [Boolean] + # true if the server is accepting requests, false otherwise + # def running? - case @server_type - when :puma - !!server.running - when :webrick - !!(server && server.status == :Running) + if @server.nil? || @server.status != :Running + return false end + + uri = URI.join(url, 'cookbooks') + headers = { 'Accept' => 'application/json' } + + Timeout.timeout(0.1) { !open(uri, headers).nil? } + rescue SocketError, Errno::ECONNREFUSED, Timeout::Error + false end + # + # Gracefully stop the Chef Zero server. + # + # @param [Fixnum] wait + # the number of seconds to wait before raising force-terminating the + # server + # def stop(wait = 5) - case @server_type - when :puma - server.stop(true) - when :webrick - server.shutdown - @server + Timeout.timeout(wait) do + @server.shutdown + @thread.join(wait) if @thread end + rescue Timeout::Error if @thread - begin - @thread.join(wait) if @thread - rescue - if @thread - ChefZero::Log.error "Server did not stop within #{wait} seconds. Killing..." - @thread.kill - end - end + ChefZero::Log.error("Chef Zero did not stop within #{wait} seconds! Killing...") + @thread.kill end ensure + @server = nil @thread = nil end @@ -283,9 +303,17 @@ module ChefZero @request_handler = block end + def to_s + "#<#{self.class} #{url}>" + end + + def inspect + "#<#{self.class} @url=#{url.inspect}>" + end + private - def make_app + def app router = RestRouter.new([ [ '/authenticate_user', AuthenticateUserEndpoint.new(self) ], [ '/clients', ActorsEndpoint.new(self) ], @@ -365,9 +393,5 @@ module ChefZero end value end - - def generate_real_keys? - !!@generate_real_keys - end end end |