class Interval

Public Instance Methods

get_prop(option_name) click to toggle source
# File lib/interval.rb, line 184
def get_prop(option_name)
  prop_value = nil
  if @options[option_name]
    prop_value = @options[option_name]
  end
  logger.debug "Found value #{prop_value} for #{option_name.to_s}"
  prop_value
end
my_fail(msg) click to toggle source

Fail, exit with an error message

# File lib/interval.rb, line 152
def my_fail(msg)
  logger.fatal msg if logger
  STDERR.puts msg
  send_to_sensu(1, "Error running interval for #{@options[:proc_name]}, #{msg}")
  store_status 1
  exit 1
end
parse_time(s) click to toggle source

Parse a time expression

The argument should be in one of the following forms

<number>   - parsed as seconds
<number>s  - parsed as seconds
<number>m  - parsed as minutes
<number>h  - parsed as hours

@param s [String] String to parse

return [Numeric] number of seconds time represents

# File lib/interval.rb, line 129
def parse_time(s)
  case s
    when /^\d+s?$/
      s.to_i
    when /^\d+m$/
      s.to_i * 60
    when /^\d+h$/
      s.to_i * 60 * 60
    else
      -1
  end
end
read_options(argv) click to toggle source

Read and parse command line options

# File lib/interval.rb, line 17
def read_options(argv)
  @options = {}
  optparse = OptionParser.new do|opts|
    opts.banner = "Usage: interval [options] -p <proc_name> -c <command_to_run>"

    @options[:proc_name] = nil
    opts.on( '-p', '--proc_name <name>', 'Process name to use' ) do |proc_name|
      @options[:proc_name] = proc_name
    end

    @options[:cmd] = nil
    opts.on( '-c', '--cmd <path>', 'Command to run' ) do |cmd|
      @options[:cmd] = cmd
    end

    opts.on( '-u', '--uri_cmd <path>', 'Command to run (URI-encoded)' ) do |ucmd|
      @options[:cmd] = URI.unescape(ucmd)
    end

    @options[:config_name] = nil
    opts.on( '-C', '--config_name <name>', 'Config name to use' ) do |config_name|
      @options[:config_name] = config_name
    end

    @options[:home] = nil
    opts.on( '-h', '--home <home>', 'Home directory of application' ) do |home|
      @options[:home] = home
    end

    @options[:log_file] = nil
    opts.on( '-l', '--logfile FILE', 'Write log to FILE, defaults to STDOUT' ) do |file|
      @options[:log_file] = file
    end

    @options[:log_level] = 'info'
    opts.on( '-L', '--log_level level', 'Set the log level (debug, info, warn, error, fatal)' ) do| level|
      @options[:log_level] = level
    end

    @options[:count] = nil
    opts.on( '-n', '--num <count>', 'Run the command <count> times and then exit (use for testing)' ) do |count|
      @options[:count] = count.to_i
    end

    @options[:fail_on_error] = false
    opts.on( '-f', '--fail_on_error', 'Abort if the program fails' ) do
      @options[:fail_on_error] = true
    end

    @options[:sensu_enabled] = false
    opts.on( '-z', '--sensu', 'Do report status to Sensu' ) do
      @options[:sensu_enabled] = true
    end

    @options[:sensu_port] = 3030
    opts.on( '--sensu_port <port>', Integer, 'Port to talk to sensu on' ) do |port|
      @options[:sensu_port] = port
    end

    @options[:status_file] = nil
    opts.on( '-s', '--status_file <status>', 'File to write last run status to' ) do |status|
      @options[:status_file] = status
    end

    @options[:interval] = nil
    opts.on( '-i', '--interval <interval>', 'Time between runs of program (seconds if no unit is specified)' ) do |interval|
      @options[:interval] = interval
    end

    @options[:time_stamp] = nil
    opts.on( '-t', '--time <time>', 'Run command at a specific time each day, could be comma separated. Format HHMM. Example 1430,0230 will run the command at 14:30 and 02:30 each day' ) do |time|
      @options[:time_stamp] = time.split(",")
    end

    opts.on_tail("-h", "--help", "Show this message") do
      puts opts
      exit
    end
  end

  begin
    optparse.parse!(argv)
    if @options[:config_name].nil?
      @options[:config_name] = @options[:proc_name]
    end
    mandatory = [:proc_name, :cmd]
    missing = mandatory.select{ |param| @options[param].nil? }
    unless missing.empty?
      STDERR.puts "Missing options: #{missing.join(', ')}"
      STDERR.puts optparse
      exit 1
    end
  rescue OptionParser::InvalidOption, OptionParser::MissingArgument  #
    STDERR.puts $!.to_s                                                     # Friendly output when parsing fails
    STDERR.puts optparse                                                    #
    exit 1                                                           #
  end
