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.
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
data/flapjack.gemspec CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/flapjack/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = [ "Lindsay Holmwood", "Jesse Reynolds", "Ali Graham" ]
6
6
  gem.email = "lindsay@holmwood.id.au"
7
- gem.description = "Flapjack is distributed monitoring notification system that provides a scalable method for processing streams of events from Nagios and deciding who should be notified"
7
+ gem.description = "Flapjack is a distributed monitoring notification system that provides a scalable method for processing streams of events from Nagios and deciding who should be notified"
8
8
  gem.summary = "Intelligent, scalable, distributed monitoring notification system."
9
- gem.homepage = "http://flapjack-project.com/"
9
+ gem.homepage = "http://flapjack.io/"
10
10
  gem.license = 'MIT'
11
11
 
12
12
  # see http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
@@ -24,7 +24,7 @@ Gem::Specification.new do |gem|
24
24
  gem.add_dependency 'hiredis'
25
25
  gem.add_dependency 'em-synchrony', '~> 1.0.2'
26
26
  gem.add_dependency 'em-http-request'
27
- gem.add_dependency 'redis'
27
+ gem.add_dependency 'redis', '~> 3.0.6'
28
28
  gem.add_dependency 'em-resque'
29
29
  gem.add_dependency 'resque', '~> 1.23.0'
30
30
  gem.add_dependency 'sinatra'
@@ -38,6 +38,7 @@ Gem::Specification.new do |gem|
38
38
  gem.add_dependency 'ice_cube'
39
39
  gem.add_dependency 'tzinfo', '~> 1.0.1'
40
40
  gem.add_dependency 'tzinfo-data'
41
+ gem.add_dependency 'tzinfo-data'
41
42
 
42
43
  gem.add_development_dependency 'rake'
43
44
  end
@@ -10,13 +10,17 @@ require 'flapjack/data/notification_rule'
10
10
  require 'flapjack/data/tag'
11
11
  require 'flapjack/data/tag_set'
12
12
 
13
+ require 'securerandom'
14
+
13
15
  module Flapjack
14
16
 
15
17
  module Data
16
18
 
17
19
  class Contact
18
20
 
19
- attr_accessor :id, :first_name, :last_name, :email, :media, :media_intervals, :media_rollup_thresholds, :pagerduty_credentials
21
+ attr_accessor :id, :first_name, :last_name, :email, :media,
22
+ :media_intervals, :media_rollup_thresholds, :pagerduty_credentials,
23
+ :linked_entity_ids
20
24
 
21
25
  TAG_PREFIX = 'contact_tag'
22
26
  ALL_MEDIA = ['email', 'sms', 'jabber', 'pagerduty']
@@ -46,6 +50,22 @@ module Flapjack
46
50
  contact
47
51
  end
48
52
 
53
+ def self.find_by_ids(contact_ids, options = {})
54
+ raise "Redis connection not set" unless redis = options[:redis]
55
+ logger = options[:logger]
56
+
57
+ contact_ids.map do |id|
58
+ self.find_by_id(id, options)
59
+ end
60
+ end
61
+
62
+ def self.exists_with_id?(contact_id, options = {})
63
+ raise "Redis connection not set" unless redis = options[:redis]
64
+ raise "No id value passed" unless contact_id
65
+
66
+ redis.exists("contact:#{contact_id}")
67
+ end
68
+
49
69
  def self.add(contact_data, options = {})
50
70
  raise "Redis connection not set" unless redis = options[:redis]
51
71
  contact_id = contact_data['id']
@@ -169,6 +189,36 @@ module Flapjack
169
189
  }.values
170
190
  end
171
191
 
