flapjack 0.5.1 → 0.5.3

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/.gitignore +16 -0
  2. data/Rakefile +72 -32
  3. data/VERSION +1 -0
  4. data/bin/flapjack-netsaint-parser +433 -0
  5. data/bin/flapjack-populator +29 -0
  6. data/bin/flapjack-stats +10 -5
  7. data/{etc → dist/etc}/default/flapjack-notifier +0 -0
  8. data/{etc → dist/etc}/default/flapjack-workers +0 -0
  9. data/{etc → dist/etc}/flapjack/flapjack-notifier.conf.example +0 -0
  10. data/{etc → dist/etc}/flapjack/recipients.conf.example +0 -0
  11. data/{etc → dist/etc}/init.d/flapjack-notifier +0 -0
  12. data/{etc → dist/etc}/init.d/flapjack-workers +7 -7
  13. data/dist/puppet/flapjack/files/.stub +0 -0
  14. data/dist/puppet/flapjack/manifests/common.pp +61 -0
  15. data/dist/puppet/flapjack/manifests/notifier.pp +13 -0
  16. data/dist/puppet/flapjack/manifests/worker.pp +13 -0
  17. data/dist/puppet/flapjack/templates/.stub +0 -0
  18. data/dist/puppet/ruby/manifests/dev.pp +5 -0
  19. data/dist/puppet/ruby/manifests/rubygems.pp +14 -0
  20. data/dist/puppet/sqlite3/manifests/dev.pp +5 -0
  21. data/features/netsaint-config-converter.feature +126 -0
  22. data/features/steps/flapjack-importer_steps.rb +112 -0
  23. data/features/steps/flapjack-netsaint-parser_steps.rb +51 -0
  24. data/features/steps/flapjack-worker-manager_steps.rb +6 -8
  25. data/features/support/env.rb +22 -19
  26. data/flapjack.gemspec +186 -23
  27. data/lib/flapjack.rb +4 -0
  28. data/spec/check_sandbox/echo +3 -0
  29. data/spec/check_sandbox/sandboxed_check +5 -0
  30. data/spec/configs/flapjack-notifier-couchdb.ini +25 -0
  31. data/spec/configs/flapjack-notifier.ini +39 -0
  32. data/spec/configs/recipients.ini +14 -0
  33. data/spec/helpers.rb +15 -0
  34. data/spec/inifile_spec.rb +66 -0
  35. data/spec/mock-notifiers/mock/init.rb +3 -0
  36. data/spec/mock-notifiers/mock/mock.rb +19 -0
  37. data/spec/notifier-directories/spoons/testmailer/init.rb +20 -0
  38. data/spec/notifier_application_spec.rb +222 -0
  39. data/spec/notifier_filters_spec.rb +52 -0
  40. data/spec/notifier_options_multiplexer_spec.rb +71 -0
  41. data/spec/notifier_options_spec.rb +115 -0
  42. data/spec/notifier_spec.rb +57 -0
  43. data/spec/notifiers/mailer_spec.rb +36 -0
  44. data/spec/notifiers/xmpp_spec.rb +36 -0
  45. data/spec/persistence/datamapper_spec.rb +74 -0
  46. data/spec/persistence/mock_persistence_backend.rb +26 -0
  47. data/spec/simple.ini +6 -0
  48. data/spec/spec.opts +4 -0
  49. data/spec/test-filters/blocker.rb +13 -0
  50. data/spec/test-filters/mock.rb +13 -0
  51. data/spec/transports/beanstalkd_spec.rb +44 -0
  52. data/spec/transports/mock_transport.rb +58 -0
  53. data/spec/worker_application_spec.rb +62 -0
  54. data/spec/worker_options_spec.rb +83 -0
  55. metadata +166 -47
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ *~
3
+ .#*
4
+ *.swp
5
+ .bzr
6
+ .bzrignore
7
+ gems/specifications/*
8
+ gems/gems/*
9
+ gems/bin/*
10
+ recipients.yaml
11
+ .yardoc
12
+ #doc/*
13
+ pkg/*
14
+ !etc/flapjack/recipients.yaml
15
+ spec/test.db
16
+ features/support/tmp/*
data/Rakefile CHANGED
@@ -1,11 +1,64 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
4
  require 'fileutils'
5
5
  require 'spec/rake/spectask'
6
+ require 'rake'
7
+
8
+ #
9
+ # Release management
10
+ #
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "flapjack"
15
+ gem.summary = %Q{a scalable and distributed monitoring system}
16
+ gem.description = %Q{lapjack is highly scalable and distributed monitoring system. It understands the Nagios plugin format, and can easily be scaled from 1 server to 1000.}
17
+ gem.email = "lindsay@holmwood.id.au"
18
+ gem.homepage = "http://flapjack-project.com/"
19
+ gem.authors = ["Lindsay Holmwood"]
20
+ gem.has_rdoc = false
21
+
22
+ gem.add_dependency('daemons', '= 1.0.10')
23
+ gem.add_dependency('beanstalk-client', '= 1.0.2')
24
+ gem.add_dependency('log4r', '= 1.1.5')
25
+ gem.add_dependency('xmpp4r', '= 0.5')
26
+ gem.add_dependency('tmail', '= 1.2.3.1')
27
+ gem.add_dependency('yajl-ruby', '= 0.6.4')
28
+ gem.add_dependency('sqlite3-ruby', '= 1.2.5')
29
+
30
+ # Don't release unsanitised NetSaint config into gem.
31
+ gem.files.exclude('features/support/data/etc/netsaint')
32
+ end
33
+ Jeweler::GemcutterTasks.new
34
+ rescue LoadError
35
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
36
+ end
6
37
 
7
- # integration tests for cli utils
8
- begin
38
+ #
39
+ # Testing
40
+ #
41
+ require 'rake/testtask'
42
+ Rake::TestTask.new(:test) do |test|
43
+ test.libs << 'lib' << 'test'
44
+ test.pattern = 'test/**/test_*.rb'
45
+ test.verbose = true
46
+ end
47
+
48
+ begin
49
+ require 'rcov/rcovtask'
50
+ Rcov::RcovTask.new do |test|
51
+ test.libs << 'test'
52
+ test.pattern = 'test/**/test_*.rb'
53
+ test.verbose = true
54
+ end
55
+ rescue LoadError
56
+ task :rcov do
57
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
58
+ end
59
+ end
60
+
61
+ begin
9
62
  require 'cucumber/rake/task'
