flapjack 0.5.5 → 0.6.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. data/.gitignore +10 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +10 -0
  4. data/Gemfile +18 -0
  5. data/Guardfile +14 -0
  6. data/README.md +152 -173
  7. data/Rakefile +53 -150
  8. data/bin/flapjack +72 -0
  9. data/bin/flapjack-nagios-receiver +111 -0
  10. data/bin/flapjack-nagios-receiver-control +15 -0
  11. data/bin/flapjack-netsaint-parser +0 -2
  12. data/bin/flapjack-populator +133 -16
  13. data/bin/install-flapjack-systemwide +2 -2
  14. data/config.ru +11 -0
  15. data/dist/etc/init.d/flapjack +46 -0
  16. data/dist/etc/init.d/flapjack-nagios-receiver +36 -0
  17. data/doc/GLOSSARY.md +19 -0
  18. data/etc/flapjack_config.yaml.example +90 -0
  19. data/features/events.feature +132 -0
  20. data/features/notifications.feature +57 -0
  21. data/features/packaging-lintian.feature +5 -3
  22. data/features/steps/events_steps.rb +164 -0
  23. data/features/steps/flapjack-importer_steps.rb +2 -5
  24. data/features/steps/flapjack-worker_steps.rb +13 -6
  25. data/features/steps/notifications_steps.rb +178 -0
  26. data/features/steps/packaging-lintian_steps.rb +14 -0
  27. data/features/steps/time_travel_steps.rb +34 -0
  28. data/features/support/env.rb +63 -36
  29. data/flapjack.gemspec +35 -186
  30. data/lib/flapjack.rb +2 -0
  31. data/lib/flapjack/api.rb +274 -0
  32. data/lib/flapjack/api/entity_check_presenter.rb +184 -0
  33. data/lib/flapjack/api/entity_presenter.rb +66 -0
  34. data/lib/flapjack/cli/worker_manager.rb +1 -2
  35. data/lib/flapjack/configuration.rb +11 -0
  36. data/lib/flapjack/coordinator.rb +288 -0
  37. data/lib/flapjack/daemonizing.rb +186 -0
  38. data/lib/flapjack/data/contact.rb +45 -0
  39. data/lib/flapjack/data/entity.rb +89 -0
  40. data/lib/flapjack/data/entity_check.rb +396 -0
  41. data/lib/flapjack/data/event.rb +144 -0
  42. data/lib/flapjack/data/notification.rb +13 -0
  43. data/lib/flapjack/executive.rb +289 -0
  44. data/lib/flapjack/filters/acknowledgement.rb +39 -0
  45. data/lib/flapjack/filters/{any_parents_failed.rb → base.rb} +6 -4
  46. data/lib/flapjack/filters/delays.rb +53 -0
  47. data/lib/flapjack/filters/detect_mass_client_failures.rb +44 -0
  48. data/lib/flapjack/filters/ok.rb +25 -5
  49. data/lib/flapjack/filters/scheduled_maintenance.rb +17 -0
  50. data/lib/flapjack/filters/unscheduled_maintenance.rb +17 -0
  51. data/lib/flapjack/jabber.rb +294 -0
  52. data/lib/flapjack/notification/common.rb +23 -0
  53. data/lib/flapjack/notification/email.rb +107 -0
  54. data/lib/flapjack/notification/email/alert.html.haml +48 -0
  55. data/lib/flapjack/notification/email/alert.text.erb +14 -0
  56. data/lib/flapjack/notification/sms.rb +42 -0
  57. data/lib/flapjack/notification/sms/messagenet.rb +49 -0
  58. data/lib/flapjack/notifier_engine.rb +4 -4
  59. data/lib/flapjack/notifiers/mailer/mailer.rb +6 -7
  60. data/lib/flapjack/notifiers/xmpp/xmpp.rb +12 -12
  61. data/lib/flapjack/pagerduty.rb +230 -0
  62. data/lib/flapjack/patches.rb +108 -19
  63. data/lib/flapjack/persistence/data_mapper/models/check.rb +5 -3
  64. data/lib/flapjack/persistence/data_mapper/models/check_template.rb +2 -0
  65. data/lib/flapjack/persistence/data_mapper/models/event.rb +2 -0
  66. data/lib/flapjack/persistence/data_mapper/models/node.rb +3 -1
  67. data/lib/flapjack/persistence/data_mapper/models/related_check.rb +3 -1
  68. data/lib/flapjack/pikelet.rb +56 -0
  69. data/lib/flapjack/transports/beanstalkd.rb +1 -1
  70. data/lib/flapjack/transports/result.rb +6 -6
  71. data/lib/flapjack/utility.rb +46 -0
  72. data/lib/flapjack/version.rb +5 -0
  73. data/lib/flapjack/web.rb +198 -0
  74. data/lib/flapjack/web/views/acknowledge.haml +55 -0
  75. data/lib/flapjack/web/views/check.haml +162 -0
  76. data/lib/flapjack/web/views/index.haml +92 -0
  77. data/lib/flapjack/web/views/self_stats.haml +56 -0
  78. data/lib/flapjack/{applications/worker.rb → worker/application.rb} +0 -0
  79. data/lib/flapjack/worker/cli.rb +49 -0
  80. data/{spec → spec.old}/check_sandbox/echo +0 -0
  81. data/{spec → spec.old}/check_sandbox/sandboxed_check +0 -0
  82. data/{spec → spec.old}/configs/flapjack-notifier-couchdb.ini +0 -0
  83. data/{spec → spec.old}/configs/flapjack-notifier.ini +0 -0
  84. data/{spec → spec.old}/configs/recipients.ini +0 -0
  85. data/{spec → spec.old}/helpers.rb +0 -0
  86. data/{spec → spec.old}/inifile_spec.rb +0 -0
  87. data/{spec → spec.old}/mock-notifiers/mock/init.rb +0 -0
  88. data/{spec → spec.old}/mock-notifiers/mock/mock.rb +0 -0
  89. data/{spec → spec.old}/notifier-directories/spoons/testmailer/init.rb +0 -0
  90. data/{spec → spec.old}/notifier_application_spec.rb +0 -0
  91. data/{spec → spec.old}/notifier_filters_spec.rb +0 -0
  92. data/{spec → spec.old}/notifier_options_multiplexer_spec.rb +0 -0
  93. data/{spec → spec.old}/notifier_options_spec.rb +0 -0
  94. data/{spec → spec.old}/notifier_spec.rb +0 -0
  95. data/{spec → spec.old}/notifiers/mailer_spec.rb +0 -0
  96. data/{spec → spec.old}/notifiers/xmpp_spec.rb +0 -0
  97. data/{spec → spec.old}/persistence/datamapper_spec.rb +0 -0
  98. data/{spec → spec.old}/persistence/mock_persistence_backend.rb +0 -0
  99. data/{spec → spec.old}/simple.ini +0 -0
  100. data/{spec → spec.old}/spec.opts +0 -0
  101. data/{spec → spec.old}/test-filters/blocker.rb +0 -0
  102. data/{spec → spec.old}/test-filters/mock.rb +0 -0
  103. data/{spec → spec.old}/transports/beanstalkd_spec.rb +0 -0
  104. data/{spec → spec.old}/transports/mock_transport.rb +0 -0
  105. data/{spec → spec.old}/worker_application_spec.rb +0 -0
  106. data/{spec → spec.old}/worker_options_spec.rb +0 -0
  107. data/spec/lib/flapjack/api/entity_check_presenter_spec.rb +117 -0
  108. data/spec/lib/flapjack/api/entity_presenter_spec.rb +92 -0
  109. data/spec/lib/flapjack/api_spec.rb +170 -0
  110. data/spec/lib/flapjack/coordinator_spec.rb +16 -0
  111. data/spec/lib/flapjack/data/entity_check_spec.rb +398 -0
  112. data/spec/lib/flapjack/data/entity_spec.rb +71 -0
  113. data/spec/lib/flapjack/data/event_spec.rb +6 -0
  114. data/spec/lib/flapjack/executive_spec.rb +59 -0
  115. data/spec/lib/flapjack/filters/acknowledgement_spec.rb +6 -0
  116. data/spec/lib/flapjack/filters/delays_spec.rb +6 -0
  117. data/spec/lib/flapjack/filters/detect_mass_client_failures_spec.rb +6 -0
  118. data/spec/lib/flapjack/filters/ok_spec.rb +6 -0
  119. data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +6 -0
  120. data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +6 -0
  121. data/spec/lib/flapjack/jabber_spec.rb +150 -0
  122. data/spec/lib/flapjack/notification/email_spec.rb +6 -0
  123. data/spec/lib/flapjack/notification/sms_spec.rb +6 -0
  124. data/spec/lib/flapjack/pikelet_spec.rb +28 -0
  125. data/spec/lib/flapjack/web_spec.rb +188 -0
  126. data/spec/spec_helper.rb +44 -0
  127. data/spec/support/profile_all_formatter.rb +44 -0
  128. data/spec/support/uncolored_doc_formatter.rb +9 -0
  129. data/tasks/events.rake +85 -0
  130. data/tmp/acknowledge.rb +14 -0
  131. data/tmp/create_config_yaml.rb +16 -0
  132. data/tmp/create_events_failure.rb +33 -0
  133. data/tmp/create_events_ok.rb +33 -0
  134. data/tmp/create_events_ok_fail_ack_ok.rb +54 -0
  135. data/tmp/create_events_ok_failure.rb +40 -0
  136. data/tmp/create_events_ok_failure_ack.rb +54 -0
  137. data/tmp/dummy_entities.json +1 -0
  138. data/tmp/generate_nagios_test_hosts.rb +16 -0
  139. data/tmp/parse_config_yaml.rb +7 -0
  140. data/tmp/redis_delete_all_keys.rb +11 -0
  141. data/tmp/test_entities.json +1 -0
  142. metadata +482 -221
  143. data/TODO.md +0 -36
  144. data/VERSION +0 -1
  145. data/bin/flapjack-benchmark +0 -50
  146. data/bin/flapjack-notifier +0 -21
  147. data/bin/flapjack-notifier-manager +0 -43
  148. data/bin/flapjack-stats +0 -27
  149. data/bin/flapjack-worker +0 -13
  150. data/bin/flapjack-worker-manager +0 -35
  151. data/dist/etc/init.d/flapjack-notifier +0 -47
  152. data/dist/etc/init.d/flapjack-workers +0 -44
  153. data/features/flapjack-notifier-manager.feature +0 -19
  154. data/features/flapjack-worker-manager.feature +0 -27
  155. data/features/flapjack-worker.feature +0 -27
  156. data/features/netsaint-config-converter.feature +0 -126
  157. data/features/persistence/couch.feature +0 -105
  158. data/features/persistence/sqlite3.feature +0 -105
  159. data/features/persistence/steps/couch_steps.rb +0 -25
  160. data/features/persistence/steps/generic_steps.rb +0 -102
  161. data/features/persistence/steps/sqlite3_steps.rb +0 -13
  162. data/features/steps/flapjack-notifier-manager_steps.rb +0 -24
  163. data/features/steps/flapjack-worker-manager_steps.rb +0 -48
  164. data/lib/flapjack/applications/notifier.rb +0 -222
  165. data/lib/flapjack/cli/notifier.rb +0 -108
  166. data/lib/flapjack/cli/notifier_manager.rb +0 -86
  167. data/lib/flapjack/cli/worker.rb +0 -51
