Project

General

Profile

« Previous | Next » 

Revision 335a1610

Added by Lukas Zapletal over 8 years ago

Fixes #11323 - fixed PID writing, interrupt trap and daemon logging

View differences:

config/settings.yml.example
:virsh_network: default
# Log configuration
# Uncomment and modify if you want to change the location of the log file or use STDOUT
# Uncomment and modify if you want to change the location of the log file or use STDOUT or SYSLOG values
#:log_file: /var/log/foreman-proxy/proxy.log
# Uncomment and modify if you want to change the log level
# WARN, DEBUG, ERROR, FATAL, INFO, UNKNOWN
lib/launcher.rb
require 'proxy/log'
require 'proxy/settings'
module Proxy
class Launcher
include ::Proxy::Log
def pid_path
SETTINGS.daemon_pid
end
def https_enabled?
SETTINGS.ssl_private_key && SETTINGS.ssl_certificate && SETTINGS.ssl_ca_file
end
def http_app
return nil if SETTINGS.http_port.nil?
app = Rack::Builder.new do
::Proxy::Plugins.enabled_plugins.each do |p|
instance_eval(p.http_rackup)
end
end
Rack::Server.new(
:app => app,
:server => :webrick,
:Host => SETTINGS.bind_host,
:Port => SETTINGS.http_port,
:daemonize => false)
end
def https_app
unless https_enabled?
logger.warn "Missing SSL setup, https is disabled."
nil
else
app = Rack::Builder.new do
::Proxy::Plugins.enabled_plugins.each {|p| instance_eval(p.https_rackup)}
end
Rack::Server.new(
:app => app,
:server => :webrick,
:Host => SETTINGS.bind_host,
:Port => SETTINGS.https_port,
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER,
:SSLPrivateKey => load_ssl_private_key(SETTINGS.ssl_private_key),
:SSLCertificate => load_ssl_certificate(SETTINGS.ssl_certificate),
:SSLCACertificateFile => SETTINGS.ssl_ca_file,
:daemonize => false)
end
end
def load_ssl_private_key(path)
OpenSSL::PKey::RSA.new(File.read(path))
rescue Exception => e
logger.error "Unable to load private SSL key. Are the values correct in settings.yml and do permissions allow reading?: #{e}"
raise e
end
def load_ssl_certificate(path)
OpenSSL::X509::Certificate.new(File.read(path))
rescue Exception => e
logger.error "Unable to load SSL certificate. Are the values correct in settings.yml and do permissions allow reading?: #{e}"
raise e
end
def pid_status
return :exited unless File.exist?(pid_path)
pid = ::File.read(pid_path).to_i
return :dead if pid == 0
Process.kill(0, pid)
:running
rescue Errno::ESRCH
:dead
rescue Errno::EPERM
:not_owned
end
def check_pid
case pid_status
when :running, :not_owned
logger.error "A server is already running. Check #{pid_path}"
exit(2)
when :dead
File.delete(pid_path)
end
end
def write_pid
FileUtils.mkdir_p(File.dirname(pid_path)) unless File.exist?(pid_path)
File.open(pid_path, ::File::CREAT | ::File::EXCL | ::File::WRONLY){|f| f.write("#{Process.pid}") }
at_exit { File.delete(pid_path) if File.exist?(pid_path) }
rescue Errno::EEXIST
check_pid
retry
end
def launch
::Proxy::Plugins.configure_loaded_plugins
http_app = http_app()
https_app = https_app()
raise Exception.new("Both http and https are disabled, unable to start.") if http_app.nil? && https_app.nil?
if SETTINGS.daemon
check_pid
Process.daemon
write_pid
end
t1 = Thread.new { https_app.start } unless https_app.nil?
t2 = Thread.new { http_app.start } unless http_app.nil?
begin
if Gem.loaded_specs['rack'].version < Gem::Version.create('1.6.4')
# Rack installs its own trap; Sleeping for 5 secs insures we overwrite it with our own
sleep 5
trap(:INT) do
exit(0)
end
end
rescue Exception => e
logger.warn "Unable to overwrite interrupt trap: #{e}"
end
(t1 || t2).join
rescue SignalException => e
# This is to prevent the exception handler below from catching SignalException exceptions.
logger.info("Caught #{e}. Exiting")
raise
rescue SystemExit
# do nothing. This is to prevent the exception handler below from catching SystemExit exceptions.
raise
rescue Exception => e
logger.error("Error during startup, terminating. #{e}")
logger.debug("#{e}:#{e.backtrace.join("\n")}")
puts "Errors detected on startup, see log for details. Exiting: #{e}"
exit(1)
end
end
end
lib/proxy/log.rb
require 'logger'
begin
require 'syslog/logger'
::Syslog::Logger.class_eval { alias_method :write, :info }
rescue LoadError
puts "Setting log_file=SYSLOG not supported on this platform, ignoring"
end
Logger.class_eval { alias_method :write, :'<<' } # ::Rack::CommonLogger expects loggers to implement 'write' method
# ::Rack::CommonLogger expects loggers to implement 'write' method
Logger.class_eval { alias_method :write, :info }
module Proxy
module Log
......
@@logger ||= ::Proxy::Log.logger
end
def self.default_logger(log_file)
# We keep the last 6 10MB log files
::Logger.new(log_file, 6, 1024*1024*10)
end
def self.logger
log_file = ::Proxy::SETTINGS.log_file
if log_file.upcase == 'STDOUT'
if SETTINGS.daemon
puts "Settings log_file=STDOUT and daemon=true are incompatible, exiting..."
exit 1
end
logger = ::Logger.new(STDOUT)
elsif log_file.upcase == 'SYSLOG'
begin
logger = ::Syslog::Logger.new 'foreman-proxy'
rescue
logger = default_logger(log_file)
end
else
# We keep the last 6 10MB log files
logger = ::Logger.new(log_file, 6, 1024*1024*10)
logger = default_logger(log_file)
end
logger.level = ::Logger.const_get(::Proxy::SETTINGS.log_level.upcase)
logger
lib/smart_proxy.rb
APP_ROOT = "#{File.dirname(__FILE__)}/.."
require 'proxy'
require 'launcher'
require 'fileutils'
require 'pathname'
require 'checks'
require 'webrick/https'
require 'daemon' # FIXME: Do we still need this?
require 'daemon'
require 'checks'
require 'proxy/log'
......
def self.version
{:version => VERSION}
end
class Launcher
include ::Proxy::Log
def pid_path
SETTINGS.daemon_pid
end
def create_pid_dir
if SETTINGS.daemon
FileUtils.mkdir_p(File.dirname(pid_path)) unless File.exist?(pid_path)
end
end
def https_enabled?
SETTINGS.ssl_private_key && SETTINGS.ssl_certificate && SETTINGS.ssl_ca_file
end
def http_app
return nil if SETTINGS.http_port.nil?
app = Rack::Builder.new do
::Proxy::Plugins.enabled_plugins.each do |p|
instance_eval(p.http_rackup)
end
end
Rack::Server.new(
:app => app,
:server => :webrick,
:Host => SETTINGS.bind_host,
:Port => SETTINGS.http_port,
:daemonize => false,
:pid => (SETTINGS.daemon && !https_enabled?) ? pid_path : nil)
end
def https_app
unless https_enabled?
logger.warn "Missing SSL setup, https is disabled."
nil
else
app = Rack::Builder.new do
::Proxy::Plugins.enabled_plugins.each {|p| instance_eval(p.https_rackup)}
end
Rack::Server.new(
:app => app,
:server => :webrick,
:Host => SETTINGS.bind_host,
:Port => SETTINGS.https_port,
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER,
:SSLPrivateKey => load_ssl_private_key(SETTINGS.ssl_private_key),
:SSLCertificate => load_ssl_certificate(SETTINGS.ssl_certificate),
:SSLCACertificateFile => SETTINGS.ssl_ca_file,
:daemonize => false,
:pid => SETTINGS.daemon ? pid_path : nil)
end
end
def load_ssl_private_key(path)
OpenSSL::PKey::RSA.new(File.read(path))
rescue Exception => e
logger.error "Unable to load private SSL key. Are the values correct in settings.yml and do permissions allow reading?: #{e}"
raise e
end
def load_ssl_certificate(path)
OpenSSL::X509::Certificate.new(File.read(path))
rescue Exception => e
logger.error "Unable to load SSL certificate. Are the values correct in settings.yml and do permissions allow reading?: #{e}"
raise e
end
def launch
::Proxy::Plugins.configure_loaded_plugins
create_pid_dir
http_app = http_app()
https_app = https_app()
raise Exception.new("Both http and https are disabled, unable to start.") if http_app.nil? && https_app.nil?
Process.daemon if SETTINGS.daemon
t1 = Thread.new { https_app.start } unless https_app.nil?
t2 = Thread.new { http_app.start } unless http_app.nil?
sleep 5 # Rack installs its own trap; Sleeping for 5 secs insures we overwrite it with our own
trap(:INT) do
exit(0)
end
(t1 || t2).join
rescue SignalException => e
# This is to prevent the exception handler below from catching SignalException exceptions.
logger.info("Caught #{e}. Exiting")
raise
rescue SystemExit
# do nothing. This is to prevent the exception handler below from catching SystemExit exceptions.
raise
rescue Exception => e
logger.error("Error during startup, terminating. #{e}")
logger.debug("#{e}:#{e.backtrace.join("\n")}")
puts "Errors detected on startup, see log for details. Exiting."
exit(1)
end
end
end
test/launcher_test.rb
require 'test_helper'
require 'launcher'
class LauncherTest < Test::Unit::TestCase
def setup
@launcher = Proxy::Launcher.new
@launcher.stubs(:pid_path).returns("launcher_test.pid")
end
def test_pid_status_exited
assert_equal :exited, @launcher.pid_status
end
def test_write_pid_success
@launcher.write_pid
assert File.exist?(@launcher.pid_path)
ensure
FileUtils.rm_f @launcher.pid_path
end
def test_pid_status_running
@launcher.write_pid
assert_equal :running, @launcher.pid_status
ensure
FileUtils.rm_f @launcher.pid_path
end
def test_check_pid_deletes_dead
Process.stubs(:kill).returns { raise Errno::ESRCH }
@launcher.check_pid
assert_equal false, File.exist?(@launcher.pid_path)
end
def test_check_pid_exits_program
@launcher.write_pid
assert_raises SystemExit do
@launcher.check_pid
end
ensure
FileUtils.rm_f @launcher.pid_path
end
end

Also available in: Unified diff