flapjack 0.5.1 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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