class Nines::App

Attributes

config[RW]
continue[RW]
debug[RW]
email_from[RW]
email_subject_prefix[RW]
logfile[RW]
logger[RW]
notifier[RW]
pidfile[RW]
root[RW]
verbose[RW]

Public Class Methods

new(config_file) click to toggle source
# File lib/nines/app.rb, line 14
def initialize(config_file)
  self.class.root = File.expand_path('../../../', __FILE__)
  
  # load config files
  case File.extname(config_file)
    when '.yml' then self.class.config = YAML.load(ERB.new(File.read(config_file)).result)
    when '.rb'  then require config_file
  end
  self.class.config = stringify_keys_and_symbols(self.class.config)
  
  # set main parameters
  self.class.debug      = config['debug']
  self.class.verbose    = config['verbose']
  
  self.class.logfile    = config['logfile'] || 'nines.log'
  self.class.pidfile    = config['pidfile'] || 'nines.pid'
  self.class.email_from = config['email_from'] || 'Nines Notifier <no-reply@example.com>'
  self.class.email_subject_prefix = config['email_subject_prefix'] || ''
end

Public Instance Methods

check_hostnames() click to toggle source

make sure you’re not using OpenDNS or something else that resolves invalid names

# File lib/nines/app.rb, line 88
def check_hostnames
  all_good = true
  
  @check_groups.each do |group|
    group.checks.each do |check|
      unless check.hostname && Dnsruby::Resolv.getaddress(check.hostname)
        puts "Error: check #{check.name} has invalid hostname '#{check.hostname}'"
        all_good = false
      end
    end
  end
  
  all_good
end
config() click to toggle source

shortcuts

# File lib/nines/app.rb, line 35
def config    ; self.class.config   ; end
configure_smtp() click to toggle source
# File lib/nines/app.rb, line 103
def configure_smtp
  if config['smtp'].is_a?(Hash)
    Mail.defaults do
      delivery_method :smtp, { 
        :address => Nines::App.config['smtp']['address'] || 'localhost',
        :port => Nines::App.config['smtp']['port'] || 25,
        :domain => Nines::App.config['smtp']['domain'],
        :user_name => Nines::App.config['smtp']['user_name'],
        :password => Nines::App.config['smtp']['password'],
        :authentication => (Nines::App.config['smtp']['authentication'] || 'plain').to_sym,
        :enable_starttls_auto => Nines::App.config['smtp']['enable_starttls_auto'],
        :tls => Nines::App.config['smtp']['tls'],
      }
    end
  end
end
debug() click to toggle source
# File lib/nines/app.rb, line 38
def debug     ; self.class.debug    ; end
logfile() click to toggle source
# File lib/nines/app.rb, line 36
def logfile   ; self.class.logfile  ; end
logfile_writable() click to toggle source
# File lib/nines/app.rb, line 67
def logfile_writable
  begin
    File.open(logfile, 'a') { }
    true
  rescue Exception => e
    puts "Exception: #{e}"
    false
  end
end
logger() click to toggle source
# File lib/nines/app.rb, line 39
def logger    ; self.class.logger   ; end
pidfile() click to toggle source
# File lib/nines/app.rb, line 37
def pidfile   ; self.class.pidfile  ; end
pidfile_writable() click to toggle source
# File lib/nines/app.rb, line 77
def pidfile_writable
  begin
    File.open(pidfile, 'a') { }
    true
  rescue Exception => e
    puts "Exception: #{e}"
    false
  end
end
running?() click to toggle source
# File lib/nines/app.rb, line 41
def running?
  pid = nil
  
  begin
    pid = File.open(pidfile).read.to_i
    return false if pid == 0
  rescue
    # puts "Pidfile doesn't exist"
    return false
  end
  
  begin
    Process.kill(0, pid)
    # puts "#{pid} is running"
    return true
  rescue Errno::EPERM
    # puts "No permission to query #{pid}!"
  rescue Errno::ESRCH
    # puts "#{pid} is NOT running."
  rescue
    # puts "Unable to determine status for #{pid} : #{$!}"
  end
  
  false
end
start(options = {}) click to toggle source
# File lib/nines/app.rb, line 134
def start(options = {})
  # set up logger
  self.class.logger = Logger.new(debug ? STDOUT : File.open(logfile, 'a'))
  logger.sync = 1  # makes it possible to tail the logfile
  
  # use it
  logger.puts "[#{Time.now}] - nines starting"
  
  # set up notifier
  configure_smtp
  self.class.notifier = Notifier.new(config['contacts'])
  
  # set up check_groups (uses logger and notifier)
  if !config['check_groups'].is_a?(Array) || config['check_groups'].empty?
    raise Exception.new("No check groups configured, nothing to do.")
  end
  
  @check_groups = []
  config['check_groups'].each do |options|
    @check_groups << CheckGroup.new(options)
  end
  
  # TODO: this is a little awkwardly placed, but can fix later
  unless check_hostnames
    puts "Invalid hostnames found in config file"
    exit 1
  end
  
  # fork and detach
  if pid = fork
    File.open(pidfile, 'w') { |f| f.print pid }
    puts "Background process started with pid #{pid} (end it using `#{$0} stop`)"
    puts "Debug mode enabled, background process will log to STDOUT and exit after running each check once." if debug
    exit 0
  end
  
  #
  # rest of this method runs as background process
  #
  
  # trap signals before spawning threads
  self.class.continue = true
  trap("INT") { Nines::App.continue = false ; puts "Caught SIGINT, will exit after current checks complete or time out." }
  trap("TERM") { Nines::App.continue = false ; puts "Caught SIGTERM, will exit after current checks complete or time out." }
  
  # iterate through config, spawning check threads as we go
  @threads = []
  
  @check_groups.each do |group|
    group.checks.each do |check|
      @threads << Thread.new(Thread.current) { |parent|
        begin
          check.run
        rescue Exception => e
          parent.raise e
        end
      }
    end
  end
  
  @threads.each { |t| t.join if t.alive? }
  
  logger.puts "[#{Time.now}] - nines finished"
  logger.close
  
  File.unlink(pidfile)
  
  puts "Background process finished"
end
stop(options = {}) click to toggle source
# File lib/nines/app.rb, line 204
def stop(options = {})
  begin
    pid = File.read(pidfile).to_i
    if pid == 0
      STDERR.puts "nines does not appear to be running."
      exit 1
    end
  rescue Errno::ENOENT => e
    STDERR.puts "Couldn't open pid file #{pidfile}, please check your config."
    exit 1
  end
  
  begin
    Process.kill "INT", pid
    exit 0
  rescue Errno::EPERM => e
    STDERR.puts "Couldn't kill process with pid #{pid}, appears to be owned by someone else."
    exit 1
  rescue Errno::ESRCH => e
    STDERR.puts "Couldn't kill process with pid #{pid}. Are you sure it's running?"
    exit 1
  end
end
stringify_keys_and_symbols(obj) click to toggle source
# File lib/nines/app.rb, line 120
def stringify_keys_and_symbols(obj)
  case obj.class.to_s
  when 'Array'
    obj.map! { |el| stringify_keys_and_symbols(el) }
  when 'Hash'
    obj.stringify_keys!
    obj.each { |k,v| obj[k] = stringify_keys_and_symbols(v) }
  when 'Symbol'
    obj = obj.to_s
  end
  
  obj
end