summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeth Vargo <sethvargo@gmail.com>2013-12-15 18:24:21 -0500
committerSeth Vargo <sethvargo@gmail.com>2013-12-17 12:17:04 -0500
commit0aaabe6b325fee332422bd03c94eab3eacefbc60 (patch)
tree0bdb656722c1b92bb89d38732372d7a3cc7d74a2
parent0d5e08883fa9608c328cf57c12a77b492ddf8500 (diff)
downloadchef-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.yml10
-rwxr-xr-xbin/chef-zero17
-rw-r--r--gemfiles/gemfile.puma7
-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.rb236
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