Class: Interval

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/interval.rb

Instance Method Summary collapse

Methods included from Logging

create_logger, #logger, logger, set_log_conf, #set_log_file, #set_log_level

Instance Method Details

#get_prop(option_name) ⇒ Object



184
185
186
187
188
189
190
191
# 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) ⇒ Object

Fail, exit with an error message



152
153
154
155
156
157
158
# 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) ⇒ Object

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

return [Numeric] number of seconds time represents

Parameters:

  • s (String)

    String to parse



129
130
131
132
133
134
135
136
137
138
139
140
# 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) ⇒ Object

Read and parse command line options



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 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

#runObject



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# 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) ⇒ Object

Send message to sensu



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 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) ⇒ Object

Store status on disk



144
145
146
147
148
# 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