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.
- data/bin/flapjack +4 -2
- data/bin/flapjack-nagios-receiver +4 -1
- data/bin/flapjack-populator +4 -1
- data/etc/flapjack_config.yaml.example +111 -106
- data/features/steps/notifications_steps.rb +6 -6
- data/lib/flapjack/configuration.rb +76 -24
- data/lib/flapjack/coordinator.rb +27 -44
- data/lib/flapjack/data/entity.rb +28 -7
- data/lib/flapjack/data/entity_check.rb +18 -20
- data/lib/flapjack/executive.rb +5 -4
- data/lib/flapjack/gateways/api.rb +391 -0
- data/lib/flapjack/gateways/api/entity_check_presenter.rb +185 -0
- data/lib/flapjack/gateways/api/entity_presenter.rb +70 -0
- data/lib/flapjack/gateways/base.rb +38 -0
- data/lib/flapjack/{notification → gateways}/email.rb +4 -5
- data/lib/flapjack/{notification → gateways}/email/alert.html.haml +0 -0
- data/lib/flapjack/{notification → gateways}/email/alert.text.erb +0 -0
- data/lib/flapjack/gateways/jabber.rb +387 -0
- data/lib/flapjack/gateways/oobetet.rb +241 -0
- data/lib/flapjack/gateways/pagerduty.rb +247 -0
- data/lib/flapjack/{notification → gateways}/sms.rb +5 -6
- data/lib/flapjack/{notification → gateways}/sms/messagenet.rb +1 -1
- data/lib/flapjack/gateways/web.rb +293 -0
- data/lib/flapjack/{web → gateways/web}/views/_css.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/_nav.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/check.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/contact.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/contacts.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/index.haml +0 -0
- data/lib/flapjack/{web → gateways/web}/views/self_stats.haml +0 -0
- data/lib/flapjack/pikelet.rb +0 -23
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +56 -36
- data/spec/lib/flapjack/data/entity_spec.rb +53 -4
- data/spec/lib/flapjack/{api → gateways/api}/entity_check_presenter_spec.rb +10 -13
- data/spec/lib/flapjack/{api → gateways/api}/entity_presenter_spec.rb +10 -10
- data/spec/lib/flapjack/{api_spec.rb → gateways/api_spec.rb} +14 -14
- data/spec/lib/flapjack/gateways/email_spec.rb +6 -0
- data/spec/lib/flapjack/{jabber_spec.rb → gateways/jabber_spec.rb} +9 -9
- data/spec/lib/flapjack/{oobetet_spec.rb → gateways/oobetet_spec.rb} +10 -10
- data/spec/lib/flapjack/{pagerduty_spec.rb → gateways/pagerduty_spec.rb} +11 -11
- data/spec/lib/flapjack/gateways/sms_spec.rb +6 -0
- data/spec/lib/flapjack/{web_spec.rb → gateways/web_spec.rb} +4 -4
- metadata +46 -79
- data/bin/install-flapjack-systemwide +0 -58
- data/features/steps/flapjack-importer_steps.rb +0 -109
- data/features/steps/flapjack-worker_steps.rb +0 -68
- data/lib/flapjack/api.rb +0 -388
- data/lib/flapjack/api/entity_check_presenter.rb +0 -181
- data/lib/flapjack/api/entity_presenter.rb +0 -66
- data/lib/flapjack/cli/worker_manager.rb +0 -46
- data/lib/flapjack/inifile.rb +0 -44
- data/lib/flapjack/jabber.rb +0 -383
- data/lib/flapjack/notifier_engine.rb +0 -40
- data/lib/flapjack/notifiers/mailer/init.rb +0 -3
- data/lib/flapjack/notifiers/mailer/mailer.rb +0 -51
- data/lib/flapjack/notifiers/xmpp/init.rb +0 -3
- data/lib/flapjack/notifiers/xmpp/xmpp.rb +0 -46
- data/lib/flapjack/oobetet.rb +0 -240
- data/lib/flapjack/pagerduty.rb +0 -242
- data/lib/flapjack/web.rb +0 -286
- data/spec.old/check_sandbox/echo +0 -3
- data/spec.old/check_sandbox/sandboxed_check +0 -5
- data/spec.old/configs/flapjack-notifier-couchdb.ini +0 -25
- data/spec.old/configs/flapjack-notifier.ini +0 -39
- data/spec.old/configs/recipients.ini +0 -14
- data/spec.old/helpers.rb +0 -15
- data/spec.old/inifile_spec.rb +0 -66
- data/spec.old/mock-notifiers/mock/init.rb +0 -3
- data/spec.old/mock-notifiers/mock/mock.rb +0 -19
- data/spec.old/notifier-directories/spoons/testmailer/init.rb +0 -20
- data/spec.old/notifier_application_spec.rb +0 -222
- data/spec.old/notifier_filters_spec.rb +0 -52
- data/spec.old/notifier_options_multiplexer_spec.rb +0 -71
- data/spec.old/notifier_options_spec.rb +0 -115
- data/spec.old/notifier_spec.rb +0 -57
- data/spec.old/notifiers/mailer_spec.rb +0 -36
- data/spec.old/notifiers/xmpp_spec.rb +0 -36
- data/spec.old/persistence/datamapper_spec.rb +0 -74
- data/spec.old/persistence/mock_persistence_backend.rb +0 -26
- data/spec.old/simple.ini +0 -6
- data/spec.old/spec.opts +0 -4
- data/spec.old/test-filters/blocker.rb +0 -13
- data/spec.old/test-filters/mock.rb +0 -13
- data/spec.old/transports/beanstalkd_spec.rb +0 -44
- data/spec.old/transports/mock_transport.rb +0 -58
- data/spec.old/worker_application_spec.rb +0 -62
- data/spec.old/worker_options_spec.rb +0 -83
- data/spec/lib/flapjack/notification/email_spec.rb +0 -6
- data/spec/lib/flapjack/notification/sms_spec.rb +0 -6
data/lib/flapjack/coordinator.rb
CHANGED
@@ -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
|
-
|
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
|
29
|
+
def initialize(config)
|
34
30
|
@config = config
|
35
|
-
@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.
|
83
|
-
next unless
|
84
|
-
|
85
|
-
|
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
|
-
|
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(
|
110
|
-
|
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::
|
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::
|
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::
|
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::
|
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 #{
|
178
|
-
elsif ext_mod.include?(Flapjack::
|
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 #{
|
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::
|
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::
|
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::
|
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::
|
233
|
+
elsif [Flapjack::Gateways::Resque, Flapjack::Gateways::Thin].any?{|fp|
|
251
234
|
ext_mod.include?(fp)
|
252
235
|
}
|
253
236
|
|
data/lib/flapjack/data/entity.rb
CHANGED
@@ -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 {|
|
35
|
-
|
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
|
-
|
68
|
-
a,
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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' =>
|
84
|
-
'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' =>
|
94
|
-
'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
|
-
|
421
|
-
check = @check
|
421
|
+
contact_ids = @redis.smembers("contacts_for:#{entity.id}:#{check}")
|
422
422
|
|
423
423
|
if @logger
|
424
|
-
@logger.debug("
|
425
|
-
|
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
|
-
|
431
|
-
|
432
|
-
|
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(
|
435
|
+
def initialize(ent, che, options = {})
|
438
436
|
raise "Redis connection not set" unless @redis = options[:redis]
|
439
|
-
raise "Invalid entity" unless @entity =
|
440
|
-
raise "Invalid check" unless @check =
|
441
|
-
@key = "#{
|
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)
|
data/lib/flapjack/executive.rb
CHANGED
@@ -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::
|
259
|
+
Resque.enqueue_to(@queues[:sms], Flapjack::Gateways::Sms, contents)
|
259
260
|
when :email
|
260
|
-
Resque.enqueue_to(@queues[:email], Flapjack::
|
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
|