lib/landrush/server.rb in landrush-0.19.0 vs lib/landrush/server.rb in landrush-1.0.0

- old
+ new

@@ -1,15 +1,49 @@ require 'rubydns' -require 'rexec/daemon' +require 'ipaddr' +require_relative 'store' +require_relative 'os' module Landrush - class Server < RExec::Daemon::Base + class Server + include Landrush::OS + Name = Resolv::DNS::Name IN = Resolv::DNS::Resource::IN + def self.working_dir + # TODO, https://github.com/vagrant-landrush/landrush/issues/178 + # Due to the fact that the whole server is just a bunch of static methods, + # there is no initalize method to ensure that the working directory is + # set prior to making calls to this method. Things work, since at the appropriate + # Vagrant plugin integrtion points (e.g. setup.rb) we set the working dir based + # on the enviroment passed to us. + if @working_dir.nil? + raise 'The Server\s working directory needs to be explicitly set prior to calling this method' + end + @working_dir + end + + def self.working_dir=(working_dir) + @working_dir = Pathname(working_dir).tap(&:mkpath) + end + + def self.log_directory + File.join(working_dir, 'log') + end + + def self.log_file + File.join(log_directory, 'landrush.log') + end + def self.port - @port ||= 10053 + if OS.windows? + # On Windows we need to use the default DNS port, since there seems to be no way to configure it otherwise + @port ||= 53 + else + @port ||= 100_53 + end end def self.port=(port) @port = port end @@ -19,52 +53,99 @@ @upstream_servers ||= Store.config.get('upstream').collect {|i| [i[0].to_sym, i[1], i[2]]} end def self.interfaces [ - [:udp, "0.0.0.0", port], - [:tcp, "0.0.0.0", port] + [:udp, '0.0.0.0', port], + [:tcp, '0.0.0.0', port] ] end def self.upstream @upstream ||= RubyDNS::Resolver.new(upstream_servers) end - def self.pid - RExec::Daemon::ProcessFile.recall(self) + # Used to start the Landrush DNS server as a child process using ChildProcess gem + def self.start + ensure_path_exits(log_file) + + if OS.windows? + pid = spawn('ruby', __FILE__, port.to_s, working_dir.to_s, :chdir => working_dir.to_path, [:out, :err] => [log_file, "w"], :new_pgroup => true) + else + pid = spawn('ruby', __FILE__, port.to_s, working_dir.to_s, :chdir => working_dir.to_path, [:out, :err] => [log_file, "w"], :pgroup => true) + end + Process.detach pid + + write_pid(pid) end - # For RExec - def self.working_directory - Landrush.working_dir + def self.stop + puts 'Stopping daemon...' + + # Check if the pid file exists... + unless File.file?(pid_file) + puts "Pid file #{pid_file} not found. Is the daemon running?" + return + end + + pid = read_pid + + # Check if the daemon is already stopped... + unless running? + puts "Pid #{pid} is not running. Has daemon crashed?" + return + end + + terminate_process pid + + # If after doing our best the daemon is still running (pretty odd)... + if running? + puts 'Daemon appears to be still running!' + return + end + + # Otherwise the daemon has been stopped. + delete_pid_file end - def self.running? - RExec::Daemon::ProcessFile.status(self) == :running + def self.restart + stop + start end - def self.prefork - super + def self.pid + IO.read(pid_file).to_i rescue nil end - def self.check_a_record (host, transaction) - value = Store.hosts.get(host) - if (IPAddr.new(value) rescue nil) - name = transaction.name =~ /#{host}/ ? transaction.name : host - transaction.respond!(value, {:ttl => 0, :name => name}) - else - transaction.respond!(Name.create(value), resource_class: IN::CNAME, ttl: 0) - check_a_record(value, transaction) + def self.running? + pid = read_pid + return false if pid.nil? + !!Process.kill(0, pid) rescue false + end + + def self.status + case process_status + when :running + puts "Daemon status: running pid=#{read_pid}" + when :stopped + puts 'Daemon status: stopped' + else + puts 'Daemon status: unknown' + puts "#{pid_file} exists, but process is not running" + puts "Check log file: #{log_file}" end end - def self.run + def self.run(port, working_dir) server = self - RubyDNS::run_server(:listen => interfaces) do - self.logger.level = Logger::INFO + server.port = port + server.working_dir = working_dir + # Start the DNS server + RubyDNS.run_server(:listen => interfaces) do + @logger.level = Logger::INFO + match(/.*/, IN::A) do |transaction| host = Store.hosts.find(transaction.name) if host server.check_a_record(host, transaction) else @@ -81,11 +162,92 @@ end end # Default DNS handler otherwise do |transaction| + # @logger.info "Passing on to upstream: #{transaction.to_s}" transaction.passthrough!(server.upstream) end end end + + def self.check_a_record(host, transaction) + value = Store.hosts.get(host) + if value.nil? + return + end + + if (IPAddr.new(value) rescue nil) + name = transaction.name =~ /#{host}/ ? transaction.name : host + transaction.respond!(value, :ttl => 0, :name => name) + else + transaction.respond!(Name.create(value), resource_class: IN::CNAME, ttl: 0) + check_a_record(value, transaction) + end + end + + # private methods + def self.write_pid(pid) + ensure_path_exits(pid_file) + File.open(pid_file, 'w') {|f| f << pid.to_s} + end + + def self.read_pid + IO.read(pid_file).to_i rescue nil + end + + def self.delete_pid_file + if File.exist? pid_file + FileUtils.rm(pid_file) + end + end + + def self.pid_file + File.join(working_dir, 'run', 'landrush.pid') + end + + def self.process_status + if File.exist? pid_file + return running? ? :running : :unknown + else + return :stopped + end + end + + def self.ensure_path_exits(file_name) + dirname = File.dirname(file_name) + unless File.directory?(dirname) + FileUtils.mkdir_p(dirname) + end + end + + def self.terminate_process(pid) + Process.kill("INT", pid) + sleep 0.1 + + sleep 1 if running? + + # Kill/Term loop - if the daemon didn't die easily, shoot + # it a few more times. + attempts = 5 + while running? && attempts > 0 + sig = (attempts >= 2) ? "KILL" : "TERM" + + puts "Sending #{sig} to process #{pid}..." + Process.kill(sig, pid) + + attempts -= 1 + sleep 1 + end + end + + private_class_method :write_pid, :read_pid, :delete_pid_file, :pid_file, :process_status, :ensure_path_exits, + :terminate_process end +end + +# Only run the following code when this file is the main file being run +# instead of having been required or loaded by another file +if __FILE__ == $0 + # TODO, Add some argument checks + Landrush::Server.run(ARGV[0], ARGV[1]) end