10
63
 
11
64
  Cucumber::Rake::Task.new do |task|
@@ -17,38 +70,25 @@ end
17
70
  Spec::Rake::SpecTask.new do |t|
18
71
  t.spec_opts = ["--options", "spec/spec.opts"]
19
72
  end
73
+ task :test => :check_dependencies
20
74
 
75
+ task :default => :test
21
76
 
22
- desc "generate list of files for gemspec"
23
- task "gengemfiles" do
24
- executables = `git ls-files bin/*`.split.map {|bin| bin.gsub(/^bin\//, '')}
25
- files = `git ls-files`.split.delete_if {|file| file =~ /^(spec\/|\.gitignore)/}
26
- puts
27
- puts "Copy and paste into flapjack.gemspec:"
28
- puts
29
- puts " s.executables = #{executables.inspect}"
30
- puts " s.files = #{files.inspect}"
31
- puts
32
- puts
33
- end
77
+ require 'rake/rdoctask'
78
+ Rake::RDocTask.new do |rdoc|
79
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
34
80
 
35
- desc "build gem"
36
- task :build do
37
- system("gem build flapjack.gemspec")
38
-
39
- FileUtils.mkdir_p('pkg')
40
- puts
41
- puts "Flapjack gems:"
42
- Dir.glob("flapjack-*.gem").each do |gem|
43
- dest = File.join('pkg', gem)
44
- FileUtils.mv gem, dest
45
- puts " " + dest
46
- end
81
+ rdoc.rdoc_dir = 'rdoc'
82
+ rdoc.title = "flapjack #{version}"
83
+ rdoc.rdoc_files.include('README*')
84
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
85
  end
48
86
 
49
-
87
+ #
88
+ # misc
89
+ #
50
90
  desc "display FIXMEs in the codebase"
51
- task :fixmes do
91
+ task :fixmes do
52
92
  output = `grep -nR FIXME lib/* spec/* bin/`
53
93
  output.split("\n").each do |line|
54
94
  parts = line.split(':')
@@ -58,7 +98,7 @@ task :fixmes do
58
98
  end
59
99
 
60
100
  desc "build a tarball suitable for building packages from"
61
- task :tarball do
101
+ task :tarball do
62
102
  require 'open-uri'
63
103
  require 'tmpdir'
64
104
  tmpdir = Dir.mktmpdir
@@ -109,7 +149,7 @@ task :tarball do
109
149
  # save file
110
150
  saved_file = "/tmp/#{details[:filename]}"
111
151
  File.open(saved_file, 'w') do |f|
112
- f << open(details[:source]).read
152
+ f << open(details[:source]).read
113
153
  end
114
154
 
115
155
  `tar zxf #{saved_file} -C #{tmpdir}`
@@ -127,7 +167,7 @@ task :tarball do
127
167
  end
128
168
 
129
169
  desc "dump out statements to create sqlite3 schema"
130
- task :dm_debug do
170
+ task :dm_debug do
131
171
  require 'lib/flapjack/persistence/data_mapper'
132
172
 
133
173
  DataMapper.logger.set_log(STDOUT, :debug)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.3
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require 'rubygems'
5
+ require 'yajl/json_gem'
6
+
7
+ module Netsaint
8
+ class Service
9
+ def self.all
10
+ @services = []
11
+
12
+ Netsaint::Config.services.each do |id, attributes|
13
+ attributes.each do |attrs|
14
+ @services << self.new(attrs.merge("id" => id))
15
+ end
16
+ end
17
+
18
+ #@services[0..500]
19
+ @services
20
+ end
21
+
22
+ def initialize(opts={})
23
+ @attributes = opts
24
+
25
+ @attributes.keys.each do |key|
26
+ if key == "id" || !self.respond_to?(key.to_sym)
27
+ instance_eval <<-METHOD
28
+ def #{key}
29
+ @attributes["#{key}"]
30
+ end
31
+ METHOD
32
+ end
33
+ end
34
+ end
35
+
36
+ def flapjack_id
37
+ "#{id}-#{description.downcase.gsub(' ', '_')}"
38
+ end
39
+
40
+ def check_command
41
+ parts = @attributes["check_command"].split('!')
42
+ command_id = parts.first
43
+ arguments = parts[1..-1]
44
+
45
+ attrs = { "arguments" => arguments,
46
+ "hostname" => self.id }
47
+ #command = ::Netsaint::Command.get(command_id)
48
+ #command.attributes = attrs
49
+ #command.expand_command_line
50
+ "exit 0"
51
+ end
52
+
53
+ def to_flapjack
54
+ {"id" => flapjack_id, "command" => check_command, "interval" => check_interval.to_i * 60 }
55
+ end
56
+ end
57
+
58
+ class Command
59
+ def self.all
60
+ @commands = []
61
+
62
+ Netsaint::Config.commands.each do |id, attributes|
63
+ attributes.each do |attrs|
64
+ @commands << self.new(attrs.merge("id" => id))
65
+ end
66
+ end
67
+
68
+ @commands
69
+ end
70
+
71
+ def self.get(id)
72
+ attrs = Netsaint::Config.commands[id].first
73
+ command = attrs.merge({"id" => id})
74
+
75
+ self.new(command)
76
+ end
77
+
78
+ def initialize(opts={})
79
+ @attributes = opts
80
+
81
+ @attributes.keys.each do |key|
82
+ if key == "id" || !self.respond_to?(key.to_sym)
83
+ instance_eval <<-METHOD
84
+ def #{key}
85
+ @attributes["#{key}"]
86
+ end
87
+ METHOD
88
+ end
89
+ end
90
+ end
91
+
92
+ def attributes=(attrs)
93
+ @attributes.merge!(attrs)
94
+ end
95
+
96
+ def expand_command_line
97
+ @attributes["command_line"].gsub(/\$\w+\$/) do |string|
98
+ lookup_macro(string)
99
+ end
100
+ end
101
+
102
+ def lookup_macro(string)
103
+ case string
104
+ when /^\$USER\d+\$$/
105
+ ::Netsaint::Config.fetch(string)[string]
106
+ when /^\$ARG(\d+)\$$/
107
+ index = $1.to_i - 1
108
+ @attributes["arguments"][index]
109
+ when "$HOSTNAME$"
110
+ @attributes["hostname"]
111
+ when "$HOSTADDRESS$"
112
+ hostname = @attributes["hostname"]
113
+ ::Netsaint::Host.get(hostname).address
114
+ else
115
+ raise "Don't know how to handle the #{string} macro!"
116
+ end
117
+ end
118
+ end
119
+
120
+ class Host
121
+ def self.get(id)
122
+ attrs = Netsaint::Config.hosts[id].first
123
+ host = attrs.merge({"id" => id})
124
+
125
+ self.new(host)
126
+ end
127
+
128
+ def initialize(opts={})
129
+ @attributes = opts
130
+
131
+ @attributes.keys.each do |key|
132
+ if key == "id" || !self.respond_to?(key.to_sym)
133
+ instance_eval <<-METHOD
134
+ def #{key}
135
+ @attributes["#{key}"]
136
+ end
137
+ METHOD
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ class ConfigFile
144
+ attr_accessor :filename, :config
145
+
146
+ def initialize(filename, opts={})
147
+ @filename = filename
148
+ @config = {}
149
+ end
150
+
151
+ def read
152
+ @raw_cfg = File.readlines(@filename).grep(/^[^#]/).map {|l| l.strip.empty? ? nil : l.strip }.compact
153
+ end
154
+
155
+ def extract(keyname, opts={}, &blk)
156
+ opts[:delete] ||= true
157
+ attrs = @config.find_all {|k, v| k =~ /^#{keyname}\[/ }
158
+ extracted = {}
159
+
160
+ attrs.each do |key, value|
161
+ @config.delete(key) if opts[:delete]
162
+ name = key[/#{keyname}\[(.+)\]/, 1]
163
+ extracted[name] ||= []
164
+ if value.class == Array
165
+ value.each { |v| extracted[name] << v }
166
+ else
167
+ extracted[name] << value
168
+ end
169
+ end
170
+
171
+
172
+ if block_given?
173
+ yielded = extracted.map do |key, value|
174
+ key, value = yield key, value
175
+ [ key, value ]
176
+ end
177
+ extracted = Hash[yielded]
178
+ end
179
+
180
+ extracted
181
+ end
182
+
183
+ def build_timeperiods
184
+ timeperiods = extract "timeperiod" do |key, value|
185
+ value.map! do |v|
186
+ parts = v.split(';')
187
+ { "timeperiod_alias" => parts[1], "sun_timeranges" => parts[2],
188
+ "mon_timeranges" => parts[3], "tue_timeranges" => parts[4],
189
+ "wed_timeranges" => parts[5], "thu_timeranges" => parts[6],
190
+ "fri_timeranges" => parts[7], "sat_timeranges" => parts[8] }
191
+ end
192
+ [ key, value ]
193
+ end
194
+
195
+ @config["timeperiods"] = timeperiods
196
+ end
197
+
198
+ def build_services
199
+ services = extract "service" do |key, value|
200
+ value.map! do |v|
201
+ parts = v.split(';')
202
+ { "description" => parts[0], "volatile" => parts[1],
203
+ "check_period" => parts[2], "max_attempts" => parts[3],
204
+ "check_interval" => parts[4], "retry_interval" => parts[5],
205
+ "notification_group" => parts[6], "notification_interval" => parts[7],
206
+ "notification_period" => parts[8], "notify_recovery" => parts[9],
207
+ "notify_critical" => parts[10], "notify_warning" => parts[11],
208
+ "event_hander" => parts[12], "check_command" => parts[13] }
209
+ end
210
+ [ key, value ]
211
+ end
212
+
213
+ @config["services"] = services
214
+ end
215
+
216
+ def build_contactgroups
217
+ contactgroups = extract "contactgroup" do |key, value|
218
+ value.map! do |v|
219
+ parts = v.split(';')
220
+ { "group_alias" => parts[0], "contacts" => parts[1] }
221
+ end
222
+ [ key, value ]
223
+ end
224
+
225
+ @config["contactgroups"] = contactgroups
226
+ end
227
+
228
+ def build_contacts
229
+ contacts = extract "contact" do |key, value|
230
+ value.map! do |v|
231
+ parts = v.split(';')
232
+ { "contact_alias" => parts[0], "svc_notification_period" => parts[1],
233
+ "host_notification_period" => parts[2], "notify_service_recovery" => parts[3],
234
+ "notify_service_critical" => parts[4], "notify_service_warning" => parts[5],
235
+ "notify_host_recovery" => parts[6], "notify_host_down" => parts[7],
236
+ "notify_host_unreachable" => parts[8], "service_notify_commands" => parts[9],
237
+ "host_notify_commands" => parts[10], "email_address" => parts[11],
238
+ "pager" => parts[12] }
239
+ end
240
+ [ key, value ]
241
+ end
242
+
243
+ @config["contacts"] = contacts
244
+ end
245
+
246
+ def build_hosts
247
+ hosts = extract "host" do |key, value|
248
+ value.map! do |v|
249
+ parts = v.split(';')
250
+ { "host_alias" => parts[0], "address" => parts[1],
251
+ "parent_hosts" => parts[2], "host_check_command" => parts[3],
252
+ "max_attempts" => parts[4], "notification_interval" => parts[5],
253
+ "notification_period" => parts[6], "notify_recovery" => parts[7],
254
+ "notify_down" => parts[8], "notify_unreachable" => parts[9],
255
+ "event_handler" => parts[10] }
256
+ end
257
+ [ key, value]
258
+ end
259
+
260
+ @config["hosts"] = hosts
261
+ end
262
+
263
+ def build_commands
264
+ commands = extract "command" do |key, value|
265
+ value.map! do |v|
266
+ parts = v.split(';')
267
+ { "command_line" => parts[0] }
268
+ end
269
+ [ key, value]
270
+ end
271
+
272
+ @config["commands"] = commands
273
+ end
274
+
275
+ def to_hash
276
+ self.read unless @raw_cfg
277
+
278
+ @raw_cfg.each do |o|
279
+ parts = o.scan(/^([^=]+)=(.+)$/).flatten
280
+ key = parts.first
281
+ value = parts.last
282
+ case
283
+ when @config[key] && @config[key].class == Array
284
+ @config[key] << value
285
+ when @config[key]
286
+ @config[key] = [ @config[key] ]
287
+ else
288
+ @config[key] = value
289
+ end
290
+ end
291
+
292
+ build_timeperiods
293
+ build_services
294
+ build_contactgroups
295
+ build_contacts
296
+ build_hosts
297
+ build_commands
298
+
299
+ @config
300
+ end
301
+
302
+ end
303
+
304
+ class Config
305
+ include Enumerable
306
+
307
+ class << self
308
+ attr_accessor :root, :configs, :files
309
+
310
+ @@configs = []
311
+ @@files = []
312
+
313
+ def root=(arg)
314
+ @@root = arg
315
+ @@root = Pathname.new(@@root) unless @@root.class == Pathname
316
+ end
317
+
318
+ def build
319
+ filename = @@root.join('netsaint.cfg')
320
+ build_file(filename)
321
+ end
322
+
323
+ def build_file(filename)
324
+ file = ConfigFile.new(filename)
325
+ hash = file.to_hash
326
+ @@files << file
327
+ @@configs << hash
328
+ %w(cfg_file resource_file).each do |linked_file|
329
+ if hash[linked_file]
330
+ hash[linked_file].each do |name|
331
+ path = relativise_path(name)
332
+ build_file(path)
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ def relativise_path(filename)
339
+ @@match = nil
340
+ parent = @@root.dirname.dirname
341
+ path = parent + filename.to_s[1..-1]
342
+
343
+ path
344
+ end
345
+
346
+ # Makes Enumerable mixin work
347
+ def each
348
+ @@configs.each {|i| yield i }
349
+ end
350
+
351
+ # Search across all configs for a particular key.
352
+ #
353
+ # Can be passed a string:
354
+ #
355
+ # Netsaint::Config.fetch("command_check_interval") # => {"command_check_interval" => "1"}
356
+ #
357
+ # Or a regex:
358
+ #
359
+ # Netsaint::Config.fetch(/^\$USER\d+\$$/) # => { "$USER2$"=>"/tmp", "$USER1$"=>"/boot" }
360
+ #
361
+ def fetch(key)
362
+ configs = @@configs.find_all {|c| c.keys.find {|k| k[k]} }
363
+ results = configs.map {|c| Hash[c.find_all { |k,v| k[key] }] }
364
+ results.inject {|final, hash| final.merge(hash) }
365
+ end
366
+
367
+ %w(timeperiods services contactgroups contacts hosts commands).each do |method|
368
+ class_eval <<-METHOD
369
+ def #{method}
370
+ #{method} = {}
371
+ @@configs.each do |c|
372
+ c["#{method}"].each do |key, value|
373
+ #{method}[key] ||= []
374
+ #{method}[key] << value
375
+ #{method}[key].flatten!
376
+ end
377
+ end
378
+
379
+ #{method}
380
+ end
381
+ METHOD
382
+ end
383
+
384
+ end
385
+ end
386
+ end
387
+
388
+ command = ARGV[0]
389
+
390
+ case command
391
+ when "print"
392
+ options = ARGV[2..-1]
393
+ options = Hash[options.map {|o| o.scan(/--(.+)=(.+)/).flatten }]
394
+
395
+ netsaint_root = Pathname.new(options["source"]).expand_path
396
+ Netsaint::Config.root = netsaint_root
397
+ Netsaint::Config.build
398
+
399
+ command = ARGV[0]
400
+ type = ARGV[1]
401
+
402
+ json = { type => Netsaint::Config.method(type).call }.to_json
403
+
404
+ if filename = options["to"]
405
+ File.open(filename, 'w') do |f|
406
+ f << json
407
+ end
408
+ else
409
+ puts json
410
+ end
411
+
412
+ when "dump"
413
+ options = ARGV[1..-1]
414
+ options = Hash[options.map {|o| o.scan(/--(.+)=(.+)/).flatten }]
415
+
416
+ netsaint_root = Pathname.new(options["source"]).expand_path
417
+ Netsaint::Config.root = netsaint_root
418
+ Netsaint::Config.build
419
+
420
+ @checks = Netsaint::Service.all.map { |service| service.to_flapjack }
421
+
422
+ json = { "batch" => {"id" => 1, "created_at" => Time.now}, "checks" => @checks }.to_json
423
+
424
+ if filename = options["to"]
425
+ File.open(filename, 'w') do |f|
426
+ f << json
427
+ end
428
+ puts "Dumped config to #{filename}"
429
+ else
430
+ puts json
431
+ end
432
+
433
+ end