flapjack 0.7.35 → 0.8.0
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -1
- data/Gemfile +3 -4
- data/Guardfile +1 -1
- data/README.md +38 -19
- data/Rakefile +1 -3
- data/etc/flapjack_config.yaml.example +11 -1
- data/features/steps/cli_steps.rb +3 -3
- data/features/steps/events_steps.rb +7 -6
- data/features/steps/flapjack-netsaint-parser_steps.rb +8 -8
- data/features/steps/notifications_steps.rb +10 -10
- data/features/steps/packaging-lintian_steps.rb +5 -9
- data/features/steps/time_travel_steps.rb +1 -1
- data/flapjack.gemspec +4 -3
- data/lib/flapjack/data/contact.rb +78 -6
- data/lib/flapjack/data/entity.rb +11 -2
- data/lib/flapjack/data/notification_rule.rb +67 -59
- data/lib/flapjack/data/semaphore.rb +44 -0
- data/lib/flapjack/gateways/api.rb +24 -28
- data/lib/flapjack/gateways/api/contact_methods.rb +1 -2
- data/lib/flapjack/gateways/api/entity_methods.rb +3 -3
- data/lib/flapjack/gateways/jsonapi.rb +249 -0
- data/lib/flapjack/gateways/jsonapi/contact_methods.rb +544 -0
- data/lib/flapjack/gateways/jsonapi/entity_check_presenter.rb +217 -0
- data/lib/flapjack/gateways/jsonapi/entity_methods.rb +350 -0
- data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +75 -0
- data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +32 -0
- data/lib/flapjack/gateways/web.rb +78 -12
- data/lib/flapjack/gateways/web/public/css/bootstrap-theme.css +397 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap-theme.min.css +7 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap.css +7118 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap.min.css +6 -8
- data/lib/flapjack/gateways/web/public/css/font-awesome.css +1338 -0
- data/lib/flapjack/gateways/web/public/css/font-awesome.min.css +4 -0
- data/lib/flapjack/gateways/web/public/css/screen.css +80 -0
- data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +87 -0
- data/lib/flapjack/gateways/web/public/css/select2.css +615 -0
- data/lib/flapjack/gateways/web/public/fonts/FontAwesome.otf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.eot +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.svg +414 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.ttf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.woff +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/flapjack/gateways/web/public/img/flapjack-2013-notext-transparent-300-300.png +0 -0
- data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
- data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
- data/lib/flapjack/gateways/web/public/js/backbone-min.js +2 -0
- data/lib/flapjack/gateways/web/public/js/backbone.js +1581 -0
- data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +75 -0
- data/lib/flapjack/gateways/web/public/js/bootstrap.js +2276 -0
- data/lib/flapjack/gateways/web/public/js/contacts.js +225 -0
- data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.js +9789 -0
- data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.min.js +6 -0
- data/lib/flapjack/gateways/web/public/js/select2.js +3255 -0
- data/lib/flapjack/gateways/web/public/js/select2.min.js +22 -0
- data/lib/flapjack/gateways/web/public/js/underscore-min.js +6 -0
- data/lib/flapjack/gateways/web/public/js/underscore.js +1276 -0
- data/lib/flapjack/gateways/web/views/check.html.erb +423 -193
- data/lib/flapjack/gateways/web/views/checks.html.erb +51 -71
- data/lib/flapjack/gateways/web/views/contact.html.erb +142 -164
- data/lib/flapjack/gateways/web/views/contacts.html.erb +20 -40
- data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +83 -0
- data/lib/flapjack/gateways/web/views/entities.html.erb +18 -37
- data/lib/flapjack/gateways/web/views/entity.html.erb +46 -65
- data/lib/flapjack/gateways/web/views/index.html.erb +6 -27
- data/lib/flapjack/gateways/web/views/layout.erb +95 -0
- data/lib/flapjack/gateways/web/views/self_stats.html.erb +100 -114
- data/lib/flapjack/pikelet.rb +4 -2
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +120 -120
- data/spec/lib/flapjack/data/contact_spec.rb +66 -58
- data/spec/lib/flapjack/data/entity_check_spec.rb +179 -179
- data/spec/lib/flapjack/data/entity_spec.rb +71 -71
- data/spec/lib/flapjack/data/event_spec.rb +34 -30
- data/spec/lib/flapjack/data/message_spec.rb +6 -6
- data/spec/lib/flapjack/data/notification_rule_spec.rb +24 -24
- data/spec/lib/flapjack/data/notification_spec.rb +19 -19
- data/spec/lib/flapjack/data/semaphore_spec.rb +24 -0
- data/spec/lib/flapjack/data/tag_spec.rb +11 -10
- data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +201 -201
- data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +55 -55
- data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +257 -257
- data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +26 -26
- data/spec/lib/flapjack/gateways/api_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/email_spec.rb +4 -4
- data/spec/lib/flapjack/gateways/jabber_spec.rb +77 -77
- data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +830 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_check_presenter_spec.rb +211 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +863 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_presenter_spec.rb +108 -0
- data/spec/lib/flapjack/gateways/jsonapi_spec.rb +8 -0
- data/spec/lib/flapjack/gateways/oobetet_spec.rb +35 -35
- data/spec/lib/flapjack/gateways/pagerduty_spec.rb +40 -40
- data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +3 -3
- data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +5 -5
- data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/web_spec.rb +73 -74
- data/spec/lib/flapjack/logger_spec.rb +13 -13
- data/spec/lib/flapjack/pikelet_spec.rb +33 -33
- data/spec/lib/flapjack/processor_spec.rb +22 -22
- data/spec/lib/flapjack/redis_pool_spec.rb +1 -1
- data/spec/lib/flapjack/utility_spec.rb +12 -12
- data/spec/spec_helper.rb +9 -9
- data/spec/support/erb_view_helper.rb +4 -0
- metadata +107 -96
- data/lib/flapjack/gateways/web/public/css/flapjack.css +0 -49
- data/lib/flapjack/gateways/web/views/_css.html.erb +0 -42
- data/lib/flapjack/gateways/web/views/_foot.html.erb +0 -3
- data/lib/flapjack/gateways/web/views/_head.html.erb +0 -5
- data/lib/flapjack/gateways/web/views/_nav.html.erb +0 -10
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Formats entity/check data for presentation by the API methods in Flapjack::Gateways::API.
|
|
4
|
+
|
|
5
|
+
require 'sinatra/base'
|
|
6
|
+
|
|
7
|
+
require 'flapjack/data/entity_check'
|
|
8
|
+
|
|
9
|
+
module Flapjack
|
|
10
|
+
|
|
11
|
+
module Gateways
|
|
12
|
+
|
|
13
|
+
class JSONAPI < Sinatra::Base
|
|
14
|
+
|
|
15
|
+
class EntityCheckPresenter
|
|
16
|
+
|
|
17
|
+
def initialize(entity_check)
|
|
18
|
+
@entity_check = entity_check
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def status
|
|
22
|
+
{'name' => @entity_check.check,
|
|
23
|
+
'state' => @entity_check.state,
|
|
24
|
+
'enabled' => @entity_check.enabled?,
|
|
25
|
+
'summary' => @entity_check.summary,
|
|
26
|
+
'details' => @entity_check.details,
|
|
27
|
+
'in_unscheduled_maintenance' => @entity_check.in_unscheduled_maintenance?,
|
|
28
|
+
'in_scheduled_maintenance' => @entity_check.in_scheduled_maintenance?,
|
|
29
|
+
'last_update' => @entity_check.last_update,
|
|
30
|
+
'last_problem_notification' => @entity_check.last_notification_for_state(:problem)[:timestamp],
|
|
31
|
+
'last_recovery_notification' => @entity_check.last_notification_for_state(:recovery)[:timestamp],
|
|
32
|
+
'last_acknowledgement_notification' => @entity_check.last_notification_for_state(:acknowledgement)[:timestamp]}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def outages(start_time, end_time, options = {})
|
|
36
|
+
# hist_states is an array of hashes, with [state, timestamp, summary] keys
|
|
37
|
+
hist_states = @entity_check.historical_states(start_time, end_time)
|
|
38
|
+
return hist_states if hist_states.empty?
|
|
39
|
+
|
|
40
|
+
initial = @entity_check.historical_state_before(hist_states.first[:timestamp])
|
|
41
|
+
hist_states.unshift(initial) if initial
|
|
42
|
+
|
|
43
|
+
# TODO the following works, but isn't the neatest
|
|
44
|
+
num_states = hist_states.size
|
|
45
|
+
|
|
46
|
+
index = 0
|
|
47
|
+
result = []
|
|
48
|
+
obj = nil
|
|
49
|
+
|
|
50
|
+
while index < num_states do
|
|
51
|
+
last_obj = obj
|
|
52
|
+
obj = hist_states[index]
|
|
53
|
+
index += 1
|
|
54
|
+
|
|
55
|
+
next if obj[:state] == 'ok'
|
|
56
|
+
|
|
57
|
+
if last_obj && (last_obj[:state] == obj[:state])
|
|
58
|
+
# TODO maybe build up arrays of these instead, and leave calling
|
|
59
|
+
# classes to join them together if needed?
|
|
60
|
+
result.last[:summary] << " / #{obj[:summary]}"
|
|
61
|
+
result.last[:details] << " / #{obj[:details]}"
|
|
62
|
+
next
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ts = obj[:timestamp]
|
|
66
|
+
|
|
67
|
+
obj_st = (last_obj || !start_time) ? ts : [ts, start_time].max
|
|
68
|
+
|
|
69
|
+
next_ts_obj = hist_states[index..-1].detect {|hs| hs[:state] != obj[:state] }
|
|
70
|
+
obj_et = next_ts_obj ? next_ts_obj[:timestamp] : end_time
|
|
71
|
+
|
|
72
|
+
obj_dur = obj_et ? obj_et - obj_st : nil
|
|
73
|
+
|
|
74
|
+
result << {:state => obj[:state],
|
|
75
|
+
:start_time => obj_st,
|
|
76
|
+
:end_time => obj_et,
|
|
77
|
+
:duration => obj_dur,
|
|
78
|
+
:summary => obj[:summary] || '',
|
|
79
|
+
:details => obj[:details] || ''
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def unscheduled_maintenances(start_time, end_time)
|
|
87
|
+
# unsched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
|
|
88
|
+
unsched_maintenance = @entity_check.maintenances(start_time, end_time,
|
|
89
|
+
:scheduled => false)
|
|
90
|
+
|
|
91
|
+
# to see if we start in an unscheduled maintenance period, we must check all unscheduled
|
|
92
|
+
# maintenances before the period and their durations
|
|
93
|
+
start_in_unsched = start_time.nil? ? [] :
|
|
94
|
+
@entity_check.maintenances(nil, start_time, :scheduled => false).select {|pu|
|
|
95
|
+
pu[:end_time] >= start_time
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
start_in_unsched + unsched_maintenance
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def scheduled_maintenances(start_time, end_time)
|
|
102
|
+
# sched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
|
|
103
|
+
sched_maintenance = @entity_check.maintenances(start_time, end_time,
|
|
104
|
+
:scheduled => true)
|
|
105
|
+
|
|
106
|
+
# to see if we start in a scheduled maintenance period, we must check all scheduled
|
|
107
|
+
# maintenances before the period and their durations
|
|
108
|
+
start_in_sched = start_time.nil? ? [] :
|
|
109
|
+
@entity_check.maintenances(nil, start_time, :scheduled => true).select {|ps|
|
|
110
|
+
ps[:end_time] >= start_time
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
start_in_sched + sched_maintenance
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# TODO test whether the below overlapping logic is prone to off-by-one
|
|
117
|
+
# errors; the numbers may line up more neatly if we consider outages to
|
|
118
|
+
# start one second after the maintenance period ends.
|
|
119
|
+
#
|
|
120
|
+
# TODO test performance with larger data sets
|
|
121
|
+
def downtime(start_time, end_time)
|
|
122
|
+
sched_maintenances = scheduled_maintenances(start_time, end_time)
|
|
123
|
+
|
|
124
|
+
outs = outages(start_time, end_time)
|
|
125
|
+
|
|
126
|
+
total_secs = {}
|
|
127
|
+
percentages = {}
|
|
128
|
+
|
|
129
|
+
outs.collect {|obj| obj[:state]}.uniq.each do |st|
|
|
130
|
+
total_secs[st] = 0
|
|
131
|
+
percentages[st] = (start_time.nil? || end_time.nil?) ? nil : 0
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
unless outs.empty?
|
|
135
|
+
|
|
136
|
+
# Initially we need to check for cases where a scheduled
|
|
137
|
+
# maintenance period is fully covered by an outage period.
|
|
138
|
+
# We then create two new outage periods to cover the time around
|
|
139
|
+
# the scheduled maintenance period, and remove the original.
|
|
140
|
+
|
|
141
|
+
sched_maintenances.each do |sm|
|
|
142
|
+
|
|
143
|
+
split_outs = []
|
|
144
|
+
|
|
145
|
+
outs.each { |o|
|
|
146
|
+
next unless o[:end_time] && (o[:start_time] < sm[:start_time]) &&
|
|
147
|
+
(o[:end_time] > sm[:end_time])
|
|
148
|
+
o[:delete] = true
|
|
149
|
+
split_outs += [{:state => o[:state],
|
|
150
|
+
:start_time => o[:start_time],
|
|
151
|
+
:end_time => sm[:start_time],
|
|
152
|
+
:duration => sm[:start_time] - o[:start_time],
|
|
153
|
+
:summary => "#{o[:summary]} [split start]"},
|
|
154
|
+
{:state => o[:state],
|
|
155
|
+
:start_time => sm[:end_time],
|
|
156
|
+
:end_time => o[:end_time],
|
|
157
|
+
:duration => o[:end_time] - sm[:end_time],
|
|
158
|
+
:summary => "#{o[:summary]} [split finish]"}]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
outs.reject! {|o| o[:delete]}
|
|
162
|
+
outs += split_outs
|
|
163
|
+
# not strictly necessary to keep the data sorted, but
|
|
164
|
+
# will make more sense while debgging
|
|
165
|
+
outs.sort! {|a,b| a[:start_time] <=> b[:start_time]}
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
sched_maintenances.each do |sm|
|
|
169
|
+
|
|
170
|
+
outs.each do |o|
|
|
171
|
+
next unless o[:end_time] && (sm[:start_time] < o[:end_time]) &&
|
|
172
|
+
(sm[:end_time] > o[:start_time])
|
|
173
|
+
|
|
174
|
+
if sm[:start_time] <= o[:start_time] &&
|
|
175
|
+
sm[:end_time] >= o[:end_time]
|
|
176
|
+
|
|
177
|
+
# outage is fully overlapped by the scheduled maintenance
|
|
178
|
+
o[:delete] = true
|
|
179
|
+
|
|
180
|
+
elsif sm[:start_time] <= o[:start_time]
|
|
181
|
+
# partially overlapping on the earlier side
|
|
182
|
+
o[:start_time] = sm[:end_time]
|
|
183
|
+
o[:duration] = o[:end_time] - o[:start_time]
|
|
184
|
+
elsif sm[:end_time] >= o[:end_time]
|
|
185
|
+
# partially overlapping on the later side
|
|
186
|
+
o[:end_time] = sm[:start_time]
|
|
187
|
+
o[:duration] = o[:end_time] - o[:start_time]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
outs.reject! {|o| o[:delete]}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
total_secs = outs.inject(total_secs) {|ret, o|
|
|
195
|
+
ret[o[:state]] += o[:duration] if o[:duration]
|
|
196
|
+
ret
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
unless (start_time.nil? || end_time.nil?)
|
|
200
|
+
total_secs.each_pair do |st, ts|
|
|
201
|
+
percentages[st] = (total_secs[st] * 100.0) / (end_time.to_f - start_time.to_f)
|
|
202
|
+
end
|
|
203
|
+
total_secs['ok'] = (end_time - start_time) - total_secs.values.reduce(:+)
|
|
204
|
+
percentages['ok'] = 100 - percentages.values.reduce(:+)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
{:total_seconds => total_secs, :percentages => percentages, :downtime => outs}
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
end
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'sinatra/base'
|
|
4
|
+
|
|
5
|
+
require 'flapjack/data/entity'
|
|
6
|
+
require 'flapjack/data/entity_check'
|
|
7
|
+
|
|
8
|
+
require 'flapjack/gateways/jsonapi/entity_presenter'
|
|
9
|
+
require 'flapjack/gateways/jsonapi/entity_check_presenter'
|
|
10
|
+
|
|
11
|
+
module Flapjack
|
|
12
|
+
|
|
13
|
+
module Gateways
|
|
14
|
+
|
|
15
|
+
class JSONAPI < Sinatra::Base
|
|
16
|
+
|
|
17
|
+
module EntityMethods
|
|
18
|
+
|
|
19
|
+
module Helpers
|
|
20
|
+
|
|
21
|
+
def find_entity(entity_name)
|
|
22
|
+
entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
|
|
23
|
+
raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_name) if entity.nil?
|
|
24
|
+
entity
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_entity_check(entity, check)
|
|
28
|
+
entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
|
|
29
|
+
check, :redis => redis)
|
|
30
|
+
raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity, check) if entity_check.nil?
|
|
31
|
+
entity_check
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def find_tags(tags)
|
|
35
|
+
halt err(403, "no tags") if tags.nil? || tags.empty?
|
|
36
|
+
tags
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def entities_and_checks(entity_name, check)
|
|
40
|
+
if entity_name
|
|
41
|
+
# backwards-compatible, single entity or entity&check from route
|
|
42
|
+
entities = check ? nil : [entity_name]
|
|
43
|
+
checks = check ? {entity_name => check} : nil
|
|
44
|
+
else
|
|
45
|
+
# new and improved bulk API queries
|
|
46
|
+
entities = params[:entity]
|
|
47
|
+
checks = params[:check]
|
|
48
|
+
entities = [entities] unless entities.nil? || entities.is_a?(Array)
|
|
49
|
+
# TODO err if checks isn't a Hash (similar rules as in flapjack-diner)
|
|
50
|
+
end
|
|
51
|
+
[entities, checks]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def bulk_api_check_action(entities, entity_checks, action, params = {})
|
|
55
|
+
unless entities.nil? || entities.empty?
|
|
56
|
+
entities.each do |entity_name|
|
|
57
|
+
entity = find_entity(entity_name)
|
|
58
|
+
checks = entity.check_list.sort
|
|
59
|
+
checks.each do |check|
|
|
60
|
+
action.call( find_entity_check(entity, check) )
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless entity_checks.nil? || entity_checks.empty?
|
|
66
|
+
entity_checks.each_pair do |entity_name, checks|
|
|
67
|
+
entity = find_entity(entity_name)
|
|
68
|
+
checks = [checks] unless checks.is_a?(Array)
|
|
69
|
+
checks.each do |check|
|
|
70
|
+
action.call( find_entity_check(entity, check) )
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def present_api_results(entities, entity_checks, result_type, &block)
|
|
77
|
+
result = []
|
|
78
|
+
|
|
79
|
+
unless entities.nil? || entities.empty?
|
|
80
|
+
result += entities.collect {|entity_name|
|
|
81
|
+
entity = find_entity(entity_name)
|
|
82
|
+
yield(Flapjack::Gateways::JSONAPI::EntityPresenter.new(entity, :redis => redis))
|
|
83
|
+
}.flatten(1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
unless entity_checks.nil? || entity_checks.empty?
|
|
87
|
+
result += entity_checks.inject([]) {|memo, (entity_name, checks)|
|
|
88
|
+
checks = [checks] unless checks.is_a?(Array)
|
|
89
|
+
entity = find_entity(entity_name)
|
|
90
|
+
memo += checks.collect {|check|
|
|
91
|
+
entity_check = find_entity_check(entity, check)
|
|
92
|
+
{:entity => entity_name,
|
|
93
|
+
:check => check,
|
|
94
|
+
result_type.to_sym => yield(Flapjack::Gateways::JSONAPI::EntityCheckPresenter.new(entity_check))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}.flatten(1)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
result
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# NB: casts to UTC before converting to a timestamp
|
|
104
|
+
def validate_and_parsetime(value)
|
|
105
|
+
return unless value
|
|
106
|
+
Time.iso8601(value).getutc.to_i
|
|
107
|
+
rescue ArgumentError => e
|
|
108
|
+
logger.error "Couldn't parse time from '#{value}'"
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# used for backwards-compatible route matching below
|
|
115
|
+
ENTITY_CHECK_FRAGMENT = '(?:/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(.+))?)?'
|
|
116
|
+
|
|
117
|
+
def self.registered(app)
|
|
118
|
+
|
|
119
|
+
app.helpers Flapjack::Gateways::JSONAPI::EntityMethods::Helpers
|
|
120
|
+
|
|
121
|
+
app.get '/entities' do
|
|
122
|
+
content_type :json
|
|
123
|
+
cors_headers
|
|
124
|
+
|
|
125
|
+
entities_json = Flapjack::Data::Entity.all(:redis => redis).collect {|e|
|
|
126
|
+
presenter = Flapjack::Gateways::JSONAPI::EntityPresenter.new(e, :redis => redis)
|
|
127
|
+
id = (e.id.respond_to?(:length) && e.id.length > 0) ? e.id : e.name
|
|
128
|
+
{'id' => id, 'name' => e.name, 'checks' => presenter.status }.to_json
|
|
129
|
+
}.join(',')
|
|
130
|
+
|
|
131
|
+
'{"entities":[' + entities_json + ']}'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
app.get '/checks/:entity' do
|
|
135
|
+
content_type :json
|
|
136
|
+
entity = find_entity(params[:entity])
|
|
137
|
+
entity.check_list.to_json
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
app.get %r{/status#{ENTITY_CHECK_FRAGMENT}} do
|
|
141
|
+
content_type :json
|
|
142
|
+
|
|
143
|
+
captures = params[:captures] || []
|
|
144
|
+
entity_name = captures[0]
|
|
145
|
+
check = captures[1]
|
|
146
|
+
|
|
147
|
+
entities, checks = entities_and_checks(entity_name, check)
|
|
148
|
+
|
|
149
|
+
results = present_api_results(entities, checks, 'status') {|presenter|
|
|
150
|
+
presenter.status
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if entity_name
|
|
154
|
+
# compatible with previous data format
|
|
155
|
+
results = results.collect {|status_h| status_h[:status]}
|
|
156
|
+
check ? results.first.to_json : "[" + results.map {|r| r.to_json }.join(',') + "]"
|
|
157
|
+
else
|
|
158
|
+
# new and improved data format which reflects the request param structure
|
|
159
|
+
"[" + results.map {|r| r.to_json }.join(',') + "]"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
app.get %r{/((?:outages|(?:un)?scheduled_maintenances|downtime))#{ENTITY_CHECK_FRAGMENT}} do
|
|
164
|
+
action = params[:captures][0].to_sym
|
|
165
|
+
entity_name = params[:captures][1]
|
|
166
|
+
check = params[:captures][2]
|
|
167
|
+
|
|
168
|
+
entities, checks = entities_and_checks(entity_name, check)
|
|
169
|
+
|
|
170
|
+
start_time = validate_and_parsetime(params[:start_time])
|
|
171
|
+
end_time = validate_and_parsetime(params[:end_time])
|
|
172
|
+
|
|
173
|
+
results = present_api_results(entities, checks, action) {|presenter|
|
|
174
|
+
presenter.send(action, start_time, end_time)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if check
|
|
178
|
+
# compatible with previous data format
|
|
179
|
+
results.first[action].to_json
|
|
180
|
+
elsif entity_name
|
|
181
|
+
# compatible with previous data format
|
|
182
|
+
rename = {:unscheduled_maintenances => :unscheduled_maintenance,
|
|
183
|
+
:scheduled_maintenances => :scheduled_maintenance}
|
|
184
|
+
drop = [:entity]
|
|
185
|
+
results.collect{|r|
|
|
186
|
+
r.inject({}) {|memo, (k, v)|
|
|
187
|
+
if new_k = rename[k]
|
|
188
|
+
memo[new_k] = v
|
|
189
|
+
elsif !drop.include?(k)
|
|
190
|
+
memo[k] = v
|
|
191
|
+
end
|
|
192
|
+
memo
|
|
193
|
+
}
|
|
194
|
+
}.to_json
|
|
195
|
+
else
|
|
196
|
+
# new and improved data format which reflects the request param structure
|
|
197
|
+
results.to_json
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# create a scheduled maintenance period for a check on an entity
|
|
202
|
+
app.post %r{/scheduled_maintenances#{ENTITY_CHECK_FRAGMENT}} do
|
|
203
|
+
|
|
204
|
+
captures = params[:captures] || []
|
|
205
|
+
entity_name = captures[0]
|
|
206
|
+
check = captures[1]
|
|
207
|
+
|
|
208
|
+
entities, checks = entities_and_checks(entity_name, check)
|
|
209
|
+
|
|
210
|
+
start_time = validate_and_parsetime(params[:start_time])
|
|
211
|
+
halt( err(403, "start time must be provided") ) unless start_time
|
|
212
|
+
|
|
213
|
+
act_proc = proc {|entity_check|
|
|
214
|
+
entity_check.create_scheduled_maintenance(start_time,
|
|
215
|
+
params[:duration].to_i, :summary => params[:summary])
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
bulk_api_check_action(entities, checks, act_proc)
|
|
219
|
+
status 204
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# create an acknowledgement for a service on an entity
|
|
223
|
+
# NB currently, this does not acknowledge a specific failure event, just
|
|
224
|
+
# the entity-check as a whole
|
|
225
|
+
app.post %r{/acknowledgements#{ENTITY_CHECK_FRAGMENT}} do
|
|
226
|
+
captures = params[:captures] || []
|
|
227
|
+
entity_name = captures[0]
|
|
228
|
+
check = captures[1]
|
|
229
|
+
|
|
230
|
+
entities, checks = entities_and_checks(entity_name, check)
|
|
231
|
+
|
|
232
|
+
dur = params[:duration] ? params[:duration].to_i : nil
|
|
233
|
+
duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
|
|
234
|
+
summary = params[:summary]
|
|
235
|
+
|
|
236
|
+
opts = {'duration' => duration}
|
|
237
|
+
opts['summary'] = summary if summary
|
|
238
|
+
|
|
239
|
+
act_proc = proc {|entity_check|
|
|
240
|
+
Flapjack::Data::Event.create_acknowledgement(
|
|
241
|
+
entity_check.entity_name, entity_check.check,
|
|
242
|
+
:summary => params[:summary],
|
|
243
|
+
:duration => duration,
|
|
244
|
+
:redis => redis)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
bulk_api_check_action(entities, checks, act_proc)
|
|
248
|
+
status 204
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
app.delete %r{/((?:un)?scheduled_maintenances)} do
|
|
252
|
+
action = params[:captures][0]
|
|
253
|
+
|
|
254
|
+
# no backwards-compatible mode here, it's a new method
|
|
255
|
+
entities, checks = entities_and_checks(nil, nil)
|
|
256
|
+
|
|
257
|
+
act_proc = case action
|
|
258
|
+
when 'scheduled_maintenances'
|
|
259
|
+
start_time = validate_and_parsetime(params[:start_time])
|
|
260
|
+
halt( err(403, "start time must be provided") ) unless start_time
|
|
261
|
+
opts = {}
|
|
262
|
+
proc {|entity_check| entity_check.end_scheduled_maintenance(start_time.to_i) }
|
|
263
|
+
when 'unscheduled_maintenances'
|
|
264
|
+
end_time = validate_and_parsetime(params[:end_time]) || Time.now
|
|
265
|
+
proc {|entity_check| entity_check.end_unscheduled_maintenance(end_time.to_i) }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
bulk_api_check_action(entities, checks, act_proc)
|
|
269
|
+
status 204
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
app.post %r{/test_notifications#{ENTITY_CHECK_FRAGMENT}} do
|
|
273
|
+
captures = params[:captures] || []
|
|
274
|
+
entity_name = captures[0]
|
|
275
|
+
check = captures[1]
|
|
276
|
+
|
|
277
|
+
entities, checks = entities_and_checks(entity_name, check)
|
|
278
|
+
|
|
279
|
+
act_proc = proc {|entity_check|
|
|
280
|
+
summary = params[:summary] ||
|
|
281
|
+
"Testing notifications to all contacts interested in entity #{entity_check.entity.name}"
|
|
282
|
+
Flapjack::Data::Event.test_notifications(
|
|
283
|
+
entity_check.entity_name, entity_check.check,
|
|
284
|
+
:summary => summary,
|
|
285
|
+
:redis => redis)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
bulk_api_check_action(entities, checks, act_proc)
|
|
289
|
+
status 204
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
app.post '/entities' do
|
|
293
|
+
pass unless Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type)
|
|
294
|
+
|
|
295
|
+
errors = []
|
|
296
|
+
ret = nil
|
|
297
|
+
|
|
298
|
+
# FIXME should scan for invalid records before making any changes, fail early
|
|
299
|
+
|
|
300
|
+
entities = params[:entities]
|
|
301
|
+
unless entities
|
|
302
|
+
logger.debug("no entities object found in the following supplied JSON:")
|
|
303
|
+
logger.debug(request.body)
|
|
304
|
+
return err(403, "No entities object received")
|
|
305
|
+
end
|
|
306
|
+
return err(403, "The received entities object is not an Enumerable") unless entities.is_a?(Enumerable)
|
|
307
|
+
return err(403, "Entity with a nil id detected") unless entities.any? {|e| !e['id'].nil?}
|
|
308
|
+
|
|
309
|
+
entities.each do |entity|
|
|
310
|
+
unless entity['id']
|
|
311
|
+
errors << "Entity not imported as it has no id: #{entity.inspect}"
|
|
312
|
+
next
|
|
313
|
+
end
|
|
314
|
+
Flapjack::Data::Entity.add(entity, :redis => redis)
|
|
315
|
+
end
|
|
316
|
+
errors.empty? ? 204 : err(403, *errors)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
app.post '/entities/:entity/tags' do
|
|
320
|
+
content_type :json
|
|
321
|
+
|
|
322
|
+
tags = find_tags(params[:tag])
|
|
323
|
+
entity = find_entity(params[:entity])
|
|
324
|
+
entity.add_tags(*tags)
|
|
325
|
+
entity.tags.to_json
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
app.delete '/entities/:entity/tags' do
|
|
329
|
+
tags = find_tags(params[:tag])
|
|
330
|
+
entity = find_entity(params[:entity])
|
|
331
|
+
entity.delete_tags(*tags)
|
|
332
|
+
status 204
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
app.get '/entities/:entity/tags' do
|
|
336
|
+
content_type :json
|
|
337
|
+
|
|
338
|
+
entity = find_entity(params[:entity])
|
|
339
|
+
entity.tags.to_json
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
end
|