flapjack 0.4.12 → 0.5.1

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.
Files changed (55) hide show
  1. data/README.md +77 -50
  2. data/Rakefile +78 -26
  3. data/TODO.md +15 -32
  4. data/bin/flapjack-benchmark +50 -0
  5. data/bin/flapjack-notifier +11 -36
  6. data/bin/flapjack-notifier-manager +1 -3
  7. data/bin/flapjack-worker +5 -19
  8. data/doc/PACKAGING.md +25 -0
  9. data/etc/flapjack/flapjack-notifier.conf.example +34 -0
  10. data/etc/flapjack/recipients.conf.example +14 -0
  11. data/features/flapjack-notifier-manager.feature +19 -0
  12. data/features/flapjack-worker-manager.feature +25 -0
  13. data/features/packaging-lintian.feature +15 -0
  14. data/features/persistence/couch.feature +105 -0
  15. data/features/persistence/sqlite3.feature +105 -0
  16. data/features/persistence/steps/couch_steps.rb +25 -0
  17. data/features/persistence/steps/generic_steps.rb +102 -0
  18. data/features/persistence/steps/sqlite3_steps.rb +13 -0
  19. data/features/steps/flapjack-notifier-manager_steps.rb +24 -0
  20. data/features/steps/flapjack-worker-manager_steps.rb +50 -0
  21. data/features/steps/packaging-lintian_steps.rb +13 -0
  22. data/features/support/env.rb +22 -0
  23. data/features/support/silent_system.rb +4 -0
  24. data/flapjack.gemspec +7 -11
  25. data/lib/flapjack/applications/notifier.rb +222 -0
  26. data/lib/flapjack/applications/worker.rb +99 -0
  27. data/lib/flapjack/checks/ping +10 -0
  28. data/lib/flapjack/cli/notifier.rb +80 -218
  29. data/lib/flapjack/cli/worker.rb +1 -86
  30. data/lib/flapjack/filters/any_parents_failed.rb +14 -0
  31. data/lib/flapjack/filters/ok.rb +13 -0
  32. data/lib/flapjack/inifile.rb +44 -0
  33. data/lib/flapjack/{notifier.rb → notifier_engine.rb} +13 -9
  34. data/lib/flapjack/notifiers/mailer/mailer.rb +12 -13
  35. data/lib/flapjack/notifiers/xmpp/xmpp.rb +2 -2
  36. data/lib/flapjack/patches.rb +25 -0
  37. data/lib/flapjack/persistence/couch.rb +5 -0
  38. data/lib/flapjack/persistence/couch/connection.rb +66 -0
  39. data/lib/flapjack/persistence/couch/couch.rb +63 -0
  40. data/lib/flapjack/persistence/data_mapper.rb +3 -0
  41. data/lib/flapjack/persistence/data_mapper/data_mapper.rb +67 -0
  42. data/lib/flapjack/{models → persistence/data_mapper/models}/check.rb +3 -7
  43. data/lib/flapjack/{models → persistence/data_mapper/models}/check_template.rb +0 -0
  44. data/lib/flapjack/persistence/data_mapper/models/event.rb +17 -0
  45. data/lib/flapjack/{models → persistence/data_mapper/models}/node.rb +0 -0
  46. data/lib/flapjack/{models → persistence/data_mapper/models}/related_check.rb +0 -0
  47. data/lib/flapjack/persistence/sqlite3.rb +3 -0
  48. data/lib/flapjack/persistence/sqlite3/sqlite3.rb +166 -0
  49. data/lib/flapjack/transports/beanstalkd.rb +33 -0
  50. data/lib/flapjack/transports/result.rb +58 -0
  51. metadata +46 -56
  52. data/etc/flapjack/flapjack-notifier.yaml.example +0 -8
  53. data/etc/flapjack/recipients.yaml.example +0 -10
  54. data/lib/flapjack/database.rb +0 -10
  55. data/lib/flapjack/result.rb +0 -47
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ ping -c 3 $1
4
+ retval=$?
5
+
6
+ if [ "$retval" != 0 ]; then
7
+ exit 2
8
+ else
9
+ exit 0
10
+ fi
@@ -1,246 +1,108 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
3
  require 'ostruct'
5
4
  require 'optparse'
6
5
  require 'log4r'
7
6
  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')
7
+ require File.join(File.dirname(__FILE__), '..', 'inifile')
11
8
 
12
9
  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
