flapjack 0.6.43 → 0.6.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/bin/flapjack +4 -2
  2. data/bin/flapjack-nagios-receiver +4 -1
  3. data/bin/flapjack-populator +4 -1
  4. data/etc/flapjack_config.yaml.example +111 -106
  5. data/features/steps/notifications_steps.rb +6 -6
  6. data/lib/flapjack/configuration.rb +76 -24
  7. data/lib/flapjack/coordinator.rb +27 -44
  8. data/lib/flapjack/data/entity.rb +28 -7
  9. data/lib/flapjack/data/entity_check.rb +18 -20
  10. data/lib/flapjack/executive.rb +5 -4
  11. data/lib/flapjack/gateways/api.rb +391 -0
  12. data/lib/flapjack/gateways/api/entity_check_presenter.rb +185 -0
  13. data/lib/flapjack/gateways/api/entity_presenter.rb +70 -0
  14. data/lib/flapjack/gateways/base.rb +38 -0
  15. data/lib/flapjack/{notification → gateways}/email.rb +4 -5
  16. data/lib/flapjack/{notification → gateways}/email/alert.html.haml +0 -0
  17. data/lib/flapjack/{notification → gateways}/email/alert.text.erb +0 -0
  18. data/lib/flapjack/gateways/jabber.rb +387 -0
  19. data/lib/flapjack/gateways/oobetet.rb +241 -0
  20. data/lib/flapjack/gateways/pagerduty.rb +247 -0
  21. data/lib/flapjack/{notification → gateways}/sms.rb +5 -6
  22. data/lib/flapjack/{notification → gateways}/sms/messagenet.rb +1 -1
  23. data/lib/flapjack/gateways/web.rb +293 -0
  24. data/lib/flapjack/{web → gateways/web}/views/_css.haml +0 -0
  25. data/lib/flapjack/{web → gateways/web}/views/_nav.haml +0 -0
  26. data/lib/flapjack/{web → gateways/web}/views/check.haml +0 -0
  27. data/lib/flapjack/{web → gateways/web}/views/contact.haml +0 -0
  28. data/lib/flapjack/{web → gateways/web}/views/contacts.haml +0 -0
  29. data/lib/flapjack/{web → gateways/web}/views/index.haml +0 -0
  30. data/lib/flapjack/{web → gateways/web}/views/self_stats.haml +0 -0
  31. data/lib/flapjack/pikelet.rb +0 -23
  32. data/lib/flapjack/version.rb +1 -1
  33. data/spec/lib/flapjack/coordinator_spec.rb +56 -36
  34. data/spec/lib/flapjack/data/entity_spec.rb +53 -4
  35. data/spec/lib/flapjack/{api → gateways/api}/entity_check_presenter_spec.rb +10 -13
  36. data/spec/lib/flapjack/{api → gateways/api}/entity_presenter_spec.rb +10 -10
  37. data/spec/lib/flapjack/{api_spec.rb → gateways/api_spec.rb} +14 -14
  38. data/spec/lib/flapjack/gateways/email_spec.rb +6 -0
  39. data/spec/lib/flapjack/{jabber_spec.rb → gateways/jabber_spec.rb} +9 -9
  40. data/spec/lib/flapjack/{oobetet_spec.rb → gateways/oobetet_spec.rb} +10 -10
  41. data/spec/lib/flapjack/{pagerduty_spec.rb → gateways/pagerduty_spec.rb} +11 -11
  42. data/spec/lib/flapjack/gateways/sms_spec.rb +6 -0
  43. data/spec/lib/flapjack/{web_spec.rb → gateways/web_spec.rb} +4 -4
  44. metadata +46 -79
  45. data/bin/install-flapjack-systemwide +0 -58
  46. data/features/steps/flapjack-importer_steps.rb +0 -109
  47. data/features/steps/flapjack-worker_steps.rb +0 -68
  48. data/lib/flapjack/api.rb +0 -388
  49. data/lib/flapjack/api/entity_check_presenter.rb +0 -181
  50. data/lib/flapjack/api/entity_presenter.rb +0 -66
  51. data/lib/flapjack/cli/worker_manager.rb +0 -46
  52. data/lib/flapjack/inifile.rb +0 -44
  53. data/lib/flapjack/jabber.rb +0 -383
  54. data/lib/flapjack/notifier_engine.rb +0 -40
  55. data/lib/flapjack/notifiers/mailer/init.rb +0 -3
  56. data/lib/flapjack/notifiers/mailer/mailer.rb +0 -51
  57. data/lib/flapjack/notifiers/xmpp/init.rb +0 -3
  58. data/lib/flapjack/notifiers/xmpp/xmpp.rb +0 -46
  59. data/lib/flapjack/oobetet.rb +0 -240
  60. data/lib/flapjack/pagerduty.rb +0 -242
  61. data/lib/flapjack/web.rb +0 -286
  62. data/spec.old/check_sandbox/echo +0 -3
  63. data/spec.old/check_sandbox/sandboxed_check +0 -5
  64. data/spec.old/configs/flapjack-notifier-couchdb.ini +0 -25
  65. data/spec.old/configs/flapjack-notifier.ini +0 -39
  66. data/spec.old/configs/recipients.ini +0 -14
  67. data/spec.old/helpers.rb +0 -15
  68. data/spec.old/inifile_spec.rb +0 -66
  69. data/spec.old/mock-notifiers/mock/init.rb +0 -3
  70. data/spec.old/mock-notifiers/mock/mock.rb +0 -19
  71. data/spec.old/notifier-directories/spoons/testmailer/init.rb +0 -20
  72. data/spec.old/notifier_application_spec.rb +0 -222
  73. data/spec.old/notifier_filters_spec.rb +0 -52
  74. data/spec.old/notifier_options_multiplexer_spec.rb +0 -71
  75. data/spec.old/notifier_options_spec.rb +0 -115
  76. data/spec.old/notifier_spec.rb +0 -57
  77. data/spec.old/notifiers/mailer_spec.rb +0 -36
  78. data/spec.old/notifiers/xmpp_spec.rb +0 -36
  79. data/spec.old/persistence/datamapper_spec.rb +0 -74
  80. data/spec.old/persistence/mock_persistence_backend.rb +0 -26
  81. data/spec.old/simple.ini +0 -6
  82. data/spec.old/spec.opts +0 -4
  83. data/spec.old/test-filters/blocker.rb +0 -13
  84. data/spec.old/test-filters/mock.rb +0 -13
  85. data/spec.old/transports/beanstalkd_spec.rb +0 -44
  86. data/spec.old/transports/mock_transport.rb +0 -58
  87. data/spec.old/worker_application_spec.rb +0 -62
  88. data/spec.old/worker_options_spec.rb +0 -83
  89. data/spec/lib/flapjack/notification/email_spec.rb +0 -6
  90. data/spec/lib/flapjack/notification/sms_spec.rb +0 -6