end
run() click to toggle source
# File lib/interval.rb, line 193
def run
  read_options(ARGV)

  # Check binary
  unless File.executable?(@options[:cmd].split[0])
    my_fail "Specified binary '#{@options[:cmd]}' does not exist or is not executable"
  end

  set_log_level @options[:log_level]
  set_log_file @options[:log_file] if @options[:log_file]

  if @options[:home]
    Dir.chdir(@options[:home])
  end

  # Get properties
  conf_name = @options[:config_name] ? @options[:config_name] : @options[:proc_name]

  begin
    interval_in = get_prop :interval
    if interval_in
      interval = parse_time interval_in
      logger.info "Using an run interval of #{interval} seconds"
      if interval < 1
        my_fail "Illegal interval #{interval} given for property #{conf_name}.run.interval"
      end

    else
      time_stamp = get_prop :time_stamp
      raise "Could not found interval or time_stamp option, one of those is needed" unless time_stamp
      logger.info "Will run at time stamp #{time_stamp.join(",")}"
    end
  rescue StandardError => e
    puts e.message
    my_fail "Did not find config value for #{conf_name}"
  end


  logger.info "Starting interval #{@options.inspect}"

  store_status 0

  count = 0
  first = true
  loop do
    unless time_stamp && first
      count = count+1
      logger.info "Launching[#{count}]: #{@options[:cmd]}"
      result, stdout, stderr = systemu @options[:cmd]
      store_status result.exitstatus
      if result != 0
        logger.error "Failed to run #{@options[:cmd]} (exit code #{result})"
        logger.error "Stdout: #{stdout.strip}" if stdout.length > 0
        logger.error "Stderr: #{stderr.strip}" if stderr.length > 0
        send_to_sensu(1, "Result is #{result}. Stderr is: #{stderr}, stdout is #{stdout}")
        if @options[:fail_on_error]
          exit result
        end
      else
        logger.info "Command completed successfully"
        logger.info "Stdout: #{stdout.strip}" if stdout.length > 0
        send_to_sensu(0, "")
      end
    end
    first = false

    if @options[:count] && count >= @options[:count]
      exit 0
    end
    if time_stamp
      diff = []
      time_stamp.each do |time|
        current = Time.now.strftime("%H%M%S")
        diff_time = ((time[0..1].to_i * 60 + time[2..3].to_i) - (current[0..1].to_i * 60 + current[2..3].to_i))*60 - current[4..5].to_i
        diff_time += 24*60*60 if diff_time <= 0
        diff << diff_time
      end
      logger.debug "sleep for #{diff.min + 1} seconds"
      sleep diff.min + 1
    else
      logger.debug "sleep for #{interval} seconds"
      sleep interval
    end
  end
end
send_to_sensu(status, message) click to toggle source

Send message to sensu

# File lib/interval.rb, line 163
def send_to_sensu(status, message)
  if @options[:sensu_enabled]
    report = {
        :name => "interval_error_#{@options[:proc_name]}",
        :output => "",
        :status => status
    }
    if status != 0
      report[:output] = "Failed to run #{@options[:proc_name]} #{message}"
    end

    begin
      s = TCPSocket.open(SENSU_HOST, @options[:sensu_port])
      s.print JSON.generate(report)
      s.close
    rescue Exception => e
      logger.error "Failed to send report to sensu: #{e.message}"
    end
  end
end
store_status(exit_code) click to toggle source

Store status on disk

# File lib/interval.rb, line 144
def store_status(exit_code)
  if @options[:status_file]
    File.open(@options[:status_file], 'w') do |f| f.puts("result=#{exit_code}") end
  end
end