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 | |
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.
-rw-r--r-- | .travis.yml | 10 | ||||
-rwxr-xr-x | bin/chef-zero | 17 | ||||
-rw-r--r-- | gemfiles/gemfile.puma | 7 | ||||
-rw-r--r-- | gemfiles/latest-pedant.gemfile (renamed from gemfiles/gemfile.latest-pedant) | 0 | ||||
-rw-r--r-- | gemfiles/no-pedant.gemfile (renamed from gemfiles/gemfile.no-pedant) | 0 | ||||
-rw-r--r-- | lib/chef_zero/server.rb | 236 |
6 files changed, 142 insertions, 128 deletions
diff --git a/.travis.yml b/.travis.yml index 1c8e391..381daff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,22 +10,20 @@ matrix: gemfile: Gemfile - rvm: 1.8.7 env: SKIP_PEDANT=true - gemfile: gemfiles/gemfile.no-pedant + gemfile: gemfiles/no-pedant.gemfile - rvm: 1.9.3 - gemfile: gemfiles/gemfile.latest-pedant + gemfile: gemfiles/latest-pedant.gemfile - rvm: jruby-19mode gemfile: Gemfile - rvm: jruby-19mode env: SKIP_PEDANT=true - gemfile: gemfiles/gemfile.no-pedant + gemfile: gemfiles/no-pedant.gemfile - rvm: 2.0.0 gemfile: Gemfile - - rvm: 2.0.0 - gemfile: gemfiles/gemfile.puma allow_failures: - rvm: 1.8.7 gemfile: Gemfile - rvm: jruby-19mode gemfile: Gemfile - rvm: 1.9.3 - gemfile: gemfiles/gemfile.latest-pedant + gemfile: gemfiles/latest-pedant.gemfile diff --git a/bin/chef-zero b/bin/chef-zero index aa2dd56..6d6884f 100755 --- a/bin/chef-zero +++ b/bin/chef-zero @@ -1,5 +1,8 @@ #!/usr/bin/env ruby +# Trap interrupts to quit cleanly. +Signal.trap('INT') { exit 1 } + require 'rubygems' $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) @@ -20,10 +23,6 @@ OptionParser.new do |opts| options[:port] = value end - opts.on("--socket PATH", String, "Unix socket path to listen on") do |value| - options[:socket] = value - end - opts.on("--[no-]generate-keys", "Whether to generate actual keys or fake it (faster). Default: false.") do |value| options[:generate_real_keys] = value end @@ -50,12 +49,12 @@ end.parse! server = ChefZero::Server.new(options) if options[:daemon] - unless Process.respond_to?('daemon') - abort 'Process.deamon requires Ruby >= 1.9' - else + if Process.respond_to?(:daemon) Process.daemon(true) - server.start(:publish => true) + server.start(true) + else + abort 'Process.daemon requires Ruby >= 1.9' end else - server.start(:publish => true) + server.start(true) end diff --git a/gemfiles/gemfile.puma b/gemfiles/gemfile.puma deleted file mode 100644 index 07e8d4b..0000000 --- a/gemfiles/gemfile.puma +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org' - -gemspec :path => "../" - -gem 'puma' -gem 'chef-pedant', :github => 'opscode/chef-pedant', :ref => '2c58a3736ba880503394f66c4435b063dececdc5' -gem 'chef', '>= 11.0' diff --git a/gemfiles/gemfile.latest-pedant b/gemfiles/latest-pedant.gemfile index 5b6c0cb..5b6c0cb 100644 --- a/gemfiles/gemfile.latest-pedant +++ b/gemfiles/latest-pedant.gemfile diff --git a/gemfiles/gemfile.no-pedant b/gemfiles/no-pedant.gemfile index 7784f18..7784f18 100644 --- a/gemfiles/gemfile.no-pedant +++ b/gemfiles/no-pedant.gemfile 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 |