10
+ module Notifier
11
+ class Options
12
+ def self.parse(args)
13
+ options = OpenStruct.new
14
+ opts = OptionParser.new do |opts|
15
+ opts.on('-r', '--recipients FILE', 'recipients file') do |filename|
16
+ options.recipients_filename = filename
17
+ end
18
+ opts.on('-c', '--config FILE', 'config file') do |filename|
19
+ options.config_filename = filename
20
+ end
21
+ opts.on_tail("-h", "--help", "Show this message") do
22
+ puts opts
23
+ exit
24
+ end
20
25
  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
26
 
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]."
27
+ # parse the options
28
+ begin
29
+ opts.parse!(args)
30
+ rescue OptionParser::MissingArgument => e
31
+ # if an --option is missing it's argument
32
+ puts e.message.capitalize + "\n\n"
33
+ puts opts
34
+ exit 1
73
35
  end
74
- end
75
36
 
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}"
37
+ # validation of command line arguments
38
+ @errors = []
39
+ # check that recipients file exists
40
+ if options.recipients_filename
41
+ unless File.exists?(options.recipients_filename)
42
+ @errors << "The specified recipients file doesn't exist!"
43
+ end
44
+ else
45
+ @errors << "You need to specify a recipients file with --recipients."
81
46
  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
47
 
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)
48
+ # check that config file exists
49
+ if options.config_filename
50
+ unless File.exists?(options.config_filename.to_s)
51
+ @errors << "The specified config file doesn't exist!"
52
+ end
165
53
  else
166
- @log.warning("Flapjack::Notifiers::#{notifier.to_s.capitalize} doesn't exist!")
54
+ options.config_filename = "/etc/flapjack/flapjack-notifier.conf"
55
+ unless File.exists?(options.config_filename)
56
+ @errors << "The default config file (#{options.config_filename}) doesn't exist."
57
+ @errors << "Please set one up, or specify one with --config."
58
+ end
167
59
  end
168
- end
60
+
169
61
 
170
- @notifiers
171
- end
62
+ # if there are errors, print them out and exit
63
+ if @errors.size > 0
64
+ puts "Errors:"
65
+ @errors.each do |error|
66
+ puts " - #{error}"
67
+ end
68
+ puts
69
+ puts opts
70
+ exit 2
71
+ end
72
+
73
+ # config loader
172
74
 
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
75
+ # holder for transport + persistence config
76
+ options.transport = OpenStruct.new
77
+ options.persistence = OpenStruct.new
197
78
 
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
79
+ config = Flapjack::Inifile.read(options.config_filename)
207
80
 
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
81
+ %w(transport persistence).each do |backend|
82
+ options.send("#{backend}=", config[backend].symbolize_keys)
83
+ end
216
84
 
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("Creating event for check '#{result.id}'")
228
- # FIXME: this will be a performance hit
229
- event = ::Event.new(:check_id => result.id)
230
- raise unless event.save
85
+ # base config (config.blah)
86
+ config['notifier'].each_pair do |key, value|
87
+ normalised_key = key.gsub('-', '_')
88
+ values = value.split(/,*\s+/)
89
+ options.send("#{normalised_key}=", values)
90
+ end
231
91
 
232
- @log.info("Notifying on check '#{result.id}'")
233
- @notifier.notify!(result, event)
92
+ # list of notifiers to load + their config
93
+ notifiers_to_load = options.notifiers
94
+ options.notifiers = {}
95
+ notifiers_to_load.each do |notifier|
96
+ options.notifiers[notifier] = config["#{notifier}-notifier"].symbolize_keys
234
97
  end
235
- end
236
98
 
237
- @log.info("Storing status of check in database")
238
- save_result(result)
99
+ # holder for recipients list
100
+ recipients = Flapjack::Inifile.read(options.recipients_filename)
101
+ options.recipients = recipients.all
239
102
 
240
- @log.debug("Deleting result for check '#{result.id}' from queue")
241
- result_job.delete
103
+ options
104
+ end
242
105
  end
243
-
244
106
  end
245
-
107
+
246
108
  end
@@ -6,7 +6,6 @@ require 'ostruct'
6
6
  require 'optparse'
7
7
  require 'log4r'
8
8
  require 'log4r/outputter/syslogoutputter'
9
- require 'flapjack/result'
10
9
  require 'flapjack/patches'
11
10
 
12
11
  module Flapjack
@@ -48,89 +47,5 @@ module Flapjack
48
47
  options
49
48
  end
50
49
  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
50
  end
136
-
51
+
@@ -0,0 +1,14 @@
1
+ module Flapjack
2
+ module Filters
3
+ class AnyParentsFailed
4
+ def initialize(opts={})
5
+ @log = opts[:log]
6
+ @persistence = opts[:persistence]
7
+ end
8
+
9
+ def block?(result)
10
+ @persistence.any_parents_failed?(result)
11
+ end
12
+ end
13
+ end
14
+ end