flapjack 0.7.14 → 0.7.15
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -0
- data/etc/flapjack_config.yaml.example +1 -0
- data/features/events.feature +5 -0
- data/features/notification_rules.feature +1 -1
- data/features/steps/events_steps.rb +28 -13
- data/features/steps/notifications_steps.rb +1 -1
- data/lib/flapjack/coordinator.rb +3 -1
- data/lib/flapjack/data/contact.rb +8 -6
- data/lib/flapjack/data/entity_check.rb +78 -113
- data/lib/flapjack/data/event.rb +54 -65
- data/lib/flapjack/data/notification.rb +5 -1
- data/lib/flapjack/executive.rb +42 -38
- data/lib/flapjack/filters/acknowledgement.rb +5 -5
- data/lib/flapjack/filters/base.rb +2 -2
- data/lib/flapjack/filters/delays.rb +11 -11
- data/lib/flapjack/filters/detect_mass_client_failures.rb +8 -8
- data/lib/flapjack/filters/ok.rb +6 -6
- data/lib/flapjack/filters/scheduled_maintenance.rb +2 -2
- data/lib/flapjack/filters/unscheduled_maintenance.rb +3 -2
- data/lib/flapjack/gateways/api.rb +374 -277
- data/lib/flapjack/gateways/api/entity_check_presenter.rb +52 -21
- data/lib/flapjack/gateways/api/entity_presenter.rb +14 -9
- data/lib/flapjack/gateways/email.rb +7 -0
- data/lib/flapjack/gateways/email/alert.html.haml +13 -1
- data/lib/flapjack/gateways/email/alert.text.erb +5 -4
- data/lib/flapjack/gateways/jabber.rb +90 -34
- data/lib/flapjack/gateways/pagerduty.rb +6 -2
- data/lib/flapjack/gateways/web.rb +13 -8
- data/lib/flapjack/gateways/web/views/check.haml +70 -45
- data/lib/flapjack/gateways/web/views/checks.haml +1 -1
- data/lib/flapjack/gateways/web/views/entity.haml +1 -1
- data/lib/flapjack/patches.rb +9 -2
- data/lib/flapjack/pikelet.rb +14 -10
- data/lib/flapjack/utility.rb +10 -4
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +19 -5
- data/spec/lib/flapjack/data/entity_check_spec.rb +3 -30
- data/spec/lib/flapjack/data/event_spec.rb +96 -1
- data/spec/lib/flapjack/executive_spec.rb +5 -11
- data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +22 -3
- data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +30 -15
- data/spec/lib/flapjack/gateways/api_spec.rb +552 -186
- data/spec/lib/flapjack/gateways/email_spec.rb +2 -0
- data/spec/lib/flapjack/gateways/jabber_spec.rb +5 -4
- data/spec/lib/flapjack/gateways/pagerduty_spec.rb +3 -2
- data/spec/lib/flapjack/gateways/web_spec.rb +17 -12
- data/spec/lib/flapjack/pikelet_spec.rb +5 -2
- metadata +4 -5
- data/config.ru +0 -11
@@ -18,25 +18,25 @@ module Flapjack
|
|
18
18
|
client_mass_fail_threshold = 10
|
19
19
|
timestamp = Time.now.to_i
|
20
20
|
|
21
|
-
if event.
|
22
|
-
client_fail_count = @
|
21
|
+
if event.service?
|
22
|
+
client_fail_count = @redis.zcount("failed_checks:#{event.client}", '-inf', '+inf')
|
23
23
|
|
24
24
|
if client_fail_count >= client_mass_fail_threshold
|
25
25
|
# set the flag
|
26
26
|
# FIXME: perhaps implement this with tagging
|
27
|
-
@
|
28
|
-
@
|
27
|
+
@redis.add("mass_failed_client:#{event.client}", timestamp)
|
28
|
+
@redis.zadd("mass_failure_events_client:#{event.client}", 0, timestamp)
|
29
29
|
else
|
30
30
|
# unset the flag
|
31
|
-
start_mf = @
|
31
|
+
start_mf = @redis.get("mass_failed_client:#{event.client}")
|
32
32
|
duration = Time.now.to_i - start_mf.to_i
|
33
|
-
@
|
34
|
-
@
|
33
|
+
@redis.del("mass_failed_client:#{event.client}")
|
34
|
+
@redis.zadd("mass_failure_events_client:#{event.client}", duration, start_mf)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
result = false
|
39
|
-
@
|
39
|
+
@logger.debug("Filter: DetectMassClientFailures: #{result ? "block" : "pass"}")
|
40
40
|
result
|
41
41
|
end
|
42
42
|
end
|
data/lib/flapjack/filters/ok.rb
CHANGED
@@ -19,22 +19,22 @@ module Flapjack
|
|
19
19
|
|
20
20
|
if event.ok?
|
21
21
|
if event.previous_state == 'ok'
|
22
|
-
@
|
22
|
+
@logger.debug("Filter: Ok: existing state was ok, and the previous state was ok, so blocking")
|
23
23
|
result = true
|
24
24
|
end
|
25
25
|
|
26
|
-
entity_check = Flapjack::Data::EntityCheck.for_event_id(event.id, :redis => @
|
26
|
+
entity_check = Flapjack::Data::EntityCheck.for_event_id(event.id, :redis => @redis)
|
27
27
|
|
28
28
|
last_notification = entity_check.last_notification
|
29
|
-
@
|
29
|
+
@logger.debug("Filter: Ok: last notification: #{last_notification.inspect}")
|
30
30
|
if last_notification[:type] == 'recovery'
|
31
|
-
@
|
31
|
+
@logger.debug("Filter: Ok: last notification was a recovery, so blocking")
|
32
32
|
result = true
|
33
33
|
end
|
34
34
|
|
35
35
|
if event.previous_state != 'ok'
|
36
36
|
if event.previous_state_duration < 30
|
37
|
-
@
|
37
|
+
@logger.debug("Filter: Ok: previous non ok state was for less than 30 seconds, so blocking")
|
38
38
|
result = true
|
39
39
|
end
|
40
40
|
end
|
@@ -43,7 +43,7 @@ module Flapjack
|
|
43
43
|
entity_check.end_unscheduled_maintenance
|
44
44
|
end
|
45
45
|
|
46
|
-
@
|
46
|
+
@logger.debug("Filter: Ok: #{result ? "block" : "pass"}")
|
47
47
|
result
|
48
48
|
end
|
49
49
|
end
|
@@ -8,8 +8,8 @@ module Flapjack
|
|
8
8
|
include Base
|
9
9
|
|
10
10
|
def block?(event)
|
11
|
-
result = @
|
12
|
-
@
|
11
|
+
result = @redis.exists("#{event.id}:scheduled_maintenance")
|
12
|
+
@logger.debug("Filter: Scheduled Maintenance: #{result ? "block" : "pass"}")
|
13
13
|
result
|
14
14
|
end
|
15
15
|
end
|
@@ -8,8 +8,9 @@ module Flapjack
|
|
8
8
|
include Base
|
9
9
|
|
10
10
|
def block?(event)
|
11
|
-
result = @
|
12
|
-
|
11
|
+
result = @redis.exists("#{event.id}:unscheduled_maintenance") &&
|
12
|
+
!event.acknowledgement?
|
13
|
+
@logger.debug("Filter: Unscheduled Maintenance: #{result ? "block" : "pass"}")
|
13
14
|
result
|
14
15
|
end
|
15
16
|
end
|
@@ -16,6 +16,7 @@ require 'flapjack/data/entity'
|
|
16
16
|
require 'flapjack/data/entity_check'
|
17
17
|
|
18
18
|
require 'flapjack/gateways/api/entity_presenter'
|
19
|
+
require 'flapjack/gateways/api/entity_check_presenter'
|
19
20
|
require 'flapjack/rack_logger'
|
20
21
|
require 'flapjack/redis_pool'
|
21
22
|
|
@@ -50,6 +51,38 @@ module Flapjack
|
|
50
51
|
|
51
52
|
class API < Sinatra::Base
|
52
53
|
|
54
|
+
# used for backwards-compatible route matching below
|
55
|
+
ENTITY_CHECK_FRAGMENT = '(?:/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(.+))?)?'
|
56
|
+
|
57
|
+
class EntityCheckNotFound < RuntimeError
|
58
|
+
attr_reader :entity, :check
|
59
|
+
def initialize(entity, check)
|
60
|
+
@entity = entity
|
61
|
+
@check = check
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class EntityNotFound < RuntimeError
|
66
|
+
attr_reader :entity
|
67
|
+
def initialize(entity)
|
68
|
+
@entity = entity
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ContactNotFound < RuntimeError
|
73
|
+
attr_reader :contact_id
|
74
|
+
def initialize(contact_id)
|
75
|
+
@contact_id = contact_id
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class NotificationRuleNotFound < RuntimeError
|
80
|
+
attr_reader :rule_id
|
81
|
+
def initialize(rule_id)
|
82
|
+
@rule_id = rule_id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
53
86
|
include Flapjack::Utility
|
54
87
|
|
55
88
|
set :show_exceptions, false
|
@@ -88,147 +121,171 @@ module Flapjack
|
|
88
121
|
get '/entities' do
|
89
122
|
content_type :json
|
90
123
|
ret = Flapjack::Data::Entity.all(:redis => redis).sort_by(&:name).collect {|e|
|
91
|
-
|
92
|
-
|
93
|
-
entity_check_status(e, c)
|
94
|
-
}
|
95
|
-
}
|
124
|
+
presenter = Flapjack::Gateways::API::EntityPresenter.new(e, :redis => redis)
|
125
|
+
{'id' => e.id, 'name' => e.name, 'checks' => presenter.status }
|
96
126
|
}
|
97
127
|
ret.to_json
|
98
128
|
end
|
99
129
|
|
100
130
|
get '/checks/:entity' do
|
101
131
|
content_type :json
|
102
|
-
find_entity(params[:entity])
|
103
|
-
|
104
|
-
end
|
132
|
+
entity = find_entity(params[:entity])
|
133
|
+
entity.check_list.to_json
|
105
134
|
end
|
106
135
|
|
107
|
-
get %r{/status
|
136
|
+
get %r{/status#{ENTITY_CHECK_FRAGMENT}} do
|
108
137
|
content_type :json
|
109
138
|
|
110
|
-
|
111
|
-
|
139
|
+
captures = params[:captures] || []
|
140
|
+
entity_name = captures[0]
|
141
|
+
check = captures[1]
|
112
142
|
|
113
|
-
|
114
|
-
ret = if check
|
115
|
-
entity_check_status(entity, check)
|
116
|
-
else
|
117
|
-
entity.check_list.sort.collect {|c|
|
118
|
-
entity_check_status(entity, c)
|
119
|
-
}
|
120
|
-
end
|
121
|
-
return error(404, "could not find entity check '#{entity_name}:#{check}'") if ret.nil?
|
122
|
-
ret.to_json
|
123
|
-
end
|
124
|
-
end
|
143
|
+
entities, checks = entities_and_checks(entity_name, check)
|
125
144
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
get %r{/outages/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
|
130
|
-
content_type :json
|
131
|
-
|
132
|
-
entity_name = params[:captures][0]
|
133
|
-
check = params[:captures][1]
|
134
|
-
|
135
|
-
start_time = validate_and_parsetime(params[:start_time])
|
136
|
-
end_time = validate_and_parsetime(params[:end_time])
|
145
|
+
results = present_api_results(entities, checks, 'status') {|presenter|
|
146
|
+
presenter.status
|
147
|
+
}
|
137
148
|
|
138
|
-
|
139
|
-
|
149
|
+
if entity_name
|
150
|
+
# compatible with previous data format
|
151
|
+
results = results.collect {|status_h| status_h[:status]}
|
152
|
+
(check ? results.first : results).to_json
|
153
|
+
else
|
154
|
+
# new and improved data format which reflects the request param structure
|
155
|
+
results.to_json
|
140
156
|
end
|
141
157
|
end
|
142
158
|
|
143
|
-
get %r{/
|
144
|
-
|
159
|
+
get %r{/((?:outages|(?:un)?scheduled_maintenances|downtime))#{ENTITY_CHECK_FRAGMENT}} do
|
160
|
+
action = params[:captures][0].to_sym
|
161
|
+
entity_name = params[:captures][1]
|
162
|
+
check = params[:captures][2]
|
145
163
|
|
146
|
-
|
147
|
-
check = params[:captures][1]
|
164
|
+
entities, checks = entities_and_checks(entity_name, check)
|
148
165
|
|
149
166
|
start_time = validate_and_parsetime(params[:start_time])
|
150
167
|
end_time = validate_and_parsetime(params[:end_time])
|
151
168
|
|
152
|
-
|
153
|
-
presenter.
|
169
|
+
results = present_api_results(entities, checks, action) {|presenter|
|
170
|
+
presenter.send(action, start_time, end_time)
|
171
|
+
}
|
172
|
+
|
173
|
+
if check
|
174
|
+
# compatible with previous data format
|
175
|
+
results.first[action].to_json
|
176
|
+
elsif entity_name
|
177
|
+
# compatible with previous data format
|
178
|
+
rename = {:unscheduled_maintenances => :unscheduled_maintenance,
|
179
|
+
:scheduled_maintenances => :scheduled_maintenance}
|
180
|
+
drop = [:entity]
|
181
|
+
results.collect{|r|
|
182
|
+
r.inject({}) {|memo, (k, v)|
|
183
|
+
if new_k = rename[k]
|
184
|
+
memo[new_k] = v
|
185
|
+
elsif !drop.include?(k)
|
186
|
+
memo[k] = v
|
187
|
+
end
|
188
|
+
memo
|
189
|
+
}
|
190
|
+
}.to_json
|
191
|
+
else
|
192
|
+
# new and improved data format which reflects the request param structure
|
193
|
+
results.to_json
|
154
194
|
end
|
155
195
|
end
|
156
196
|
|
157
|
-
|
158
|
-
|
159
|
-
|
197
|
+
# create a scheduled maintenance period for a check on an entity
|
198
|
+
post %r{/scheduled_maintenances#{ENTITY_CHECK_FRAGMENT}} do
|
160
199
|
entity_name = params[:captures][0]
|
161
|
-
check
|
200
|
+
check = params[:captures][1]
|
201
|
+
|
202
|
+
entities, checks = entities_and_checks(entity_name, check)
|
162
203
|
|
163
204
|
start_time = validate_and_parsetime(params[:start_time])
|
164
|
-
end_time = validate_and_parsetime(params[:end_time])
|
165
205
|
|
166
|
-
|
167
|
-
|
168
|
-
|
206
|
+
act_proc = proc {|entity_check|
|
207
|
+
entity_check.create_scheduled_maintenance(:start_time => start_time,
|
208
|
+
:duration => params[:duration].to_i, :summary => params[:summary])
|
209
|
+
}
|
210
|
+
|
211
|
+
bulk_api_check_action(entities, checks, act_proc)
|
212
|
+
status 204
|
169
213
|
end
|
170
214
|
|
171
|
-
|
172
|
-
|
215
|
+
# create an acknowledgement for a service on an entity
|
216
|
+
# NB currently, this does not acknowledge a specific failure event, just
|
217
|
+
# the entity-check as a whole
|
218
|
+
post %r{/acknowledgements#{ENTITY_CHECK_FRAGMENT}} do
|
219
|
+
captures = params[:captures] || []
|
220
|
+
entity_name = captures[0]
|
221
|
+
check = captures[1]
|
173
222
|
|
174
|
-
|
175
|
-
check = params[:captures][1]
|
223
|
+
entities, checks = entities_and_checks(entity_name, check)
|
176
224
|
|
177
|
-
|
178
|
-
|
225
|
+
dur = params[:duration] ? params[:duration].to_i : nil
|
226
|
+
duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
|
227
|
+
summary = params[:summary]
|
179
228
|
|
180
|
-
|
181
|
-
|
182
|
-
|
229
|
+
opts = {'duration' => duration}
|
230
|
+
opts['summary'] = summary if summary
|
231
|
+
|
232
|
+
act_proc = proc {|entity_check|
|
233
|
+
Flapjack::Data::Event.create_acknowledgement(
|
234
|
+
entity_check.entity_name, entity_check.check,
|
235
|
+
:summary => params[:summary],
|
236
|
+
:duration => duration,
|
237
|
+
:redis => redis)
|
238
|
+
}
|
239
|
+
|
240
|
+
bulk_api_check_action(entities, checks, act_proc)
|
241
|
+
status 204
|
183
242
|
end
|
184
243
|
|
185
|
-
|
186
|
-
|
187
|
-
content_type :json
|
244
|
+
delete %r{/((?:un)?scheduled_maintenances)} do
|
245
|
+
action = params[:captures][0]
|
188
246
|
|
189
|
-
|
247
|
+
# no backwards-compatible mode here, it's a new method
|
248
|
+
entities, checks = entities_and_checks(nil, nil)
|
190
249
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
250
|
+
act_proc = case action
|
251
|
+
when 'scheduled_maintenances'
|
252
|
+
start_time = validate_and_parsetime(params[:start_time])
|
253
|
+
opts = {}
|
254
|
+
opts[:start_time] = start_time.to_i if start_time
|
255
|
+
proc {|entity_check| entity_check.delete_scheduled_maintenance(opts) }
|
256
|
+
when 'unscheduled_maintenances'
|
257
|
+
end_time = validate_and_parsetime(params[:end_time])
|
258
|
+
opts = {}
|
259
|
+
opts[:end_time] = end_time.to_i if end_time
|
260
|
+
proc {|entity_check| entity_check.end_unscheduled_maintenance(opts) }
|
197
261
|
end
|
262
|
+
|
263
|
+
bulk_api_check_action(entities, checks, act_proc)
|
264
|
+
status 204
|
198
265
|
end
|
199
266
|
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
content_type :json
|
267
|
+
post %r{/test_notifications#{ENTITY_CHECK_FRAGMENT}} do
|
268
|
+
captures = params[:captures] || []
|
269
|
+
entity_name = captures[0]
|
270
|
+
check = captures[1]
|
205
271
|
|
206
|
-
|
207
|
-
duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
|
272
|
+
entities, checks = entities_and_checks(entity_name, check)
|
208
273
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
274
|
+
act_proc = proc {|entity_check|
|
275
|
+
summary = params[:summary] ||
|
276
|
+
"Testing notifications to all contacts interested in entity #{entity_check.entity.name}"
|
277
|
+
Flapjack::Data::Event.test_notifications(
|
278
|
+
entity_check.entity_name, entity_check.check,
|
279
|
+
:summary => summary,
|
280
|
+
:redis => redis)
|
281
|
+
}
|
217
282
|
|
218
|
-
|
219
|
-
|
220
|
-
find_entity(params[:entity]) do |entity|
|
221
|
-
find_entity_check(entity, params[:check]) do |entity_check|
|
222
|
-
summary = params[:summary] || "Testing notifications to all contacts interested in entity #{entity.name}"
|
223
|
-
entity_check.test_notifications('summary' => summary)
|
224
|
-
status 204
|
225
|
-
end
|
226
|
-
end
|
283
|
+
bulk_api_check_action(entities, checks, act_proc)
|
284
|
+
status 204
|
227
285
|
end
|
228
286
|
|
229
287
|
post '/entities' do
|
230
288
|
pass unless 'application/json'.eql?(request.content_type)
|
231
|
-
content_type :json
|
232
289
|
|
233
290
|
errors = []
|
234
291
|
ret = nil
|
@@ -239,10 +296,10 @@ module Flapjack
|
|
239
296
|
unless entities
|
240
297
|
logger.debug("no entities object found in the following supplied JSON:")
|
241
298
|
logger.debug(request.body)
|
242
|
-
return
|
299
|
+
return err(403, "No entities object received")
|
243
300
|
end
|
244
|
-
return
|
245
|
-
return
|
301
|
+
return err(403, "The received entities object is not an Enumerable") unless entities.is_a?(Enumerable)
|
302
|
+
return err(403, "Entity with a nil id detected") unless entities.any? {|e| !e['id'].nil?}
|
246
303
|
|
247
304
|
entities.each do |entity|
|
248
305
|
unless entity['id']
|
@@ -251,8 +308,7 @@ module Flapjack
|
|
251
308
|
end
|
252
309
|
Flapjack::Data::Entity.add(entity, :redis => redis)
|
253
310
|
end
|
254
|
-
|
255
|
-
errors.empty? ? 204 : error(403, *errors)
|
311
|
+
errors.empty? ? 204 : err(403, *errors)
|
256
312
|
end
|
257
313
|
|
258
314
|
post '/contacts' do
|
@@ -293,13 +349,14 @@ module Flapjack
|
|
293
349
|
end
|
294
350
|
end
|
295
351
|
end
|
296
|
-
errors.empty? ? 204 :
|
352
|
+
errors.empty? ? 204 : err(403, *errors)
|
297
353
|
end
|
298
354
|
|
299
355
|
# Returns all the contacts
|
300
356
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts
|
301
357
|
get '/contacts' do
|
302
358
|
content_type :json
|
359
|
+
|
303
360
|
Flapjack::Data::Contact.all(:redis => redis).to_json
|
304
361
|
end
|
305
362
|
|
@@ -307,18 +364,18 @@ module Flapjack
|
|
307
364
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id
|
308
365
|
get '/contacts/:contact_id' do
|
309
366
|
content_type :json
|
310
|
-
|
311
|
-
|
312
|
-
|
367
|
+
|
368
|
+
contact = find_contact(params[:contact_id])
|
369
|
+
contact.to_json
|
313
370
|
end
|
314
371
|
|
315
372
|
# Lists this contact's notification rules
|
316
373
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_notification_rules
|
317
374
|
get '/contacts/:contact_id/notification_rules' do
|
318
375
|
content_type :json
|
319
|
-
|
320
|
-
|
321
|
-
|
376
|
+
|
377
|
+
contact = find_contact(params[:contact_id])
|
378
|
+
contact.notification_rules.to_json
|
322
379
|
end
|
323
380
|
|
324
381
|
# Get the specified notification rule for this user
|
@@ -326,317 +383,357 @@ module Flapjack
|
|
326
383
|
get '/notification_rules/:id' do
|
327
384
|
content_type :json
|
328
385
|
|
329
|
-
find_rule(params[:id])
|
330
|
-
|
331
|
-
end
|
386
|
+
rule = find_rule(params[:id])
|
387
|
+
rule.to_json
|
332
388
|
end
|
333
389
|
|
334
390
|
# Creates a notification rule for a contact
|
335
391
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-post_contacts_id_notification_rules
|
336
392
|
post '/notification_rules' do
|
337
393
|
content_type :json
|
394
|
+
|
338
395
|
if params[:id]
|
339
|
-
|
396
|
+
halt err(403, "post cannot be used for update, do a put instead")
|
340
397
|
end
|
341
398
|
|
342
399
|
logger.debug("post /notification_rules data: ")
|
343
400
|
logger.debug(params.inspect)
|
344
401
|
|
345
|
-
find_contact(params[:contact_id])
|
402
|
+
contact = find_contact(params[:contact_id])
|
346
403
|
|
347
|
-
|
348
|
-
|
349
|
-
|
404
|
+
rule_data = hashify(:entities, :entity_tags,
|
405
|
+
:warning_media, :critical_media, :time_restrictions,
|
406
|
+
:warning_blackhole, :critical_blackhole) {|k| [k, params[k]]}
|
350
407
|
|
351
|
-
|
352
|
-
|
353
|
-
end
|
354
|
-
rule.to_json
|
408
|
+
unless rule = contact.add_notification_rule(rule_data, :logger => logger)
|
409
|
+
halt err(403, "invalid notification rule data")
|
355
410
|
end
|
411
|
+
rule.to_json
|
356
412
|
end
|
357
413
|
|
358
414
|
# Updates a notification rule
|
359
415
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
360
416
|
put('/notification_rules/:id') do
|
361
417
|
content_type :json
|
418
|
+
|
362
419
|
logger.debug("put /notification_rules/#{params[:id]} data: ")
|
363
420
|
logger.debug(params.inspect)
|
364
421
|
|
365
|
-
find_rule(params[:id])
|
366
|
-
|
422
|
+
rule = find_rule(params[:id])
|
423
|
+
contact = find_contact(rule.contact_id)
|
367
424
|
|
368
|
-
|
369
|
-
|
370
|
-
|
425
|
+
rule_data = hashify(:entities, :entity_tags,
|
426
|
+
:warning_media, :critical_media, :time_restrictions,
|
427
|
+
:warning_blackhole, :critical_blackhole) {|k| [k, params[k]]}
|
371
428
|
|
372
|
-
|
373
|
-
|
374
|
-
end
|
375
|
-
rule.to_json
|
376
|
-
end
|
429
|
+
unless rule.update(rule_data, :logger => logger)
|
430
|
+
halt err(403, "invalid notification rule data")
|
377
431
|
end
|
432
|
+
rule.to_json
|
378
433
|
end
|
379
434
|
|
380
435
|
# Deletes a notification rule
|
381
436
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
382
437
|
delete('/notification_rules/:id') do
|
383
438
|
logger.debug("delete /notification_rules/#{params[:id]}")
|
384
|
-
find_rule(params[:id])
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
end
|
390
|
-
end
|
439
|
+
rule = find_rule(params[:id])
|
440
|
+
logger.debug("rule to delete: #{rule.inspect}, contact_id: #{rule.contact_id}")
|
441
|
+
contact = find_contact(rule.contact_id)
|
442
|
+
contact.delete_notification_rule(rule)
|
443
|
+
status 204
|
391
444
|
end
|
392
445
|
|
393
446
|
# Returns the media of a contact
|
394
447
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media
|
395
448
|
get '/contacts/:contact_id/media' do
|
396
449
|
content_type :json
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
450
|
+
|
451
|
+
contact = find_contact(params[:contact_id])
|
452
|
+
|
453
|
+
media = contact.media
|
454
|
+
media_intervals = contact.media_intervals
|
455
|
+
media_addr_int = hashify(*media.keys) {|k|
|
456
|
+
[k, {'address' => media[k],
|
457
|
+
'interval' => media_intervals[k] }]
|
458
|
+
}
|
459
|
+
media_addr_int.to_json
|
406
460
|
end
|
407
461
|
|
408
462
|
# Returns the specified media of a contact
|
409
463
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media_media
|
410
464
|
get('/contacts/:contact_id/media/:id') do
|
411
465
|
content_type :json
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
{'address' => contact.media[params[:id]],
|
418
|
-
'interval' => contact.media_intervals[params[:id]]}.to_json
|
466
|
+
|
467
|
+
contact = find_contact(params[:contact_id])
|
468
|
+
media = contact.media[params[:id]]
|
469
|
+
if media.nil?
|
470
|
+
halt err(403, "no #{params[:id]} for contact '#{params[:contact_id]}'")
|
419
471
|
end
|
472
|
+
interval = contact.media_intervals[params[:id]]
|
473
|
+
if interval.nil?
|
474
|
+
halt err(403, "no #{params[:id]} interval for contact '#{params[:contact_id]}'")
|
475
|
+
end
|
476
|
+
{'address' => media,
|
477
|
+
'interval' => interval}.to_json
|
420
478
|
end
|
421
479
|
|
422
480
|
# Creates or updates a media of a contact
|
423
481
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_media_media
|
424
482
|
put('/contacts/:contact_id/media/:id') do
|
425
483
|
content_type :json
|
426
|
-
find_contact(params[:contact_id]) do |contact|
|
427
|
-
errors = []
|
428
|
-
if params[:address].nil?
|
429
|
-
errors << "no address for '#{params[:id]}' media"
|
430
|
-
end
|
431
|
-
if params[:interval].nil?
|
432
|
-
errors << "no interval for '#{params[:id]}' media"
|
433
|
-
end
|
434
484
|
|
435
|
-
|
485
|
+
contact = find_contact(params[:contact_id])
|
486
|
+
errors = []
|
487
|
+
if params[:address].nil?
|
488
|
+
errors << "no address for '#{params[:id]}' media"
|
489
|
+
end
|
436
490
|
|
437
|
-
|
438
|
-
contact.set_interval_for_media(params[:id], params[:interval])
|
491
|
+
halt err(403, *errors) unless errors.empty?
|
439
492
|
|
440
|
-
|
441
|
-
|
442
|
-
|
493
|
+
contact.set_address_for_media(params[:id], params[:address])
|
494
|
+
contact.set_interval_for_media(params[:id], params[:interval])
|
495
|
+
|
496
|
+
{'address' => contact.media[params[:id]],
|
497
|
+
'interval' => contact.media_intervals[params[:id]]}.to_json
|
443
498
|
end
|
444
499
|
|
445
500
|
# delete a media of a contact
|
446
501
|
delete('/contacts/:contact_id/media/:id') do
|
447
|
-
find_contact(params[:contact_id])
|
448
|
-
|
449
|
-
|
450
|
-
end
|
502
|
+
contact = find_contact(params[:contact_id])
|
503
|
+
contact.remove_media(params[:id])
|
504
|
+
status 204
|
451
505
|
end
|
452
506
|
|
453
507
|
# Returns the timezone of a contact
|
454
508
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_timezone
|
455
509
|
get('/contacts/:contact_id/timezone') do
|
456
510
|
content_type :json
|
457
|
-
|
458
|
-
|
459
|
-
|
511
|
+
|
512
|
+
contact = find_contact(params[:contact_id])
|
513
|
+
contact.timezone.name.to_json
|
460
514
|
end
|
461
515
|
|
462
516
|
# Sets the timezone of a contact
|
463
517
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
464
518
|
put('/contacts/:contact_id/timezone') do
|
465
519
|
content_type :json
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
520
|
+
|
521
|
+
contact = find_contact(params[:contact_id])
|
522
|
+
contact.timezone = params[:timezone]
|
523
|
+
contact.timezone.name.to_json
|
470
524
|
end
|
471
525
|
|
472
526
|
# Removes the timezone of a contact
|
473
527
|
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
474
528
|
delete('/contacts/:contact_id/timezone') do
|
475
|
-
find_contact(params[:contact_id])
|
476
|
-
|
477
|
-
|
478
|
-
end
|
529
|
+
contact = find_contact(params[:contact_id])
|
530
|
+
contact.timezone = nil
|
531
|
+
status 204
|
479
532
|
end
|
480
533
|
|
481
534
|
post '/contacts/:contact_id/tags' do
|
482
535
|
content_type :json
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
end
|
536
|
+
|
537
|
+
tags = find_tags(params[:tag])
|
538
|
+
contact = find_contact(params[:contact_id])
|
539
|
+
contact.add_tags(*tags)
|
540
|
+
contact.tags.to_json
|
489
541
|
end
|
490
542
|
|
491
543
|
post '/contacts/:contact_id/entity_tags' do
|
492
544
|
content_type :json
|
493
|
-
find_contact(params[:contact_id])
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
end
|
498
|
-
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
499
|
-
[et[:entity].name, et[:tags]]
|
500
|
-
}
|
501
|
-
contact_ent_tag.to_json
|
545
|
+
contact = find_contact(params[:contact_id])
|
546
|
+
contact.entities.map {|e| e[:entity]}.each do |entity|
|
547
|
+
next unless tags = params[:entity][entity.name]
|
548
|
+
entity.add_tags(*tags)
|
502
549
|
end
|
550
|
+
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
551
|
+
[et[:entity].name, et[:tags]]
|
552
|
+
}
|
553
|
+
contact_ent_tag.to_json
|
503
554
|
end
|
504
555
|
|
505
556
|
delete '/contacts/:contact_id/tags' do
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
status 204
|
511
|
-
end
|
512
|
-
end
|
557
|
+
tags = find_tags(params[:tag])
|
558
|
+
contact = find_contact(params[:contact_id])
|
559
|
+
contact.delete_tags(*tags)
|
560
|
+
status 204
|
513
561
|
end
|
514
562
|
|
515
563
|
delete '/contacts/:contact_id/entity_tags' do
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
entity.delete_tags(*tags)
|
521
|
-
end
|
522
|
-
status 204
|
564
|
+
contact = find_contact(params[:contact_id])
|
565
|
+
contact.entities.map {|e| e[:entity]}.each do |entity|
|
566
|
+
next unless tags = params[:entity][entity.name]
|
567
|
+
entity.delete_tags(*tags)
|
523
568
|
end
|
569
|
+
status 204
|
524
570
|
end
|
525
571
|
|
526
572
|
get '/contacts/:contact_id/tags' do
|
527
573
|
content_type :json
|
528
|
-
|
529
|
-
|
530
|
-
|
574
|
+
|
575
|
+
contact = find_contact(params[:contact_id])
|
576
|
+
contact.tags.to_json
|
531
577
|
end
|
532
578
|
|
533
579
|
get '/contacts/:contact_id/entity_tags' do
|
534
580
|
content_type :json
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
581
|
+
|
582
|
+
contact = find_contact(params[:contact_id])
|
583
|
+
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
584
|
+
[et[:entity].name, et[:tags]]
|
585
|
+
}
|
586
|
+
contact_ent_tag.to_json
|
541
587
|
end
|
542
588
|
|
543
589
|
post '/entities/:entity/tags' do
|
544
590
|
content_type :json
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
end
|
591
|
+
|
592
|
+
tags = find_tags(params[:tag])
|
593
|
+
entity = find_entity(params[:entity])
|
594
|
+
entity.add_tags(*tags)
|
595
|
+
entity.tags.to_json
|
551
596
|
end
|
552
597
|
|
553
598
|
delete '/entities/:entity/tags' do
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
status 204
|
559
|
-
end
|
560
|
-
end
|
599
|
+
tags = find_tags(params[:tag])
|
600
|
+
entity = find_entity(params[:entity])
|
601
|
+
entity.delete_tags(*tags)
|
602
|
+
status 204
|
561
603
|
end
|
562
604
|
|
563
605
|
get '/entities/:entity/tags' do
|
564
606
|
content_type :json
|
565
|
-
|
566
|
-
|
567
|
-
|
607
|
+
|
608
|
+
entity = find_entity(params[:entity])
|
609
|
+
entity.tags.to_json
|
568
610
|
end
|
569
611
|
|
570
612
|
not_found do
|
571
613
|
logger.debug("in not_found :-(")
|
572
|
-
|
614
|
+
err(404, "not routable")
|
615
|
+
end
|
616
|
+
|
617
|
+
error Flapjack::Gateways::API::ContactNotFound do
|
618
|
+
e = env['sinatra.error']
|
619
|
+
err(403, "could not find contact '#{e.contact_id}'")
|
620
|
+
end
|
621
|
+
|
622
|
+
error Flapjack::Gateways::API::NotificationRuleNotFound do
|
623
|
+
e = env['sinatra.error']
|
624
|
+
err(403, "could not find notification rule '#{e.rule_id}'")
|
625
|
+
end
|
626
|
+
|
627
|
+
error Flapjack::Gateways::API::EntityNotFound do
|
628
|
+
e = env['sinatra.error']
|
629
|
+
err(403, "could not find entity '#{e.entity}'")
|
630
|
+
end
|
631
|
+
|
632
|
+
error Flapjack::Gateways::API::EntityCheckNotFound do
|
633
|
+
e = env['sinatra.error']
|
634
|
+
err(403, "could not find entity check '#{e.check}'")
|
573
635
|
end
|
574
636
|
|
575
637
|
private
|
576
638
|
|
577
|
-
def
|
639
|
+
def err(status, *msg)
|
578
640
|
msg_str = msg.join(", ")
|
579
641
|
logger.info "Error: #{msg_str}"
|
580
642
|
[status, {}, {:errors => msg}.to_json]
|
581
643
|
end
|
582
644
|
|
583
|
-
def
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
{'name' => check,
|
588
|
-
'state' => entity_check.state,
|
589
|
-
'summary' => entity_check.summary,
|
590
|
-
'details' => entity_check.details,
|
591
|
-
'in_unscheduled_maintenance' => entity_check.in_unscheduled_maintenance?,
|
592
|
-
'in_scheduled_maintenance' => entity_check.in_scheduled_maintenance?,
|
593
|
-
'last_update' => entity_check.last_update,
|
594
|
-
'last_problem_notification' => entity_check.last_problem_notification,
|
595
|
-
'last_recovery_notification' => entity_check.last_recovery_notification,
|
596
|
-
'last_acknowledgement_notification' => entity_check.last_acknowledgement_notification}
|
597
|
-
end
|
598
|
-
|
599
|
-
# following a callback-heavy pattern -- feels like nodejs :)
|
600
|
-
def find_contact(contact_id, &block)
|
601
|
-
contact = Flapjack::Data::Contact.find_by_id(contact_id.to_s, :redis => redis, :logger => logger)
|
602
|
-
return(yield(contact)) if contact
|
603
|
-
error(404, "could not find contact with id '#{contact_id}'")
|
645
|
+
def find_contact(contact_id)
|
646
|
+
contact = Flapjack::Data::Contact.find_by_id(contact_id, :logger => logger, :redis => redis)
|
647
|
+
raise Flapjack::Gateways::API::ContactNotFound.new(contact_id) if contact.nil?
|
648
|
+
contact
|
604
649
|
end
|
605
650
|
|
606
|
-
def find_rule(rule_id
|
607
|
-
rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :
|
608
|
-
|
609
|
-
|
651
|
+
def find_rule(rule_id)
|
652
|
+
rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :logger => logger, :redis => redis)
|
653
|
+
raise Flapjack::Gateways::API::NotificationRuleNotFound.new(rule_id) if rule.nil?
|
654
|
+
rule
|
610
655
|
end
|
611
656
|
|
612
|
-
def find_entity(entity_name
|
657
|
+
def find_entity(entity_name)
|
613
658
|
entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
|
614
|
-
|
615
|
-
|
659
|
+
raise Flapjack::Gateways::API::EntityNotFound.new(entity_name) if entity.nil?
|
660
|
+
entity
|
616
661
|
end
|
617
662
|
|
618
|
-
def find_entity_check(entity, check
|
663
|
+
def find_entity_check(entity, check)
|
619
664
|
entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
|
620
665
|
check, :redis => redis)
|
621
|
-
|
622
|
-
|
666
|
+
raise Flapjack::Gateways::API::EntityCheckNotFound.new(entity, check) if entity_check.nil?
|
667
|
+
entity_check
|
623
668
|
end
|
624
669
|
|
625
|
-
def
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
670
|
+
def entities_and_checks(entity_name, check)
|
671
|
+
if entity_name
|
672
|
+
# backwards-compatible, single entity or entity&check from route
|
673
|
+
entities = check ? nil : [entity_name]
|
674
|
+
checks = check ? {entity_name => check} : nil
|
675
|
+
else
|
676
|
+
# new and improved bulk API queries
|
677
|
+
entities = params[:entity]
|
678
|
+
checks = params[:check]
|
679
|
+
entities = [entities] unless entities.nil? || entities.is_a?(Array)
|
680
|
+
# TODO err if checks isn't a Hash (similar rules as in flapjack-diner)
|
681
|
+
end
|
682
|
+
[entities, checks]
|
683
|
+
end
|
684
|
+
|
685
|
+
def bulk_api_check_action(entities, entity_checks, action, params = {})
|
686
|
+
unless entities.nil? || entities.empty?
|
687
|
+
entities.each do |entity_name|
|
688
|
+
entity = find_entity(entity_name)
|
689
|
+
checks = entity.check_list.sort
|
690
|
+
checks.each do |check|
|
691
|
+
action.call( find_entity_check(entity, check) )
|
630
692
|
end
|
631
|
-
else
|
632
|
-
yield(Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis))
|
633
693
|
end
|
634
694
|
end
|
695
|
+
|
696
|
+
unless entity_checks.nil? || entity_checks.empty?
|
697
|
+
entity_checks.each_pair do |entity_name, checks|
|
698
|
+
entity = find_entity(entity_name)
|
699
|
+
checks = [checks] unless checks.is_a?(Array)
|
700
|
+
checks.each do |check|
|
701
|
+
action.call( find_entity_check(entity, check) )
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
def present_api_results(entities, entity_checks, result_type, &block)
|
708
|
+
result = []
|
709
|
+
|
710
|
+
unless entities.nil? || entities.empty?
|
711
|
+
result += entities.collect {|entity_name|
|
712
|
+
entity = find_entity(entity_name)
|
713
|
+
yield(Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis))
|
714
|
+
}.flatten(1)
|
715
|
+
end
|
716
|
+
|
717
|
+
unless entity_checks.nil? || entity_checks.empty?
|
718
|
+
result += entity_checks.inject([]) {|memo, (entity_name, checks)|
|
719
|
+
checks = [checks] unless checks.is_a?(Array)
|
720
|
+
entity = find_entity(entity_name)
|
721
|
+
memo += checks.collect {|check|
|
722
|
+
entity_check = find_entity_check(entity, check)
|
723
|
+
{:entity => entity_name,
|
724
|
+
:check => check,
|
725
|
+
result_type.to_sym => yield(Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check, :redis => redis))
|
726
|
+
}
|
727
|
+
}
|
728
|
+
}.flatten(1)
|
729
|
+
end
|
730
|
+
|
731
|
+
result
|
635
732
|
end
|
636
733
|
|
637
|
-
def
|
638
|
-
|
639
|
-
|
734
|
+
def find_tags(tags)
|
735
|
+
halt err(403, "no tags") if tags.nil? || tags.empty?
|
736
|
+
tags
|
640
737
|
end
|
641
738
|
|
642
739
|
# NB: casts to UTC before converting to a timestamp
|