data/lib/flapjack.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
+
4
5
  end
6
+
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A HTTP-based API server, which provides queries to determine the status of
4
+ # entities and the checks that are reported against them.
5
+ #
6
+ # There's a matching flapjack-diner gem which consumes data from this API
7
+ # (currently at https://github.com/ali-graham/flapjack-diner -- this will change.)
8
+
9
+ require 'time'
10
+
11
+ require 'rack/fiber_pool'
12
+ require 'sinatra/base'
13
+
14
+ require 'flapjack/pikelet'
15
+
16
+ require 'flapjack/api/entity_presenter'
17
+
18
+ require 'flapjack/data/entity'
19
+ require 'flapjack/data/entity_check'
20
+
21
+ module Flapjack
22
+
23
+ class API < Sinatra::Base
24
+
25
+ # doesn't work with Rack::Test for some reason
26
+ unless 'test'.eql?(FLAPJACK_ENV)
27
+ rescue_exception = Proc.new { |env, exception|
28
+ [503, {}, exception.message]
29
+ }
30
+
31
+ use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
32
+ end
33
+ use Rack::MethodOverride
34
+ extend Flapjack::Pikelet
35
+
36
+ before do
37
+ # will only initialise the first time it's run
38
+ Flapjack::API.bootstrap
39
+ end
40
+
41
+ helpers do
42
+ def json_status(code, reason)
43
+ status code
44
+ {:status => code, :reason => reason}.to_json
45
+ end
46
+
47
+ def logger
48
+ Flapjack::API.logger
49
+ end
50
+ end
51
+
52
+ get '/entities' do
53
+ content_type :json
54
+ ret = Flapjack::Data::Entity.all(:redis => @@redis).sort_by(&:name).collect {|e|
55
+ {'id' => e.id, 'name' => e.name,
56
+ 'checks' => e.check_list.sort.collect {|c|
57
+ entity_check_status(e, c)
58
+ }
59
+ }
60
+ }
61
+ ret.to_json
62
+ end
63
+
64
+ get '/checks/:entity' do
65
+ content_type :json
66
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => @@redis)
67
+ if entity.nil?
68
+ status 404
69
+ return
70
+ end
71
+ entity.check_list.to_json
72
+ end
73
+
74
+ get %r{/status/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
75
+ content_type :json
76
+
77
+ entity_name = params[:captures][0]
78
+ check = params[:captures][1]
79
+
80
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @@redis)
81
+ if entity.nil?
82
+ status 404
83
+ return
84
+ end
85
+
86
+ ret = if check
87
+ entity_check_status(entity, check)
88
+ else
89
+ entity.check_list.sort.collect {|c|
90
+ entity_check_status(entity, c)
91
+ }
92
+ end
93
+ ret.to_json
94
+ end
95
+
96
+ # the first capture group in the regex checks for acceptable
97
+ # characters in a domain name -- this will also match strings
98
+ # that aren't acceptable domain names as well, of course.
99
+ get %r{/outages/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
100
+ content_type :json
101
+
102
+ entity_name = params[:captures][0]
103
+ check = params[:captures][1]
104
+
105
+ entity = entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @@redis)
106
+ if entity.nil?
107
+ status 404
108
+ return
109
+ end
110
+
111
+ start_time = validate_and_parsetime(params[:start_time])
112
+ end_time = validate_and_parsetime(params[:end_time])
113
+
114
+ presenter = if check
115
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
116
+ check, :redis => @@redis)
117
+ Flapjack::API::EntityCheckPresenter.new(entity_check)
118
+ else
119
+ Flapjack::API::EntityPresenter.new(entity, :redis => @@redis)
120
+ end
121
+
122
+ presenter.outages(start_time, end_time).to_json
123
+ end
124
+
125
+ get %r{/unscheduled_maintenances/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
126
+ content_type :json
127
+
128
+ entity_name = params[:captures][0]
129
+ check = params[:captures][1]
130
+
131
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @@redis)
132
+ if entity.nil?
133
+ status 404
134
+ return
135
+ end
136
+
137
+ start_time = validate_and_parsetime(params[:start_time])
138
+ end_time = validate_and_parsetime(params[:end_time])
139
+
140
+ presenter = if check
141
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
142
+ check, :redis => @@redis)
143
+ Flapjack::API::EntityCheckPresenter.new(entity_check)
144
+ else
145
+ Flapjack::API::EntityPresenter.new(entity, :redis => @@redis)
146
+ end
147
+
148
+ presenter.unscheduled_maintenance(start_time, end_time).to_json
149
+ end
150
+
151
+ get %r{/scheduled_maintenances/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
152
+ content_type :json
153
+
154
+ entity_name = params[:captures][0]
155
+ check = params[:captures][1]
156
+
157
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @@redis)
158
+ if entity.nil?
159
+ status 404
160
+ return
161
+ end
162
+
163
+ start_time = validate_and_parsetime(params[:start_time])
164
+ end_time = validate_and_parsetime(params[:end_time])
165
+
166
+ presenter = if check
167
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
168
+ check, :redis => @@redis)
169
+ Flapjack::API::EntityCheckPresenter.new(entity_check)
170
+ else
171
+ Flapjack::API::EntityPresenter.new(entity, :redis => @@redis)
172
+ end
173
+ presenter.scheduled_maintenance(start_time, end_time).to_json
174
+ end
175
+
176
+ get %r{/downtime/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(\w+))?} do
177
+ content_type :json
178
+
179
+ entity_name = params[:captures][0]
180
+ check = params[:captures][1]
181
+
182
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @@redis)
183
+ if entity.nil?
184
+ status 404
185
+ return
186
+ end
187
+
188
+ start_time = validate_and_parsetime(params[:start_time])
189
+ end_time = validate_and_parsetime(params[:end_time])
190
+
191
+ presenter = if check
192
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
193
+ check, :redis => @@redis)
194
+ Flapjack::API::EntityCheckPresenter.new(entity_check)
195
+ else
196
+ Flapjack::API::EntityPresenter.new(entity, :redis => @@redis)
197
+ end
198
+
199
+ presenter.downtime(start_time, end_time).to_json
200
+ end
201
+
202
+ # create a scheduled maintenance period for a service on an entity
203
+ post '/scheduled_maintenances/:entity/:check' do
204
+ content_type :json
205
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => @@redis)
206
+ if entity.nil?
207
+ status 404
208
+ return
209
+ end
210
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
211
+ params[:check], :redis => @@redis)
212
+ entity_check.create_scheduled_maintenance(:start_time => params[:start_time],
213
+ :duration => params[:duration], :summary => params[:summary])
214
+ status 201
215
+ end
216
+
217
+ # create an acknowledgement for a service on an entity
218
+ # NB currently, this does not acknowledge a specific failure event, just
219
+ # the entity-check as a whole
220
+ post '/acknowledgements/:entity/:check' do
221
+ content_type :json
222
+ entity = Flapjack::Data::Entity.find_by_name(params[:entity], :redis => @@redis)
223
+ if entity.nil?
224
+ status 404
225
+ return
226
+ end
227
+
228
+ dur = params[:duration] ? params[:duration].to_i : nil
229
+ duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
230
+
231
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
232
+ params[:check], :redis => @@redis)
233
+ entity_check.create_acknowledgement('summary' => params[:summary],
234
+ 'duration' => duration)
235
+ status 201
236
+ end
237
+
238
+ not_found do
239
+ json_status 404, "Not found"
240
+ end
241
+
242
+ error do
243
+ json_status 500, env['sinatra.error'].message
244
+ end
245
+
246
+ private
247
+
248
+ def entity_check_status(entity, check)
249
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
250
+ check, :redis => @@redis)
251
+ return if entity_check.nil?
252
+ { 'name' => check,
253
+ 'state' => entity_check.state,
254
+ 'in_unscheduled_maintenance' => entity_check.in_unscheduled_maintenance?,
255
+ 'in_scheduled_maintenance' => entity_check.in_scheduled_maintenance?,
256
+ 'last_update' => entity_check.last_update,
257
+ 'last_problem_notification' => entity_check.last_problem_notification,
258
+ 'last_recovery_notification' => entity_check.last_recovery_notification,
259
+ 'last_acknowledgement_notification' => entity_check.last_acknowledgement_notification
260
+ }
261
+ end
262
+
263
+ # NB: casts to UTC before converting to a timestamp
264
+ def validate_and_parsetime(value)
265
+ return unless value
266
+ Time.iso8601(value).getutc.to_i
267
+ rescue ArgumentError => e
268
+ # FIXME log error
269
+ nil
270
+ end
271
+
272
+ end
273
+
274
+ end
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Formats entity/check data for presentation by the API methods in Flapjack::API.
4
+
5
+ require 'sinatra/base'
6
+
7
+ require 'flapjack/data/entity_check'
8
+
9
+ module Flapjack
10
+
11
+ class API < Sinatra::Base
12
+
13
+ class EntityCheckPresenter
14
+
15
+ def initialize(entity_check)
16
+ @entity_check = entity_check
17
+ end
18
+
19
+ # if options[:chop] is true, overlapping outages at start and end
20
+ # times will be sliced to fit.
21
+ def outages(start_time, end_time, options = {})
22
+ # states is an array of hashes, with [state, timestamp, summary] keys
23
+ states = @entity_check.historical_states(start_time, end_time)
24
+ return states if states.empty?
25
+
26
+ # if it started failed, prepend the earlier event
27
+ initial = @entity_check.historical_state_before(states.first[:timestamp])
28
+ if (initial && (initial[:state] == Flapjack::Data::EntityCheck::STATE_CRITICAL))
29
+ states.unshift(initial)
30
+ end
31
+
32
+ # if it ended failed, append the event when it recovered
33
+ if states.last[:state] == Flapjack::Data::EntityCheck::STATE_CRITICAL
34
+ # TODO ensure this event is not CRITICAL, get first non-CRITICAL if so
35
+ last = @entity_check.historical_state_after(states.last[:timestamp])
36
+ states.push(last) if last
37
+ end
38
+
39
+ last_state = nil
40
+
41
+ # returns an array of hashes, with [:start_time, :end_time, :summary]
42
+ time_periods = states.inject([]) do |ret, obj|
43
+ if (obj[:state] == Flapjack::Data::EntityCheck::STATE_CRITICAL) &&
44
+ (last_state.nil? || (last_state != Flapjack::Data::EntityCheck::STATE_CRITICAL))
45
+
46
+ # flipped to failed, mark next outage
47
+ last_state = obj[:state]
48
+ ret << {:start_time => obj[:timestamp], :end_time => nil, :summary => obj[:summary]}
49
+ elsif (obj[:state] != Flapjack::Data::EntityCheck::STATE_CRITICAL) &&
50
+ (last_state == Flapjack::Data::EntityCheck::STATE_CRITICAL)
51
+
52
+ # flipped to not failed, mark end time for the current outage
53
+ last_state = obj[:state]
54
+ ret.last[:end_time] = obj[:timestamp]
55
+ end
56
+ ret
57
+ end
58
+
59
+ if options[:chop]
60
+ if start_time && (time_periods.first[:start_time] < start_time)
61
+ time_periods.first[:start_time] = start_time
62
+ end
63
+ if time_periods.last[:end_time].nil? || (end_time && (time_periods.last[:end_time] > end_time))
64
+ time_periods.last[:end_time] = (end_time || Time.now.to_i)
65
+ end
66
+ end
67
+
68
+ time_periods
69
+ end
70
+
71
+ def unscheduled_maintenance(start_time, end_time)
72
+ # unsched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
73
+ unsched_maintenance = @entity_check.maintenances(start_time, end_time,
74
+ :scheduled => false)
75
+
76
+ # to see if we start in an unscheduled maintenance period, we must check all unscheduled
77
+ # maintenances before the period and their durations
78
+ start_in_unsched = start_time.nil? ? [] :
79
+ @entity_check.maintenances(nil, start_time, :scheduled => false).select {|pu|
80
+ pu[:end_time] >= start_time
81
+ }
82
+
83
+ start_in_unsched + unsched_maintenance
84
+ end
85
+
86
+ def scheduled_maintenance(start_time, end_time)
87
+ # sched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
88
+ sched_maintenance = @entity_check.maintenances(start_time, end_time,
89
+ :scheduled => true)
90
+
91
+ # to see if we start in a scheduled maintenance period, we must check all scheduled
92
+ # maintenances before the period and their durations
93
+ start_in_sched = start_time.nil? ? [] :
94
+ @entity_check.maintenances(nil, start_time, :scheduled => true).select {|ps|
95
+ ps[:end_time] >= start_time
96
+ }
97
+
98
+ start_in_sched + sched_maintenance
99
+ end
100
+
101
+ # TODO test whether the below overlapping logic is prone to off-by-one
102
+ # errors; the numbers may line up more neatly if we consider outages to
103
+ # start one second after the maintenance period ends.
104
+ #
105
+ # TODO test performance with larger data sets
106
+ def downtime(start_time, end_time)
107
+ sched_maintenances = scheduled_maintenance(start_time, end_time)
108
+
109
+ outs = outages(start_time, end_time, :chop => true)
110
+
111
+ total_secs = 0
112
+ percentage = 0
113
+
114
+ unless outs.empty?
115
+
116
+ # Initially we need to check for cases where a scheduled
117
+ # maintenance period is fully covered by an outage period.
118
+ # We then create two new outage periods to cover the time around
119
+ # the scheduled maintenance period, and remove the original.
120
+
121
+ sched_maintenances.each do |sm|
122
+
123
+ split_outs = []
124
+
125
+ outs.each { |o|
126
+ next unless o[:start_time] < sm[:start_time] &&
127
+ o[:end_time] > sm[:end_time]
128
+ o[:delete] = true
129
+ split_outs += [{:start_time => o[:start_time],
130
+ :end_time => sm[:start_time],
131
+ :summary => "#{o[:summary]} [split start]"},
132
+ {:start_time => sm[:end_time],
133
+ :end_time => o[:end_time],
134
+ :summary => "#{o[:summary]} [split finish]"}]
135
+ }
136
+
137
+ outs.reject! {|o| o[:delete]}
138
+ outs += split_outs
139
+ # not strictly necessary to keep the data sorted, but
140
+ # will make more sense while debgging
141
+ outs.sort! {|a,b| a[:start_time] <=> b[:start_time]}
142
+ end
143
+
144
+ sched_maintenances.each do |sm|
145
+
146
+ outs.each do |o|
147
+ next unless (sm[:start_time] < o[:end_time]) &&
148
+ (sm[:end_time] > o[:start_time])
149
+
150
+ if sm[:start_time] <= o[:start_time] &&
151
+ sm[:end_time] >= o[:end_time]
152
+
153
+ # outage is fully overlapped by the scheduled maintenance
154
+ o[:delete] = true
155
+
156
+ elsif sm[:start_time] <= o[:start_time]
157
+ # partially overlapping on the earlier side
158
+ o[:start_time] = sm[:end_time]
159
+ elsif sm[:end_time] >= o[:end_time]
160
+ # partially overlapping on the later side
161
+ o[:end_time] = sm[:start_time]
162
+ end
163
+ end
164
+
165
+ outs.reject! {|o| o[:delete]}
166
+ end
167
+
168
+ # sum outage times
169
+ total_secs = outs.inject(0) {|sum, o|
170
+ sum += (o[:end_time] - o[:start_time])
171
+ }
172
+
173
+ percentage = (start_time.nil? || end_time.nil?) ? nil :
174
+ (total_secs * 100) / (end_time - start_time)
175
+ end
176
+
177
+ {:total_seconds => total_secs, :percentage => percentage, :downtime => outs}
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+
184
+ end