192
+ def self.entities_jsonapi(contact_ids, options = {})
193
+ raise "Redis connection not set" unless redis = options[:redis]
194
+
195
+ entity_data = []
196
+ linked_entity_ids = {}
197
+
198
+ temp_set = SecureRandom.uuid
199
+ redis.sadd(temp_set, contact_ids)
200
+
201
+ redis.keys('contacts_for:*').each do |k|
202
+ contact_ids = redis.sinter(k, temp_set)
203
+ next if contact_ids.empty?
204
+ next unless k =~ /^contacts_for:([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?::(\w+))?$/
205
+
206
+ entity_id = $1
207
+ check = $2
208
+
209
+ entity_data << {:id => entity_id, :name => redis.hget("entity:#{entity_id}", 'name')}
210
+
211
+ contact_ids.each do |contact_id|
212
+ linked_entity_ids[contact_id] ||= []
213
+ linked_entity_ids[contact_id] << entity_id
214
+ end
215
+ end
216
+
217
+ redis.del(temp_set)
218
+
219
+ [entity_data, linked_entity_ids]
220
+ end
221
+
172
222
  def name
173
223
  [(self.first_name || ''), (self.last_name || '')].join(" ").strip
174
224
  end
@@ -408,11 +458,17 @@ module Flapjack
408
458
  end
409
459
 
410
460
  def to_json(*args)
411
- { "id" => self.id,
412
- "first_name" => self.first_name,
413
- "last_name" => self.last_name,
414
- "email" => self.email,
415
- "tags" => self.tags.to_a }.to_json
461
+ { "id" => self.id,
462
+ "first_name" => self.first_name,
463
+ "last_name" => self.last_name,
464
+ "email" => self.email,
465
+ "media" => self.media,
466
+ "media_intervals" => self.media_intervals,
467
+ "media_rollup_thresholds" => self.media_rollup_thresholds,
468
+ "timezone" => self.timezone.name,
469
+ "tags" => self.tags.to_a,
470
+ "links" => {:entities => @linked_entity_ids || []}
471
+ }.to_json
416
472
  end
417
473
 
418
474
  private
@@ -433,6 +489,12 @@ module Flapjack
433
489
  redis.hmset("contact:#{contact_id}",
434
490
  *['first_name', 'last_name', 'email'].collect {|f| [f, contact_data[f]]})
435
491
 
492
+ if ( ! contact_data['tags'].nil? && contact_data['tags'].is_a?(Enumerable))
493
+ contact_data['tags'].each do |t|
494
+ Flapjack::Data::Tag.create("#{TAG_PREFIX}:#{t}", [contact_id], :redis => redis)
495
+ end
496
+ end
497
+
436
498
  unless contact_data['media'].nil?
437
499
  redis.del("contact_media:#{contact_id}")
438
500
  redis.del("contact_media_intervals:#{contact_id}")
@@ -452,6 +514,16 @@ module Flapjack
452
514
  end
453
515
  }
454
516
  end
517
+ if contact_data.key?('timezone')
518
+ tz = contact_data['timezone']
519
+ if tz.nil?
520
+ redis.del("contact_tz:#{contact_id}")
521
+ else
522
+ # ActiveSupport::TimeZone or String
523
+ redis.set("contact_tz:#{contact_id}",
524
+ tz.respond_to?(:name) ? tz.name : tz )
525
+ end
526
+ end
455
527
  end
456
528
 
457
529
  end
@@ -16,9 +16,11 @@ module Flapjack
16
16
 
17
17
  def self.all(options = {})
18
18
  raise "Redis connection not set" unless redis = options[:redis]