@@ -11,18 +11,14 @@ require 'em-resque'
11
11
  require 'em-resque/worker'
12
12
  require 'thin'
13
13
 
14
+ require 'flapjack/configuration'
14
15
  require 'flapjack/patches'
15
-
16
- require 'flapjack/api'
17
16
  require 'flapjack/daemonizing'
18
17
  require 'flapjack/executive'
19
- require 'flapjack/jabber'
20
- require 'flapjack/oobetet'
21
- require 'flapjack/pagerduty'
22
- require 'flapjack/notification/email'
23
- require 'flapjack/notification/sms'
24
18
  require 'flapjack/redis_pool'
25
- require 'flapjack/web'
19
+
20
+ require 'flapjack/pikelet'
21
+ require 'flapjack/gateways/base'
26
22
 
27
23
  module Flapjack
28
24
 
@@ -30,9 +26,9 @@ module Flapjack
30
26
 
31
27
  include Flapjack::Daemonizable
32
28
 
33
- def initialize(config, redis_options)
29
+ def initialize(config)
34
30
  @config = config
35
- @redis_options = redis_options
31
+ @redis_options = config.for_redis
36
32
  @pikelets = []
37
33
 
38
34
  @logger = Log4r::Logger.new("flapjack-coordinator")
@@ -62,31 +58,19 @@ module Flapjack
62
58
 
63
59
  private
64
60
 
65
- # map from config key to pikelet class
66
- PIKELET_TYPES = {'executive' => Flapjack::Executive,
67
- 'jabber_gateway' => Flapjack::Jabber,
68
- 'pagerduty_gateway' => Flapjack::Pagerduty,
69
- 'oobetet' => Flapjack::Oobetet,
70
-
71
- 'web' => Flapjack::Web,
72
- 'api' => Flapjack::API,
73
-
74
- 'email_notifier' => Flapjack::Notification::Email,
75
- 'sms_notifier' => Flapjack::Notification::Sms}
76
-
77
61
  def run(options = {})
78
62
 
79
63
  EM.synchrony do
80
- @logger.debug "config keys: #{@config.keys}"
81
64
 
82
- @config.keys.each do |pikelet_type|
83
- next unless PIKELET_TYPES.keys.include?(pikelet_type) &&
84
- @config[pikelet_type].is_a?(Hash) &&
85
- @config[pikelet_type]['enabled']
86
- @logger.debug "coordinator is now initialising the #{pikelet_type} pikelet"
87
- pikelet_cfg = @config[pikelet_type]
65
+ @config.pikelets.each_pair do |pikelet_class, pikelet_cfg|
66
+ next unless pikelet_cfg['enabled']
67
+ build_pikelet(pikelet_class, pikelet_cfg)
68
+ end
88
69
 
89
- build_pikelet(pikelet_type, pikelet_cfg)
70
+ @config.gateways.each_pair do |gateway_class, gateway_cfg|
71
+ # TODO split out gateway logic to build_gateway
72
+ next unless gateway_cfg['enabled']
73
+ build_pikelet(gateway_class, gateway_cfg)
90
74
  end
91
75
 
92
76
  setup_signals if @signals
@@ -106,8 +90,8 @@ module Flapjack
106
90
  end
107
91
  end
108
92
 
109
- def build_pikelet(pikelet_type, pikelet_cfg)
110
- return unless pikelet_class = PIKELET_TYPES[pikelet_type]
93
+ def build_pikelet(pikelet_class, pikelet_cfg)
94
+ @logger.debug "coordinator is now initialising the #{pikelet_class} pikelet"
111
95
 
112
96
  inc_mod = pikelet_class.included_modules
113
97
  ext_mod = extended_modules(pikelet_class)
@@ -118,11 +102,10 @@ module Flapjack
118
102
  if inc_mod.include?(Flapjack::GenericPikelet)
119
103
  pikelet = pikelet_class.new
120
104
  pikelet.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
121
-
122
105
  else
123
106
  pikelet_class.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
124
107
 
125
- if ext_mod.include?(Flapjack::ThinPikelet)
108
+ if ext_mod.include?(Flapjack::Gateways::Thin)
126
109
 
127
110
  unless @thin_silenced
128
111
  Thin::Logging.silent = true
@@ -133,7 +116,7 @@ module Flapjack
133
116
  pikelet_class.instance_variable_get('@port'),
134
117
  pikelet_class, :signals => false)
135
118
 
136
- elsif ext_mod.include?(Flapjack::ResquePikelet)
119
+ elsif ext_mod.include?(Flapjack::Gateways::Resque)
137
120
 
138
121
  # set up connection pooling, stop resque errors
139
122
  unless @resque_pool
@@ -155,14 +138,14 @@ module Flapjack
155
138
  pikelet_info = {:class => pikelet_class, :instance => pikelet}
156
139
 
157
140
  if inc_mod.include?(Flapjack::GenericPikelet) ||
158
- ext_mod.include?(Flapjack::ResquePikelet)
141
+ ext_mod.include?(Flapjack::Gateways::Resque)
159
142
 
160
143
  fiber = Fiber.new {
161
144
  begin
162
145
  # Can't use local inc_mod/ext_mod variables in the new fiber
163
146
  if pikelet.is_a?(Flapjack::GenericPikelet)
164
147
  pikelet.main
165
- elsif extended_modules(pikelet_class).include?(Flapjack::ResquePikelet)
148
+ elsif extended_modules(pikelet_class).include?(Flapjack::Gateways::Resque)
166
149
  pikelet.work(0.1)
167
150
  end
168
151
  rescue Exception => e
@@ -174,10 +157,10 @@ module Flapjack
174
157
 
175
158
  pikelet_info[:fiber] = fiber
176
159
  fiber.resume
177
- @logger.debug "new fiber created for #{pikelet_type}"
178
- elsif ext_mod.include?(Flapjack::ThinPikelet)
160
+ @logger.debug "new fiber created for #{pikelet_class}"
161
+ elsif ext_mod.include?(Flapjack::Gateways::Thin)
179
162
  pikelet.start
180
- @logger.debug "new thin server instance started for #{pikelet_type}"
163
+ @logger.debug "new thin server instance started for #{pikelet_class}"
181
164
  end
182
165
 
183
166
  @pikelets << pikelet_info
@@ -187,7 +170,7 @@ module Flapjack
187
170
  # cause everything else to be spammy
188
171
  def health_check
189
172
  @pikelets.each do |pik|
190
- status = if extended_modules(pik[:class]).include?(Flapjack::ThinPikelet)
173
+ status = if extended_modules(pik[:class]).include?(Flapjack::Gateways::Thin)
191
174
  pik[:instance].backend.size > 0 ? 'running' : 'stopped'
192
175
  elsif pik[:fiber]
193
176
  pik[:fiber].alive? ? 'running' : 'stopped'
@@ -217,10 +200,10 @@ module Flapjack
217
200
  r.quit
218
201
  }.resume
