flapjack 0.6.43 → 0.6.44

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 (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