19
- redis.keys("entity_id:*").collect {|k|
19
+ keys = redis.keys("entity_id:*")
20
+ ids = redis.mget(keys)
21
+ keys.collect {|k|
20
22
  k =~ /^entity_id:(.+)$/; entity_name = $1
21
- self.new(:name => entity_name, :id => redis.get("entity_id:#{entity_name}"), :redis => redis)
23
+ self.new(:name => entity_name, :id => ids.shift, :redis => redis)
22
24
  }.sort_by(&:name)
23
25
  end
24
26
 
@@ -173,6 +175,13 @@ module Flapjack
173
175
  end
174
176
  end
175
177
 
178
+ def as_json(*args)
179
+ {
180
+ "id" => self.id,
181
+ "name" => self.name,
182
+ }
183
+ end
184
+
176
185
  private
177
186
 
178
187
  # NB: initializer should not be used directly -- instead one of the finder methods
@@ -121,11 +121,11 @@ module Flapjack
121
121
  refresh
122
122
  end
123
123
 
124
- def self.add_or_update(rule_data, options = {})
125
- redis = options[:redis]
126
- raise "a redis connection must be supplied" unless redis
127
- logger = options[:logger]
124
+ def self.prevalidate_data(rule_data, options = {})
125
+ errors = self.validate_data(preen(rule_data), options.merge(:id_not_required => true))
126
+ end
128
127
 
128
+ def self.preen(rule_data)
129
129
  # make some assumptions about the incoming data
130
130
  rule_data[:unknown_blackhole] = rule_data[:unknown_blackhole] || false
131
131
  rule_data[:warning_blackhole] = rule_data[:warning_blackhole] || false
@@ -133,9 +133,16 @@ module Flapjack
133
133
  if rule_data[:tags].is_a?(Array)
134
134
  rule_data[:tags] = Flapjack::Data::TagSet.new(rule_data[:tags])
135
135
  end
136
+ rule_data
137
+ end
136
138
 
137
- errors = self.validate_data(rule_data, options)
139
+ def self.add_or_update(rule_data, options = {})
140
+ redis = options[:redis]
141
+ raise "a redis connection must be supplied" unless redis
142
+ logger = options[:logger]
138
143
 
144
+ rule_data = preen(rule_data)
145
+ errors = self.validate_data(rule_data, options)
139
146
  return errors unless errors.nil? || errors.empty?
140
147
 
141
148
  # whitelisting fields, rather than passing through submitted data directly
@@ -227,61 +234,62 @@ module Flapjack
227
234
  end
228
235
 
229
236
  def self.validate_data(d, options = {})
237
+ id_not_required = !!options[:id_not_required]
230
238
  # hash with validation => error_message
231
- validations = {proc { d.has_key?(:id) } =>
232
- "id not set",
233
-
234
- proc { !d.has_key?(:entities) ||
235
- ( d[:entities].nil? ||
236
- d[:entities].is_a?(Array) &&
237
- d[:entities].all? {|e| e.is_a?(String)} ) } =>
238
- "entities must be a list of strings",
239
-
240
- proc { !d.has_key?(:tags) ||
241
- ( d[:tags].nil? ||
242
- d[:tags].is_a?(Flapjack::Data::TagSet) &&
243
- d[:tags].all? {|et| et.is_a?(String)} ) } =>
244
- "tags must be a tag_set of strings",
245
-
246
- proc { !d.has_key?(:time_restrictions) ||
247
- ( d[:time_restrictions].nil? ||
248
- d[:time_restrictions].all? {|tr|
249
- !!prepare_time_restriction(symbolize(tr))
250
- } )
251
- } =>
252
- "time restrictions are invalid",
253
-
254
- # TODO should the media types be checked against a whitelist?
255
- proc { !d.has_key?(:unknown_media) ||
256
- ( d[:unknown_media].nil? ||
257
- d[:unknown_media].is_a?(Array) &&
258
- d[:unknown_media].all? {|et| et.is_a?(String)} ) } =>
259
- "unknown_media must be a list of strings",
260
-
261
- proc { !d.has_key?(:warning_media) ||
262
- ( d[:warning_media].nil? ||
263
- d[:warning_media].is_a?(Array) &&
264
- d[:warning_media].all? {|et| et.is_a?(String)} ) } =>
265
- "warning_media must be a list of strings",
266
-
267
- proc { !d.has_key?(:critical_media) ||
268
- ( d[:critical_media].nil? ||
269
- d[:critical_media].is_a?(Array) &&
270
- d[:critical_media].all? {|et| et.is_a?(String)} ) } =>
271
- "critical_media must be a list of strings",
272
-
273
- proc { !d.has_key?(:unknown_blackhole) ||
274
- [TrueClass, FalseClass].include?(d[:unknown_blackhole].class) } =>
275
- "unknown_blackhole must be true or false",
276
-
277
- proc { !d.has_key?(:warning_blackhole) ||
278
- [TrueClass, FalseClass].include?(d[:warning_blackhole].class) } =>
279
- "warning_blackhole must be true or false",
280
-
281
- proc { !d.has_key?(:critical_blackhole) ||
282
- [TrueClass, FalseClass].include?(d[:critical_blackhole].class) } =>
283
- "critical_blackhole must be true or false",
284
- }
239
+ validations = {}
240
+ validations.merge!({ proc { d.has_key?(:id) } => "id not set"}) unless id_not_required
241
+ validations.merge!({
242
+ proc { !d.has_key?(:entities) ||
243
+ ( d[:entities].nil? ||
244
+ d[:entities].is_a?(Array) &&
245
+ d[:entities].all? {|e| e.is_a?(String)} ) } =>
246
+ "entities must be a list of strings",
247
+
248
+ proc { !d.has_key?(:tags) ||
249
+ ( d[:tags].nil? ||
250
+ d[:tags].is_a?(Flapjack::Data::TagSet) &&
251
+ d[:tags].all? {|et| et.is_a?(String)} ) } =>
252
+ "tags must be a tag_set of strings",
253
+
254
+ proc { !d.has_key?(:time_restrictions) ||
255
+ ( d[:time_restrictions].nil? ||
256
+ d[:time_restrictions].all? {|tr|
257
+ !!prepare_time_restriction(symbolize(tr))
258
+ } )
259
+ } =>
260
+ "time restrictions are invalid",
261
+
262
+ # TODO should the media types be checked against a whitelist?
263
+ proc { !d.has_key?(:unknown_media) ||
264
+ ( d[:unknown_media].nil? ||
265
+ d[:unknown_media].is_a?(Array) &&
266
+ d[:unknown_media].all? {|et| et.is_a?(String)} ) } =>
267
+ "unknown_media must be a list of strings",
268
+
269
+ proc { !d.has_key?(:warning_media) ||
270
+ ( d[:warning_media].nil? ||
271
+ d[:warning_media].is_a?(Array) &&
272
+ d[:warning_media].all? {|et| et.is_a?(String)} ) } =>
273
+ "warning_media must be a list of strings",
274
+
275
+ proc { !d.has_key?(:critical_media) ||
276
+ ( d[:critical_media].nil? ||
277
+ d[:critical_media].is_a?(Array) &&
278
+ d[:critical_media].all? {|et| et.is_a?(String)} ) } =>
279
+ "critical_media must be a list of strings",
280
+
281
+ proc { !d.has_key?(:unknown_blackhole) ||
282
+ [TrueClass, FalseClass].include?(d[:unknown_blackhole].class) } =>
283
+ "unknown_blackhole must be true or false",
284
+
285
+ proc { !d.has_key?(:warning_blackhole) ||
286
+ [TrueClass, FalseClass].include?(d[:warning_blackhole].class) } =>
287
+ "warning_blackhole must be true or false",
288
+
289
+ proc { !d.has_key?(:critical_blackhole) ||
290
+ [TrueClass, FalseClass].include?(d[:critical_blackhole].class) } =>
291
+ "critical_blackhole must be true or false",
292
+ })
285
293
 
286
294
  errors = validations.keys.inject([]) {|ret,vk|
287
295
  ret << "Rule #{validations[vk]}" unless vk.call
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'securerandom'
4
+
5
+ module Flapjack
6
+ module Data
7
+
8
+ # http://redis.io/commands/set
9
+ class Semaphore
10
+
11
+ SEMAPHORE_KEYSPACE = 'semaphores:'
12
+
13
+ attr_reader :token, :expiry, :resource
14
+
15
+ class ResourceLocked < RuntimeError
16
+ end
17
+
18
+ def initialize(resource, options)
19
+ raise "redis connection must be passed in options" unless @redis = options[:redis]
20
+ @resource = resource
21
+ @token = options[:token] || SecureRandom.uuid
22
+ @expiry = options[:expiry] || 30
23
+
24
+ @key = "#{SEMAPHORE_KEYSPACE}#{@resource}"
25
+
26
+ raise Flapjack::Data::Semaphore::ResourceLocked.new unless @redis.set(@key, @token, {:nx => true, :ex => @expiry})
27
+ end
28
+
29
+ def release
30
+ unlock_script = '
31
+ if redis.call("get",KEYS[1]) == ARGV[1]
32
+ then
33
+ return redis.call("del",KEYS[1])
34
+ else
35
+ return 0
36
+ end
37
+ '
38
+ @redis.eval(unlock_script, [@key], [@token])
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+
@@ -27,41 +27,37 @@ module Flapjack
27
27
 
28
28
  include Flapjack::Utility
29
29
 
30
- set :show_exceptions, false
30
+ set :dump_errors, false
31
31
 
32
- rescue_exception = Proc.new { |env, exception|
33
-
34
- error = proc {|status, exception, *msg|
35
- if !msg || msg.empty?
36
- trace = exception.backtrace.join("\n")
37
- msg = "#{exception.class} - #{exception.message}"
38
- msg_str = "#{msg}\n#{trace}"
39
- else
40
- msg_str = msg.join(", ")
41
- end
42
- logger = Flapjack::Gateways::API.instance_variable_get('@logger')
43
- case
44
- when status < 500
45
- logger.warn "Error: #{msg_str}"
46
- else
47
- logger.error "Error: #{msg_str}"
48
- end
49
- [status, {}, {:errors => msg}.to_json]
50
- }
51
-
52
- e = env['sinatra.error']
32
+ rescue_error = Proc.new {|status, exception, *msg|
33
+ if !msg || msg.empty?
34
+ trace = exception.backtrace.join("\n")
35
+ msg = "#{exception.class} - #{exception.message}"
36
+ msg_str = "#{msg}\n#{trace}"
37
+ else
38
+ msg_str = msg.join(", ")
39
+ end
40
+ case
41
+ when status < 500
42
+ @logger.warn "Error: #{msg_str}"
43
+ else
44
+ @logger.error "Error: #{msg_str}"
45
+ end
46
+ [status, {}, {:errors => msg}.to_json]
47
+ }
53
48
 
54
- case exception
49
+ rescue_exception = Proc.new {|env, e|
50
+ case e
55
51
  when Flapjack::Gateways::API::ContactNotFound
56
- error.call(403, e, "could not find contact '#{e.contact_id}'")
52
+ rescue_error.call(403, e, "could not find contact '#{e.contact_id}'")
57
53
  when Flapjack::Gateways::API::NotificationRuleNotFound
58
- error.call(403, e, "could not find notification rule '#{e.rule_id}'")
54
+ rescue_error.call(403, e, "could not find notification rule '#{e.rule_id}'")
59
55
  when Flapjack::Gateways::API::EntityNotFound
60
- error.call(403, e, "could not find entity '#{e.entity}'")
56
+ rescue_error.call(403, e, "could not find entity '#{e.entity}'")
61
57
  when Flapjack::Gateways::API::EntityCheckNotFound
62
- error.call(403, e, "could not find entity check '#{e.check}'")
58
+ rescue_error.call(403, e, "could not find entity check '#{e.check}'")
63
59
  else
64
- error.call(500, exception)
60
+ rescue_error.call(500, e)
65
61
  end
66
62
  }
67
63
  use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception