flapjack 0.5.5 → 0.6.23

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.
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