flapjack 0.7.14 → 0.7.15
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/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
|