219
202
  end
220
- elsif ext_mod.include?(Flapjack::ResquePikelet)
203
+ elsif ext_mod.include?(Flapjack::Gateways::Resque)
221
204
  # resque is polling, so we don't need a shutdown object
222
205
  pik_inst.shutdown if pik[:fiber] && pik[:fiber].alive?
223
- elsif ext_mod.include?(Flapjack::ThinPikelet)
206
+ elsif ext_mod.include?(Flapjack::Gateways::Thin)
224
207
  # drop from this side, as HTTP keepalive etc. means browsers
225
208
  # keep connections alive for ages, and we'd be hanging around
226
209
  # waiting for them to drop
@@ -247,7 +230,7 @@ module Flapjack
247
230
 
248
231
  pik_inst.cleanup
249
232
 
250
- elsif [Flapjack::ResquePikelet, Flapjack::ThinPikelet].any?{|fp|
233
+ elsif [Flapjack::Gateways::Resque, Flapjack::Gateways::Thin].any?{|fp|
251
234
  ext_mod.include?(fp)
252
235
  }
253
236
 
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'flapjack/data/contact'
4
+
3
5
  module Flapjack
4
6
 
5
7
  module Data
@@ -31,8 +33,9 @@ module Flapjack
31
33
 
32
34
  redis.del("contacts_for:#{entity['id']}")
33
35
  if entity['contacts'] && entity['contacts'].respond_to?(:each)
34
- entity['contacts'].each {|contact|
35
- redis.sadd("contacts_for:#{entity['id']}", contact)
36
+ entity['contacts'].each {|contact_id|
37
+ next if Flapjack::Data::Contact.find_by_id(contact_id, :redis => redis).nil?
38
+ redis.sadd("contacts_for:#{entity['id']}", contact_id)
36
39
  }
37
40
  end
38
41
  else
@@ -62,13 +65,31 @@ module Flapjack
62
65
  self.new(:name => entity_name, :id => entity_id, :redis => redis)
63
66
  end
64
67
 
68
+ # NB: if we're worried about user input, https://github.com/mudge/re2
69
+ # has bindings for a non-backtracking RE engine that runs in linear
70
+ # time
65
71
  def self.find_all_name_matching(pattern, options = {})
66
72
  raise "Redis connection not set" unless redis = options[:redis]
67
- matched_entities = redis.keys('check:*').collect {|check|
68
- a, entity, c = check.split(':')
69
- match = (entity =~ /#{pattern}/) ? entity : nil
70
- }
71
- matched_entities.compact.sort.uniq
73
+ redis.keys('entity_id:*').inject([]) {|memo, check|
74
+ a, entity_name = check.split(':')
75
+ if (entity_name =~ /#{pattern}/) && !memo.include?(entity_name)
76
+ memo << entity_name
77
+ end
78
+ memo
79
+ }.sort
80
+ end
81
+
82
+ def contacts
83
+ contact_ids = @redis.smembers("contacts_for:#{id}")
84
+
85
+ if @logger
86
+ @logger.debug("#{contact_ids.length} contact(s) for #{id} (#{name}): " +
87
+ contact_ids.length)
88
+ end
89
+
90
+ contact_ids.collect {|c_id|
91
+ Flapjack::Data::Contact.find_by_id(c_id, :redis => @redis)
92
+ }.compact
72
93
  end
73
94
 
74
95
  def check_list
@@ -4,6 +4,7 @@ require 'yajl/json_gem'
4
4
 
5
5
  require 'flapjack/patches'
6
6
 
7
+ require 'flapjack/data/contact'
7
8
  require 'flapjack/data/entity'
8
9
 
9
10
  # TODO might want to split the class methods out to a separate class, DAO pattern
@@ -49,7 +50,7 @@ module Flapjack
49
50
  end
50
51
 
51
52
  def entity_name
52
- @entity.name
53
+ entity.name
53
54
  end
54
55
 
55
56
  # takes a key "entity:check", returns true if the check is in unscheduled
@@ -74,24 +75,26 @@ module Flapjack
74
75
  }
75
76
  end
76
77
 
78
+ # TODO move to Event
77
79
  def create_acknowledgement(options = {})
78
80
  event = { 'type' => 'action',
79
81
  'state' => 'acknowledgement',
80
82
  'summary' => options['summary'],
81
83
  'duration' => options['duration'],
82
84
  'acknowledgement_id' => options['acknowledgement_id'],
83
- 'entity' => @entity.name,
84
- 'check' => @check
85
+ 'entity' => entity.name,
86
+ 'check' => check
85
87
  }
86
88
  Flapjack::Data::Event.add(event, :redis => @redis)
87
89
  end
88
90
 
91
+ # TODO move to Event
89
92
  def test_notifications(options = {})
90
93
  event = { 'type' => 'action',
91
94
  'state' => 'test_notifications',
92
95
  'summary' => options['summary'],
93
- 'entity' => @entity.name,
94
- 'check' => @check
96
+ 'entity' => entity.name,
97
+ 'check' => check
95
98
  }
96
99
  Flapjack::Data::Event.add(event, :redis => @redis)
97
100
  end
@@ -405,8 +408,6 @@ module Flapjack
405
408
  # has pagerduty credentials then there'll be one hash in the array for each set of
406
409
  # credentials.
407
410
  def pagerduty_credentials(options)
408
- # raise "Redis connection not set" unless redis = options[:redis]
409
-
410
411
  self.contacts.inject([]) {|ret, contact|
411
412
  cred = contact.pagerduty_credentials
412
413
  ret << cred if cred
@@ -417,28 +418,25 @@ module Flapjack
417
418
  # takes a check, looks up contacts that are interested in this check (or in the check's entity)
418
419
  # and returns an array of contact records
419
420
  def contacts
420
- entity = @entity
421
- check = @check
421
+ contact_ids = @redis.smembers("contacts_for:#{entity.id}:#{check}")
422
422
 
423
423
  if @logger
424
- @logger.debug("contacts for #{@entity.id} (#{@entity.name}): " +
425
- @redis.smembers("contacts_for:#{@entity.id}").length.to_s)
426
- @logger.debug("contacts for #{check}: " +
427
- @redis.smembers("contacts_for:#{check}").length.to_s)
424
+ @logger.debug("#{contact_ids.length} contact(s) for #{entity.id}:#{check}: " +
425
+ contact_ids.inspect)
428
426
  end
429
427
 
430
- union = @redis.sunion("contacts_for:#{@entity.id}", "contacts_for:#{check}")
431
- @logger.debug("contacts for union of #{@entity.id} and #{check}: " + union.length.to_s) if @logger
432
- union.collect {|c_id| Flapjack::Data::Contact.find_by_id(c_id, :redis => @redis) }
428
+ entity.contacts + contact_ids.collect {|c_id|
429
+ Flapjack::Data::Contact.find_by_id(c_id, :redis => @redis)
430
+ }.compact
433
431
  end
434
432
 
435
433
  private
436
434
 
437
- def initialize(entity, check, options = {})
435
+ def initialize(ent, che, options = {})
438
436
  raise "Redis connection not set" unless @redis = options[:redis]
439
- raise "Invalid entity" unless @entity = entity
440
- raise "Invalid check" unless @check = check
441
- @key = "#{entity.name}:#{check}"
437
+ raise "Invalid entity" unless @entity = ent
438
+ raise "Invalid check" unless @check = che
439
+ @key = "#{ent.name}:#{che}"
442
440
  end
443
441
 
444
442
  def validate_state(state)
@@ -14,11 +14,12 @@ require 'flapjack/data/contact'
14
14
  require 'flapjack/data/entity_check'
15
15
  require 'flapjack/data/notification'
16
16
  require 'flapjack/data/event'
17
- require 'flapjack/notification/sms'
18
- require 'flapjack/notification/email'
19
17
  require 'flapjack/pikelet'
20
18
  require 'flapjack/redis_pool'
21
19
 
20
+ require 'flapjack/gateways/email'
21
+ require 'flapjack/gateways/sms'
22
+
22
23
  module Flapjack
23
24
 
24
25
  class Executive
@@ -255,9 +256,9 @@ module Flapjack
255
256
  # TODO consider changing Resque jobs to use raw blpop like the others
256
257
  case media_type
257
258
  when :sms
258
- Resque.enqueue_to(@queues[:sms], Flapjack::Notification::Sms, contents)
259
+ Resque.enqueue_to(@queues[:sms], Flapjack::Gateways::Sms, contents)
259
260
  when :email
260
- Resque.enqueue_to(@queues[:email], Flapjack::Notification::Email, contents)
261
+ Resque.enqueue_to(@queues[:email], Flapjack::Gateways::Email, contents)
261
262
  when :jabber
262
263
  # TODO move next line up into other notif value setting above?
263
264
  contents['event_count'] = @event_count if @event_count
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A HTTP-based API server, which provides queries to determine the status of
4
+ # entities and the checks that are reported against them.
5
+ #
6
+ # There's a matching flapjack-diner gem at https://github.com/flpjck/flapjack-diner
7
+ # which consumes data from this API.
8
+
9
+ require 'time'
10
+
11
+ require 'async-rack'
12
+ require 'rack/fiber_pool'
13
+ require 'sinatra/base'
14
+
15
+ require 'flapjack/data/contact'
16
+ require 'flapjack/data/entity'
17
+ require 'flapjack/data/entity_check'
18
+
19
+ require 'flapjack/gateways/api/entity_presenter'
20
+ require 'flapjack/rack_logger'
21
+ require 'flapjack/redis_pool'
22
+
23
+ require 'flapjack/gateways/base'
24
+
25
+ # from https://github.com/sinatra/sinatra/issues/501
26
+ # TODO move to its own file
27
+ module Rack
28
+ class JsonParamsParser < Struct.new(:app)
29
+ def call(env)
30
+ if env['rack.input'] and not input_parsed?(env) and type_match?(env)
31
+ env['rack.request.form_input'] = env['rack.input']
32
+ data = env['rack.input'].read
33
+ env['rack.request.form_hash'] = data.empty?? {} : JSON.parse(data)
34
+ end
35
+ app.call(env)
36
+ end
37
+
38
+ def input_parsed? env
39
+ env['rack.request.form_input'].eql? env['rack.input']
40
+ end
41
+
42
+ def type_match? env
43
+ type = env['CONTENT_TYPE'] and
44
+ type.split(/\s*[;,]\s*/, 2).first.downcase == 'application/json'
45
+ end
46
+ end
47
+ end
48
+
49
+ module Flapjack
50
+
51
+ module Gateways
52
+
53
+ class API < Sinatra::Base
54
+
55
+ set :show_exceptions, false
56
+
57
+ if defined?(FLAPJACK_ENV) && 'test'.eql?(FLAPJACK_ENV)
58
+ # expose test errors properly
59
+ set :raise_errors, true
60
+ else
61
+ # doesn't work with Rack::Test unless we wrap tests in EM.synchrony blocks
62
+ rescue_exception = Proc.new { |env, exception|
63
+ logger.error exception.message
64
+ logger.error exception.backtrace.join("\n")
65
+ [503, {}, {:errors => [exception.message]}.to_json]
66
+ }
67
+
68
+ use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
69
+ end
70
+ use Rack::MethodOverride
71
+ use Rack::JsonParamsParser
72
+
73
+ class << self
74
+ include Flapjack::Gateways::Thin
75
+
76
+ attr_accessor :redis
77
+
78
+ alias_method :thin_bootstrap, :bootstrap
79
+ alias_method :thin_cleanup, :cleanup
80
+
81
+ def bootstrap(opts = {})
82
+ thin_bootstrap(opts)
83
+ @redis = Flapjack::RedisPool.new(:config => opts[:redis_config], :size => 1)
84
+
85
+ if config && config['access_log']
86
+ access_logger = Flapjack::RackLogger.new(config['access_log'])
87
+ use Rack::CommonLogger, access_logger
88
+ end
89
+ end
90
+
91
+ def cleanup
92
+ @redis.empty! if @redis
93
+ thin_cleanup
94
+ end
95
+
96
+ end
97
+
98
+ def redis
99
+ self.class.instance_variable_get('@redis')
100
+ end
101
+
102
+ def logger
103
+ self.class.instance_variable_get('@logger')
104
+ end
105
+
106
+ get '/entities' do
107
+ content_type :json
108
+ ret = Flapjack::Data::Entity.all(:redis => redis).sort_by(&:name).collect {|e|
109
+ {'id' => e.id, 'name' => e.name,
110
+ 'checks' => e.check_list.sort.collect {|c|
111
+ entity_check_status(e, c)
112
+ }
113
+ }
114
+ }
115
+ ret.to_json
116
+ end
117
+
118
+ get '/checks/:entity' do
119
+ content_type :json
120
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => redis)
121
+ if entity.nil?
122
+ status 404
123
+ return
124
+ end
125
+ entity.check_list.to_json
126
+ end
127
+
128
+ get %r{/status/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
129
+ content_type :json
130
+
131
+ entity_name = params[:captures][0]
132
+ check = params[:captures][1]
133
+
134
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
135
+ if entity.nil?
136
+ status 404
137
+ return
138
+ end
139
+
140
+ ret = if check
141
+ entity_check_status(entity, check)
142
+ else
143
+ entity.check_list.sort.collect {|c|
144
+ entity_check_status(entity, c)
145
+ }
146
+ end
147
+ ret.to_json
148
+ end
149
+
150
+ # the first capture group in the regex checks for acceptable
151
+ # characters in a domain name -- this will also match strings
152
+ # that aren't acceptable domain names as well, of course.
153
+ get %r{/outages/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
154
+ content_type :json
155
+
156
+ entity_name = params[:captures][0]
157
+ check = params[:captures][1]
158
+
159
+ entity = entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
160
+ if entity.nil?
161
+ status 404
162
+ return
163
+ end
164
+
165
+ start_time = validate_and_parsetime(params[:start_time])
166
+ end_time = validate_and_parsetime(params[:end_time])
167
+
168
+ presenter = if check
169
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
170
+ check, :redis => redis)
171
+ Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check)
172
+ else
173
+ Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis)
174
+ end
175
+
176
+ presenter.outages(start_time, end_time).to_json
177
+ end
178
+
179
+ get %r{/unscheduled_maintenances/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
180
+ content_type :json
181
+
182
+ entity_name = params[:captures][0]
183
+ check = params[:captures][1]
184
+
185
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
186
+ if entity.nil?
187
+ status 404
188
+ return
189
+ end
190
+
191
+ start_time = validate_and_parsetime(params[:start_time])
192
+ end_time = validate_and_parsetime(params[:end_time])
193
+
194
+ presenter = if check
195
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
196
+ check, :redis => redis)
197
+ Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check)
198
+ else
199
+ Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis)
200
+ end
201
+
202
+ presenter.unscheduled_maintenance(start_time, end_time).to_json
203
+ end
204
+
205
+ get %r{/scheduled_maintenances/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
206
+ content_type :json
207
+
208
+ entity_name = params[:captures][0]
209
+ check = params[:captures][1]
210
+
211
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
212
+ if entity.nil?
213
+ status 404
214
+ return
215
+ end
216
+
217
+ start_time = validate_and_parsetime(params[:start_time])
218
+ end_time = validate_and_parsetime(params[:end_time])
219
+
220
+ presenter = if check
221
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
222
+ check, :redis => redis)
223
+ Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check)
224
+ else
225
+ Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis)
226
+ end
227
+ presenter.scheduled_maintenance(start_time, end_time).to_json
228
+ end
229
+
230
+ get %r{/downtime/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
231
+ content_type :json
232
+
233
+ entity_name = params[:captures][0]
234
+ check = params[:captures][1]
235
+
236
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
237
+ if entity.nil?
238
+ status 404
239
+ return
240
+ end
241
+
242
+ start_time = validate_and_parsetime(params[:start_time])
243
+ end_time = validate_and_parsetime(params[:end_time])
244
+
245
+ presenter = if check
246
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
247
+ check, :redis => redis)
248
+ Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check)
249
+ else
250
+ Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis)
251
+ end
252
+
253
+ presenter.downtime(start_time, end_time).to_json
254
+ end
255
+
256
+ # create a scheduled maintenance period for a service on an entity
257
+ post '/scheduled_maintenances/:entity/:check' do
258
+ content_type :json
259
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => redis)
260
+ if entity.nil?
261
+ status 404
262
+ return
263
+ end
264
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
265
+ params[:check], :redis => redis)
266
+ entity_check.create_scheduled_maintenance(:start_time => params[:start_time],
267
+ :duration => params[:duration], :summary => params[:summary])
268
+ status 204
269
+ end
270
+
271
+ # create an acknowledgement for a service on an entity
272
+ # NB currently, this does not acknowledge a specific failure event, just
273
+ # the entity-check as a whole
274
+ post '/acknowledgements/:entity/:check' do
275
+ content_type :json
276
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => redis)
277
+ if entity.nil?
278
+ status 404
279
+ return
280
+ end
281
+
282
+ dur = params[:duration] ? params[:duration].to_i : nil
283
+ duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
284
+
285
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
286
+ params[:check], :redis => redis)
287
+ entity_check.create_acknowledgement('summary' => params[:summary],
288
+ 'duration' => duration)
289
+ status 204
290
+ end
291
+
292
+ post '/test_notifications/:entity/:check' do
293
+ content_type :json
294
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => redis)
295
+ if entity.nil?
296
+ status 404
297
+ return
298
+ end
299
+
300
+ summary = params[:summary] || "Testing notifications to all contacts interested in entity #{entity.name}"
301
+
302
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
303
+ params[:check], :redis => redis)
304
+ entity_check.test_notifications('summary' => summary)
305
+ status 204
306
+ end
307
+
308
+ post '/entities' do
309
+ pass unless 'application/json'.eql?(request.content_type)
310
+ content_type :json
311
+
312
+ errors = []
313
+ ret = nil
314
+
315
+ entities = params[:entities]
316
+ if entities && entities.is_a?(Enumerable) && entities.any? {|e| !e['id'].nil?}
317
+ entities.each do |entity|
318
+ unless entity['id']
319
+ errors << "Entity not imported as it has no id: #{entity.inspect}"
320
+ next
321
+ end
322
+ Flapjack::Data::Entity.add(entity, :redis => redis)
323
+ end
324
+ ret = 200
325
+ else
326
+ ret = 403
327
+ errors << "No valid entities were submitted"
328
+ end
329
+ errors.empty? ? ret : [ret, {}, {:errors => [errors]}.to_json]
330
+ end
331
+
332
+ post '/contacts' do
333
+ pass unless 'application/json'.eql?(request.content_type)
334
+ content_type :json
335
+
336
+ errors = []
337
+ ret = nil
338
+
339
+ contacts = params[:contacts]
340
+ if contacts && contacts.is_a?(Enumerable) && contacts.any? {|c| !c['id'].nil?}
341
+ Flapjack::Data::Contact.delete_all(:redis => redis)
342
+ contacts.each do |contact|
343
+ unless contact['id']
344
+ logger.warn "Contact not imported as it has no id: #{contact.inspect}"
345
+ next
346
+ end
347
+ Flapjack::Data::Contact.add(contact, :redis => redis)
348
+ end
349
+ ret = 200
350
+ else
351
+ ret = 403
352
+ errors << "No valid contacts were submitted"
353
+ end
354
+ errors.empty? ? ret : [ret, {}, {:errors => [errors]}.to_json]
355
+ end
356
+
357
+ not_found do
358
+ [404, {}, {:errors => ["Not found"]}.to_json]
359
+ end
360
+
361
+ private
362
+
363
+ def entity_check_status(entity, check)
364
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
365
+ check, :redis => redis)
366
+ return if entity_check.nil?
367
+ { 'name' => check,
368
+ 'state' => entity_check.state,
369
+ 'in_unscheduled_maintenance' => entity_check.in_unscheduled_maintenance?,
370
+ 'in_scheduled_maintenance' => entity_check.in_scheduled_maintenance?,
371
+ 'last_update' => entity_check.last_update,
372
+ 'last_problem_notification' => entity_check.last_problem_notification,
373
+ 'last_recovery_notification' => entity_check.last_recovery_notification,
374
+ 'last_acknowledgement_notification' => entity_check.last_acknowledgement_notification
375
+ }
376
+ end
377
+
378
+ # NB: casts to UTC before converting to a timestamp
379
+ def validate_and_parsetime(value)
380
+ return unless value
381
+ Time.iso8601(value).getutc.to_i
382
+ rescue ArgumentError => e
383
+ logger.error "Couldn't parse time from '#{value}'"
384
+ nil
385
+ end
386
+
387
+ end
388
+
389
+ end
390
+
391
+ end