flapjack 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'open-uri'
4
+
5
+ page = ARGV[0]
6
+ string = ARGV[1]
7
+
8
+ exit 2 unless page && string
9
+
10
+ body = open(page).read
11
+ if body =~ /#{string}/
12
+ exit 0
13
+ else
14
+ exit 2
15
+ end
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ostruct'
5
+ require 'optparse'
6
+ require 'log4r'
7
+ require 'log4r/outputter/syslogoutputter'
8
+ require File.join(File.dirname(__FILE__), '..', 'database')
9
+ require File.join(File.dirname(__FILE__), '..', 'notifier')
10
+ require File.join(File.dirname(__FILE__), '..', 'result')
11
+
12
+ module Flapjack
13
+ class NotifierOptions
14
+ def self.parse(args)
15
+ options = OpenStruct.new
16
+ opts = OptionParser.new do |opts|
17
+ # the available command line options
18
+ opts.on('-b', '--beanstalk HOST', 'location of the beanstalkd') do |host|
19
+ options.host = host
20
+ end
21
+ opts.on('-p', '--port PORT', 'beanstalkd port') do |port|
22
+ options.port = port.to_i
23
+ end
24
+ opts.on('-r', '--recipients FILE', 'recipients file') do |recipients|
25
+ options.recipients = recipients.to_s
26
+ end
27
+ opts.on('-c', '--config FILE', 'config file') do |config|
28
+ options.config_filename = config.to_s
29
+ end
30
+ opts.on('-d', '--database-uri URI', 'location of the checks database') do |db|
31
+ options.database_uri = db.to_s
32
+ end
33
+ opts.on_tail("-h", "--help", "Show this message") do
34
+ puts opts
35
+ exit
36
+ end
37
+ end
38
+
39
+ # parse the options
40
+ begin
41
+ opts.parse!(args)
42
+ rescue OptionParser::MissingArgument => e
43
+ # if an --option is missing it's argument
44
+ puts e.message.capitalize + "\n\n"
45
+ puts opts
46
+ exit 1
47
+ end
48
+
49
+ # default the host + port
50
+ options.host ||= 'localhost'
51
+ options.port ||= 11300
52
+
53
+ @errors = []
54
+ # check that recipients file exists
55
+ if options.recipients
56
+ unless File.exists?(options.recipients.to_s)
57
+ @errors << "The specified recipients file doesn't exist!"
58
+ end
59
+ else
60
+ @errors << "You need to specify a recipients file with [-r|--recipients]."
61
+ end
62
+
63
+ # check that config file exists
64
+ if options.config_filename
65
+ unless File.exists?(options.config_filename.to_s)
66
+ @errors << "The specified config file doesn't exist!"
67
+ end
68
+ else
69
+ options.config_filename ||= "/etc/flapjack/flapjack-notifier.yaml"
70
+ unless File.exists?(options.config_filename.to_s)
71
+ @errors << "The default config file (#{options.config_filename}) doesn't exist!"
72
+ @errors << "Set one up, or specify one with [-c|--config]."
73
+ end
74
+ end
75
+
76
+ # if there are errors, print them out and exit
77
+ if @errors.size > 0
78
+ puts "Errors:"
79
+ @errors.each do |error|
80
+ puts " - #{error}"
81
+ end
82
+ puts
83
+ puts opts
84
+ exit 2
85
+ end
86
+
87
+ options
88
+ end
89
+ end
90
+
91
+ class NotifierCLI
92
+ attr_accessor :log, :recipients, :results_queue, :config
93
+ attr_accessor :notifier, :notifiers
94
+ attr_accessor :condition
95
+
96
+ def initialize(opts={})
97
+ @log = opts[:logger]
98
+ @log ||= Log4r::Logger.new("notifier")
99
+ end
100
+
101
+ def setup_loggers
102
+ @log.add(Log4r::StdoutOutputter.new('notifier'))
103
+ @log.add(Log4r::SyslogOutputter.new('notifier'))
104
+ end
105
+
106
+ def setup_recipients(opts={})
107
+
108
+ if opts[:yaml]
109
+ yaml = opts[:yaml]
110
+ else
111
+ opts[:filename] ||= File.join(Dir.pwd, "recipients.yaml")
112
+ yaml = YAML::load(File.read(opts[:filename]))
113
+ end
114
+
115
+ # FIXME: add error detection for passing in dumb things
116
+
117
+ @recipients = yaml.map do |r|
118
+ OpenStruct.new(r)
119
+ end
120
+ end
121
+
122
+ def setup_config(opts={})
123
+ if opts[:yaml]
124
+ yaml = opts[:yaml]
125
+ else
126
+ opts[:filename] ||= File.join(Dir.pwd, "flapjack-notifier.yaml")
127
+ yaml = YAML::load(File.read(opts[:filename]))
128
+ end
129
+
130
+ @config = OpenStruct.new(yaml)
131
+ end
132
+
133
+ def setup_database(opts={})
134
+ uri = (opts[:database_uri] || @config.database_uri)
135
+ raise ArgumentError, "database URI wasn't specified!" unless uri
136
+ DataMapper.setup(:default, uri)
137
+ validate_database
138
+ end
139
+
140
+ def validate_database
141
+ begin
142
+ DataMapper.repository(:default).adapter.execute("SELECT 'id' FROM 'checks';")
143
+ rescue Sqlite3Error
144
+ @log.warning("The specified database doesn't appear to have any structure!")
145
+ @log.warning("You need to investigate this.")
146
+ end
147
+ # FIXME: add more rescues in here!
148
+ end
149
+
150
+ def initialize_notifiers(opts={})
151
+ notifiers_directory = opts[:notifiers_directory]
152
+ notifiers_directory ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'notifiers'))
153
+
154
+ raise ArgumentError, "notifiers directory doesn't exist!" unless File.exists?(notifiers_directory)
155
+
156
+ @notifiers = []
157
+
158
+ @config.notifiers.each_pair do |notifier, config|
159
+ filename = File.join(notifiers_directory, notifier.to_s, 'init')
160
+ if File.exists?(filename + '.rb')
161
+ @log.debug("Loading the #{notifier.to_s.capitalize} notifier")
162
+ require filename
163
+ config.merge!(:logger => @log, :website_uri => @config.website_uri)
164
+ @notifiers << Flapjack::Notifiers.const_get("#{notifier.to_s.capitalize}").new(config)
165
+ else
166
+ @log.warning("Flapjack::Notifiers::#{notifier.to_s.capitalize} doesn't exist!")
167
+ end
168
+ end
169
+
170
+ @notifiers
171
+ end
172
+
173
+ # Sets up notifier to do the grunt work of notifying people when checks
174
+ # return badly.
175
+ #
176
+ # Accepts a list of recipients (:recipients) and a logger (:logger) as
177
+ # arguments. If neither of these are specified, it will default to an
178
+ # existing list of recipients and the current logger.
179
+ #
180
+ # Sets up and returns @notifier, an instance of Flapjack::Notifier
181
+ def setup_notifier(opts={})
182
+ recipients = (opts[:recipients] || @recipients)
183
+ log = (opts[:logger] || @log)
184
+ initialize_notifiers
185
+ notifiers = @notifiers # should we accept a list of notifiers?
186
+ @notifier = Flapjack::Notifier.new(:logger => log,
187
+ :notifiers => notifiers,
188
+ :recipients => recipients)
189
+ end
190
+
191
+ def process_loop
192
+ @log.info("Processing results...")
193
+ loop do
194
+ process_result
195
+ end
196
+ end
197
+
198
+ # FIXME: we're doing a lookup twice - optimise out
199
+ def save_result(result)
200
+ if check = Check.get(result.id)
201
+ check.status = result.retval
202
+ check.save
203
+ else
204
+ @log.error("Got result for check #{result.id}, but it doesn't exist!")
205
+ end
206
+ end
207
+
208
+ # FIXME: we're doing a lookup twice - optimise out
209
+ def any_parents_warning_or_critical?(result)
210
+ if check = Check.get(result.id)
211
+ check.worst_parent_status > 0 ? true : false
212
+ else
213
+ @log.error("Got result for check #{result.id}, but it doesn't exist!")
214
+ end
215
+ end
216
+
217
+ def process_result
218
+ @log.debug("Waiting for new result...")
219
+ result_job = @results_queue.reserve # blocks until a job is reserved
220
+ result = Flapjack::Result.new(YAML::load(result_job.body))
221
+
222
+ @log.info("Processing result for check '#{result.id}'")
223
+ if result.warning? || result.critical?
224
+ if any_parents_warning_or_critical?(result)
225
+ @log.info("Not notifying on check '#{result.id}' as parent is failing.")
226
+ else
227
+ @log.info("Notifying on check '#{result.id}'")
228
+ @notifier.notify!(result)
229
+ end
230
+
231
+ @log.info("Creating event for check '#{result.id}'")
232
+ event = Event.new(:check_id => result.id)
233
+ raise unless event.save
234
+ end
235
+
236
+ @log.info("Storing status of check in database")
237
+ save_result(result)
238
+
239
+ @log.debug("Deleting result for check '#{result.id}' from queue")
240
+ result_job.delete
241
+ end
242
+
243
+ end
244
+
245
+ end
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ostruct'
5
+ require 'optparse'
6
+
7
+ module Flapjack
8
+ class NotifierManagerOptions
9
+ def self.parse(args)
10
+ options = OpenStruct.new
11
+ opts = OptionParser.new do |opts|
12
+ opts.banner = "Usage: flapjack-notifier-manager <command> [options]"
13
+ opts.separator " "
14
+ opts.separator " where <command> is one of:"
15
+ opts.separator " start start a worker"
16
+ opts.separator " stop stop all workers"
17
+ opts.separator " restart restart workers"
18
+ opts.separator " "
19
+ opts.separator " and [options] are:"
20
+
21
+ opts.on('-b', '--beanstalk HOST', 'location of the beanstalkd') do |host|
22
+ options.host = host
23
+ end
24
+ opts.on('-p', '--port PORT', 'beanstalkd port') do |port|
25
+ options.port = port.to_s
26
+ end
27
+ opts.on('-r', '--recipients FILE', 'recipients file') do |recipients|
28
+ options.recipients = File.expand_path(recipients.to_s)
29
+ end
30
+ opts.on('-c', '--config FILE', 'config file') do |config|
31
+ options.config_filename = config.to_s
32
+ end
33
+ end
34
+
35
+ begin
36
+ opts.parse!(args)
37
+ rescue => e
38
+ puts e.message.capitalize + "\n\n"
39
+ puts opts
40
+ exit 1
41
+ end
42
+
43
+ # defaults
44
+ options.host ||= "localhost"
45
+ options.port ||= 11300
46
+
47
+ @errors = []
48
+
49
+ unless ARGV[0] == "stop"
50
+ if options.recipients
51
+ unless File.exists?(options.recipients)
52
+ @errors << "The specified recipients file doesn't exist!"
53
+ end
54
+ else
55
+ @errors << "You must specify a recipients file!"
56
+ end
57
+
58
+ if options.config_filename
59
+ unless File.exists?(options.config_filename)
60
+ @errors << "The specified config file dosen't exist!"
61
+ end
62
+ else
63
+ @errors << "You must specify a config file!"
64
+ end
65
+ end
66
+
67
+ if @errors.size > 0
68
+ puts "Errors:"
69
+ @errors.each do |error|
70
+ puts " - #{error}"
71
+ end
72
+ puts
73
+ puts opts
74
+ exit 2
75
+ end
76
+
77
+ unless %w(start stop restart).include?(args[0])
78
+ puts opts
79
+ exit 1
80
+ end
81
+
82
+ options
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'beanstalk-client'
5
+ require 'ostruct'
6
+ require 'optparse'
7
+ require 'log4r'
8
+ require 'log4r/outputter/syslogoutputter'
9
+ require 'flapjack/result'
10
+ require 'flapjack/patches'
11
+
12
+ module Flapjack
13
+ class WorkerOptions
14
+ def self.parse(args)
15
+ options = OpenStruct.new
16
+ opts = OptionParser.new do |opts|
17
+ # the available command line options
18
+ opts.on('-b', '--beanstalk HOST', 'location of the beanstalkd') do |host|
19
+ options.host = host
20
+ end
21
+ opts.on('-p', '--port PORT', 'beanstalkd port') do |port|
22
+ options.port = port.to_i
23
+ end
24
+ opts.on('-c', '--checks-directory DIR', 'sandboxed check directory') do |dir|
25
+ options.checks_directory = dir.to_s
26
+ end
27
+ opts.on_tail("-h", "--help", "Show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+ end
32
+
33
+ # parse the options
34
+ begin
35
+ opts.parse!(args)
36
+ rescue OptionParser::MissingArgument => e
37
+ # if an --option is missing it's argument
38
+ puts e.message.capitalize + "\n\n"
39
+ puts opts
40
+ exit 1
41
+ end
42
+
43
+ # default the port
44
+ options.host ||= 'localhost'
45
+ options.port ||= 11300
46
+ options.checks_directory ||= File.join(File.dirname(__FILE__), '..', 'checks')
47
+
48
+ options
49
+ end
50
+ end
51
+
52
+ class Worker
53
+
54
+ attr_accessor :jobs, :results, :log
55
+
56
+ def initialize(opts={})
57
+ @jobs = Beanstalk::Pool.new(["#{opts[:host]}:#{opts[:port]}"], 'jobs')
58
+ @results = Beanstalk::Pool.new(["#{opts[:host]}:#{opts[:port]}"], 'results')
59
+ @sandbox = (opts[:check_directory] || File.expand_path(File.join(File.dirname(__FILE__), '..', 'checks')))
60
+
61
+ if opts[:logger]
62
+ @log = opts[:logger]
63
+ else
64
+ @log = Log4r::Logger.new('worker')
65
+ @log.add(Log4r::StdoutOutputter.new('worker'))
66
+ @log.add(Log4r::SyslogOutputter.new('worker'))
67
+ end
68
+ end
69
+
70
+ def process_loop
71
+ @log.info("Booting main loop...")
72
+ loop do
73
+ process_check
74
+ end
75
+ end
76
+
77
+ def process_check
78
+ # get next check off the beanstalk
79
+ job, check = get_check()
80
+
81
+ # do the actual check
82
+ result, retval = perform_check(check.command)
83
+
84
+ # report the results of the check
85
+ report_check(:result => result, :retval => retval, :check => check)
86
+
87
+ # create another job for the check, delete current job
88
+ cleanup_job(:job => job, :check => check)
89
+ end
90
+
91
+ def perform_check(cmd)
92
+ command = "sh -c '#{@sandbox}/#{cmd}'"
93
+ @log.debug("Executing check: \"#{command}\"")
94
+ result = `#{command}`
95
+ retval = $?.exitstatus
96
+
97
+ return result, retval
98
+ end
99
+
100
+ def report_check(opts={})
101
+ raise ArgumentError unless (opts[:result] && opts[:retval] && opts[:check])
102
+
103
+ @log.debug "Reporting results for check id #{opts[:check].id}."
104
+ @results.yput({:id => opts[:check].id,
105
+ :output => opts[:result],
106
+ :retval => opts[:retval].to_i})
107
+ end
108
+
109
+ def cleanup_job(opts={})
110
+ raise ArgumentError unless (opts[:job] && opts[:check])
111
+
112
+ # add job back onto stack
113
+ @log.debug("Putting check back onto beanstalk.")
114
+ @jobs.yput(opts[:check].to_h, 65536, opts[:check].frequency)
115
+
116
+ # FIXME: what happens when power goes out here?
117
+
118
+ # once we're done, clean up
119
+ @log.debug("Deleting job.")
120
+ opts[:job].delete
121
+ end
122
+
123
+ def get_check
124
+ @log.debug("Waiting for check...")
125
+ job = @jobs.reserve
126
+ # FIXME: maybe wrap Result as Job now that Check is reserved?
127
+ check = Result.new(YAML::load(job.body))
128
+ @log.info("Got check with id #{check.id}")
129
+
130
+ return job, check
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+