flapjack 0.7.35 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -1
  3. data/Gemfile +3 -4
  4. data/Guardfile +1 -1
  5. data/README.md +38 -19
  6. data/Rakefile +1 -3
  7. data/etc/flapjack_config.yaml.example +11 -1
  8. data/features/steps/cli_steps.rb +3 -3
  9. data/features/steps/events_steps.rb +7 -6
  10. data/features/steps/flapjack-netsaint-parser_steps.rb +8 -8
  11. data/features/steps/notifications_steps.rb +10 -10
  12. data/features/steps/packaging-lintian_steps.rb +5 -9
  13. data/features/steps/time_travel_steps.rb +1 -1
  14. data/flapjack.gemspec +4 -3
  15. data/lib/flapjack/data/contact.rb +78 -6
  16. data/lib/flapjack/data/entity.rb +11 -2
  17. data/lib/flapjack/data/notification_rule.rb +67 -59
  18. data/lib/flapjack/data/semaphore.rb +44 -0
  19. data/lib/flapjack/gateways/api.rb +24 -28
  20. data/lib/flapjack/gateways/api/contact_methods.rb +1 -2
  21. data/lib/flapjack/gateways/api/entity_methods.rb +3 -3
  22. data/lib/flapjack/gateways/jsonapi.rb +249 -0
  23. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +544 -0
  24. data/lib/flapjack/gateways/jsonapi/entity_check_presenter.rb +217 -0
  25. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +350 -0
  26. data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +75 -0
  27. data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +32 -0
  28. data/lib/flapjack/gateways/web.rb +78 -12
  29. data/lib/flapjack/gateways/web/public/css/bootstrap-theme.css +397 -0
  30. data/lib/flapjack/gateways/web/public/css/bootstrap-theme.min.css +7 -0
  31. data/lib/flapjack/gateways/web/public/css/bootstrap.css +7118 -0
  32. data/lib/flapjack/gateways/web/public/css/bootstrap.min.css +6 -8
  33. data/lib/flapjack/gateways/web/public/css/font-awesome.css +1338 -0
  34. data/lib/flapjack/gateways/web/public/css/font-awesome.min.css +4 -0
  35. data/lib/flapjack/gateways/web/public/css/screen.css +80 -0
  36. data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +87 -0
  37. data/lib/flapjack/gateways/web/public/css/select2.css +615 -0
  38. data/lib/flapjack/gateways/web/public/fonts/FontAwesome.otf +0 -0
  39. data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.eot +0 -0
  40. data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.svg +414 -0
  41. data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.ttf +0 -0
  42. data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.woff +0 -0
  43. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
  44. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +229 -0
  45. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  46. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
  47. data/lib/flapjack/gateways/web/public/img/flapjack-2013-notext-transparent-300-300.png +0 -0
  48. data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
  49. data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
  50. data/lib/flapjack/gateways/web/public/js/backbone-min.js +2 -0
  51. data/lib/flapjack/gateways/web/public/js/backbone.js +1581 -0
  52. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +75 -0
  53. data/lib/flapjack/gateways/web/public/js/bootstrap.js +2276 -0
  54. data/lib/flapjack/gateways/web/public/js/contacts.js +225 -0
  55. data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.js +9789 -0
  56. data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.min.js +6 -0
  57. data/lib/flapjack/gateways/web/public/js/select2.js +3255 -0
  58. data/lib/flapjack/gateways/web/public/js/select2.min.js +22 -0
  59. data/lib/flapjack/gateways/web/public/js/underscore-min.js +6 -0
  60. data/lib/flapjack/gateways/web/public/js/underscore.js +1276 -0
  61. data/lib/flapjack/gateways/web/views/check.html.erb +423 -193
  62. data/lib/flapjack/gateways/web/views/checks.html.erb +51 -71
  63. data/lib/flapjack/gateways/web/views/contact.html.erb +142 -164
  64. data/lib/flapjack/gateways/web/views/contacts.html.erb +20 -40
  65. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +83 -0
  66. data/lib/flapjack/gateways/web/views/entities.html.erb +18 -37
  67. data/lib/flapjack/gateways/web/views/entity.html.erb +46 -65
  68. data/lib/flapjack/gateways/web/views/index.html.erb +6 -27
  69. data/lib/flapjack/gateways/web/views/layout.erb +95 -0
  70. data/lib/flapjack/gateways/web/views/self_stats.html.erb +100 -114
  71. data/lib/flapjack/pikelet.rb +4 -2
  72. data/lib/flapjack/version.rb +1 -1
  73. data/spec/lib/flapjack/coordinator_spec.rb +120 -120
  74. data/spec/lib/flapjack/data/contact_spec.rb +66 -58
  75. data/spec/lib/flapjack/data/entity_check_spec.rb +179 -179
  76. data/spec/lib/flapjack/data/entity_spec.rb +71 -71
  77. data/spec/lib/flapjack/data/event_spec.rb +34 -30
  78. data/spec/lib/flapjack/data/message_spec.rb +6 -6
  79. data/spec/lib/flapjack/data/notification_rule_spec.rb +24 -24
  80. data/spec/lib/flapjack/data/notification_spec.rb +19 -19
  81. data/spec/lib/flapjack/data/semaphore_spec.rb +24 -0
  82. data/spec/lib/flapjack/data/tag_spec.rb +11 -10
  83. data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +201 -201
  84. data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +55 -55
  85. data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +257 -257
  86. data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +26 -26
  87. data/spec/lib/flapjack/gateways/api_spec.rb +1 -1
  88. data/spec/lib/flapjack/gateways/email_spec.rb +4 -4
  89. data/spec/lib/flapjack/gateways/jabber_spec.rb +77 -77
  90. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +830 -0
  91. data/spec/lib/flapjack/gateways/jsonapi/entity_check_presenter_spec.rb +211 -0
  92. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +863 -0
  93. data/spec/lib/flapjack/gateways/jsonapi/entity_presenter_spec.rb +108 -0
  94. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +8 -0
  95. data/spec/lib/flapjack/gateways/oobetet_spec.rb +35 -35
  96. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +40 -40
  97. data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +3 -3
  98. data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +1 -1
  99. data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +5 -5
  100. data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +1 -1
  101. data/spec/lib/flapjack/gateways/web_spec.rb +73 -74
  102. data/spec/lib/flapjack/logger_spec.rb +13 -13
  103. data/spec/lib/flapjack/pikelet_spec.rb +33 -33
  104. data/spec/lib/flapjack/processor_spec.rb +22 -22
  105. data/spec/lib/flapjack/redis_pool_spec.rb +1 -1
  106. data/spec/lib/flapjack/utility_spec.rb +12 -12
  107. data/spec/spec_helper.rb +9 -9
  108. data/spec/support/erb_view_helper.rb +4 -0
  109. metadata +107 -96
  110. data/lib/flapjack/gateways/web/public/css/flapjack.css +0 -49
  111. data/lib/flapjack/gateways/web/views/_css.html.erb +0 -42
  112. data/lib/flapjack/gateways/web/views/_foot.html.erb +0 -3
  113. data/lib/flapjack/gateways/web/views/_head.html.erb +0 -5
  114. 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