flapjack 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ede6c2a1d9cef33334510db3aafc14c3adfe81e
4
- data.tar.gz: 1e9dd581aec598085752a982f13d93a0936799de
3
+ metadata.gz: a9f395ed95f7e409a6dccf833a149abce9ba2c3c
4
+ data.tar.gz: 02c26390a79ff90130c1c9c3a6fe1618c1c0341e
5
5
  SHA512:
6
- metadata.gz: 4f7d21e0b77c86255e58a11786e7a288bc0b11101f1357d764f14bf8b0c6232566293fd577f3ba8652b81311c22e973954581ddcb08a243bfdd0b7d82ebd5cc1
7
- data.tar.gz: 1cb8e1563662182441a1067baf645b9410133e480af1ec31fb28cb6d39978273e6b046ee67bfe01164a54a7988ff1d03bbd3eda7dc3359a3ef1da4f70c43a8ae
6
+ metadata.gz: caba9206c27111faae12acef641840b0fbd44cd7e8227462d0ba30bedf5a21cd71f54070ffc33015478eda857f06a28666a3772127a554f0b262545022bcd84f
7
+ data.tar.gz: c2af3088fc0154cc99f3520a254fa57e49ec110d1e0ccd0649fed0a21dc5462497eded455565131dc99e63e71d3725adf57ba725ae0442ea9b65197c78daac34
data/.travis.yml CHANGED
@@ -2,6 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - "1.9.3"
4
4
  - "2.0.0"
5
+ - "2.1.0"
5
6
  env:
6
7
  - ENTITIES=10 INTERVAL=120
7
8
  gemfile:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## Flapjack Changelog
2
2
 
3
+ # 0.8.2 - 2014-01-16
4
+ - Bug: no HTML parsing in summary of scheduled maintenance (thanks @marek-knappe) gh-413 (@ali-graham)
5
+ - Bug: if tags and entities are present in a notification rule, both should match the event (thanks @jswoods) gh-417 (@jessereynolds)
6
+ - Bug: improperly structured events crash the processor gh-411 (thanks @someword) (@ali-graham)
7
+ - Bug: Critical events are no longer showing up as red in the flapjack UI gh-419 (thanks @jswoods) (@jessereynolds)
8
+
9
+ # 0.8.1 - 2014-01-06
10
+ - Bug: JSONAPI: 500 on GET /entities with no entities in the db gh-409 (@jessereynolds)
11
+
12
+ # 0.8.0 - 2014-01-06
13
+ - Feature: Start rewriting API as per jsonapi.org gh-253 (@ali-graham, @jessereynolds)
14
+ - Feature: API consuming webapp beginnings gh-253 (@ali-graham, @auxesis, @jessereynolds)
15
+ - Feature: New styles for navigation and so on (@auxesis)
16
+ - Chore: Upgrade Rspec to v3 gh-389 (@ali-graham)
17
+ - Chore: Make README clearer gh-406 (@auxesis)
18
+ - Bug: Listing notification rules for a non existent contact gives 500, should be 404 gh-392 (@ali-graham)
19
+
3
20
  # 0.7.35 - 2013-12-10
4
21
  - Feature: allow flapper to flap with an arbitrary interval gh-383 (@jessereynolds)
5
22
  - Feature: Expose statistics for currency of all checks gh-386 (@jessereynolds)
data/bin/flapjack CHANGED
@@ -163,7 +163,7 @@ when "start"
163
163
  return_value = flapjack_coord.call
164
164
  }
165
165
  puts " done."
166
- exit(return_value + 128) unless (return_value.nil? || (return_value == 0))
166
+ exit(return_value) unless return_value.nil?
167
167
  end
168
168
 
169
169
  when "stop"
data/features/cli.feature CHANGED
@@ -69,6 +69,7 @@ test:
69
69
 
70
70
  Scenario: Reloading flapjack configuration
71
71
  When I start flapjack with `flapjack start --config tmp/cucumber_cli/flapjack_cfg.yaml`
72
+ Then flapjack should start within 15 seconds
72
73
  When I run `mv tmp/cucumber_cli/flapjack_cfg.yaml tmp/cucumber_cli/flapjack_cfg.yaml.bak`
73
74
  Given a file named "flapjack_cfg.yaml" with:
74
75
  """
@@ -80,7 +81,7 @@ test:
80
81
  """
81
82
  When I send a SIGHUP to the flapjack process
82
83
  # TODO how to test for config file change?
83
- When I send a SIGINT to the flapjack process
84
+ When I stop flapjack with `flapjack stop --config tmp/cucumber_cli/flapjack_cfg_d.yaml`
84
85
  Then flapjack should stop within 15 seconds
85
86
 
86
87
 
@@ -3,12 +3,13 @@ Feature: Notification rules on a per contact basis
3
3
 
4
4
  Background:
5
5
  Given the following users exist:
6
- | id | first_name | last_name | email | sms | timezone |
7
- | c1 | Malak | Al-Musawi | malak@example.com | +61400000001 | Asia/Baghdad |
8
- | c2 | Imani | Farooq | imani@example.com | +61400000002 | Europe/Moscow |
9
- | c3 | Vera | Дурейко | vera@example.com | +61400000003 | Europe/Paris |
10
- | c4 | Lucia | Moretti | lucia@example.com | +61400000004 | Europe/Rome |
11
- | c5 | Wang Fang | Wong | fang@example.com | +61400000005 | Asia/Shanghai |
6
+ | id | first_name | last_name | email | sms | timezone |
7
+ | c1 | Malak | Al-Musawi | malak@example.com | +61400000001 | Asia/Baghdad |
8
+ | c2 | Imani | Farooq | imani@example.com | +61400000002 | Europe/Moscow |
9
+ | c3 | Vera | Дурейко | vera@example.com | +61400000003 | Europe/Paris |
10
+ | c4 | Lucia | Moretti | lucia@example.com | +61400000004 | Europe/Rome |
11
+ | c5 | Wang Fang | Wong | fang@example.com | +61400000005 | Asia/Shanghai |
12
+ | c6 | Jive | Smith | jive@example.com | +61400000006 | America/Los_Angeles |
12
13
 
13
14
  And the following entities exist:
14
15
  | id | name | contacts |
@@ -16,7 +17,7 @@ Feature: Notification rules on a per contact basis
16
17
  | 2 | bar | c1,c2,c3 |
17
18
  | 3 | baz | c1,c3 |
18
19
  | 4 | buf | c1,c2,c3 |
19
- | 5 | foo-app-01.xyz | c4 |
20
+ | 5 | foo-app-01.xyz | c4,c6 |
20
21
 
21
22
  And user c1 has the following notification intervals:
22
23
  | email | sms |
@@ -66,6 +67,11 @@ Feature: Notification rules on a per contact basis
66
67
  | unknown_media | critical_media |
67
68
  | email | email, sms |
68
69
 
70
+ And user c6 has the following notification rules:
71
+ | entities | tags | warning_media | critical_media |
72
+ | | | | |
73
+ | foo-app-01.xyz | check_disk | email | email |
74
+
69
75
  @time_restrictions @time
70
76
  Scenario: Alerts only during specified time restrictions
71
77
  Given the timezone is Asia/Baghdad
@@ -380,6 +386,15 @@ Feature: Notification rules on a per contact basis
380
386
  And a critical event is received
381
387
  Then no sms alerts should be queued for +61400000004
382
388
 
389
+ @time
390
+ Scenario: Don't notify when tags in a rule don't match, but the entity does match
391
+ Given the check is check 'Memory Util' on entity 'foo-app-01.xyz'
392
+ And the check is in an ok state
393
+ When a critical event is received
394
+ And 1 minute passes
395
+ And a critical event is received
396
+ Then no email alerts should be queued for jive@example.com
397
+
383
398
  @time
384
399
  Scenario: Only notify during specified time periods in tag matched rules
385
400
  Given the timezone is Europe/Rome
@@ -83,8 +83,7 @@ def submit_ok(entity, check)
83
83
  'state' => 'ok',
84
84
  'summary' => '0% packet loss',
85
85
  'entity' => entity,
86
- 'check' => check,
87
- 'client' => 'clientx'
86
+ 'check' => check
88
87
  }
89
88
  submit_event(event)
90
89
  end
@@ -95,8 +94,7 @@ def submit_warning(entity, check)
95
94
  'state' => 'warning',
96
95
  'summary' => '25% packet loss',
97
96
  'entity' => entity,
98
- 'check' => check,
99
- 'client' => 'clientx'
97
+ 'check' => check
100
98
  }
101
99
  submit_event(event)
102
100
  end
@@ -107,8 +105,7 @@ def submit_critical(entity, check)
107
105
  'state' => 'critical',
108
106
  'summary' => '100% packet loss',
109
107
  'entity' => entity,
110
- 'check' => check,
111
- 'client' => 'clientx'
108
+ 'check' => check
112
109
  }
113
110
  submit_event(event)
114
111
  end
@@ -119,8 +116,7 @@ def submit_unknown(entity, check)
119
116
  'state' => 'unknown',
120
117
  'summary' => 'check execution error',
121
118
  'entity' => entity,
122
- 'check' => check,
123
- 'client' => 'clientx'
119
+ 'check' => check
124
120
  }
125
121
  submit_event(event)
126
122
  end
@@ -131,8 +127,7 @@ def submit_acknowledgement(entity, check)
131
127
  'state' => 'acknowledgement',
132
128
  'summary' => "I'll have this fixed in a jiffy, saw the same thing yesterday",
133
129
  'entity' => entity,
134
- 'check' => check,
135
- 'client' => 'clientx',
130
+ 'check' => check
136
131
  }
137
132
  submit_event(event)
138
133
  end
@@ -143,8 +138,7 @@ def submit_test(entity, check)
143
138
  'state' => 'test_notifications',
144
139
  'summary' => "test notification for all contacts interested in #{entity}",
145
140
  'entity' => entity,
146
- 'check' => check,
147
- 'client' => 'clientx',
141
+ 'check' => check
148
142
  }
149
143
  submit_event(event)
150
144
  end
@@ -21,6 +21,8 @@ module Flapjack
21
21
  @redis_options = config.for_redis
22
22
  @pikelets = []
23
23
 
24
+ @received_signals = []
25
+
24
26
  @logger = Flapjack::Logger.new("flapjack-coordinator", @config.all['logger'])
25
27
  end
26
28
 
@@ -29,22 +31,39 @@ module Flapjack
29
31
 
30
32
  EM.synchrony do
31
33
  setup_signals if options[:signals]
32
- add_pikelets(pikelets(@config.all))
34
+
35
+ begin
36
+ add_pikelets(pikelets(@config.all))
37
+ loop do
38
+ while sig = @received_signals.shift do
39
+ case sig
40
+ when 'INT', 'TERM', 'QUIT'
41
+ @exit_value = Signal.list[sig] + 128
42
+ raise Interrupt
43
+ when 'HUP'
44
+ reload
45
+ end
46
+ end
47
+ EM::Synchrony.sleep 0.25
48
+ end
49
+ rescue Exception => e
50
+ unless e.is_a?(Interrupt)
51
+ trace = e.backtrace.join("\n")
52
+ @logger.fatal "#{e.class.name}\n#{e.message}\n#{trace}"
53
+ @exit_value = 1
54
+ end
55
+ remove_pikelets(@pikelets)
56
+ EM.stop
57
+ end
33
58
  end
34
59
 
60
+ Syslog.close if Syslog.opened?
61
+
35
62
  @exit_value
36
63
  end
37
64
 
38
- def stop(value = 0)
39
- return unless @exit_value.nil?
40
- @exit_value = value
41
- remove_pikelets(@pikelets, :shutdown => true)
42
- # Syslog.close if Syslog.opened? # TODO revisit in threading branch
43
- end
65
+ private
44
66
 
45
- # NB: global config options (e.g. daemonize, pidfile,
46
- # logfile, redis options) won't be checked on reload.
47
- # should we do a full restart if some of these change?
48
67
  def reload
49
68
  prev_pikelet_cfg = pikelets(@config.all)
50
69
 
@@ -85,85 +104,61 @@ module Flapjack
85
104
  added << pik.type
86
105
  end
87
106
 
88
- # puts "removed"
89
- # p removed
90
-
91
- # puts "added"
92
- # p added
93
-
94
107
  removed_pikelets = @pikelets.select {|pik| removed.include?(pik.type) }
95
108
 
96
- # puts "removed pikelets"
97
- # p removed_pikelets
98
-
99
109
  remove_pikelets(removed_pikelets)
100
110
 
101
111
  # is there a nicer way to only keep the parts of the hash with matching keys?
102
112
  added_pikelets = enabled_pikelet_cfg.select {|k, v| added.include?(k) }
103
113
 
104
- # puts "added pikelet configs"
105
- # p added_pikelets
106
-
107
114
  add_pikelets(added_pikelets)
108
115
  end
109
116
 
110
- private
111
-
112
117
  # the global nature of this seems at odds with it calling stop
113
118
  # within a single coordinator instance. Coordinator is essentially
114
119
  # a singleton anyway...
115
120
  def setup_signals
116
- Kernel.trap('INT') { stop(Signal.list['INT']) }
117
- Kernel.trap('TERM') { stop(Signal.list['TERM']) }
121
+ Kernel.trap('INT') { @received_signals << 'INT' unless @received_signals.include?('INT') }
122
+ Kernel.trap('TERM') { @received_signals << 'TERM' unless @received_signals.include?('TERM') }
118
123
  unless RbConfig::CONFIG['host_os'] =~ /mswin|windows|cygwin/i
119
- Kernel.trap('QUIT') { stop(Signal.list['QUIT']) }
120
- Kernel.trap('HUP') { reload }
124
+ Kernel.trap('QUIT') { @received_signals << 'QUIT' unless @received_signals.include?('QUIT') }
125
+ Kernel.trap('HUP') { @received_signals << 'HUP' unless @received_signals.include?('HUP') }
121
126
  end
122
127
  end
123
128
 
124
129
  # passed a hash with {PIKELET_TYPE => PIKELET_CFG, ...}
125
130
  def add_pikelets(pikelets_data = {})
126
- start_piks = []
127
131
  pikelets_data.each_pair do |type, cfg|
128
132
  next unless pikelet = Flapjack::Pikelet.create(type,
129
- :config => cfg, :redis_config => @redis_options, :boot_time => @boot_time, :coordinator => self)
130
- start_piks << pikelet
133
+ :config => cfg, :redis_config => @redis_options,
134
+ :boot_time => @boot_time)
135
+
131
136
  @pikelets << pikelet
132
- end
133
- begin
134
- start_piks.each {|pik| pik.start }
135
- rescue Exception => e
136
- trace = e.backtrace.join("\n")
137
- @logger.fatal "#{e.class.name}\n#{e.message}\n#{trace}"
138
- stop
137
+ pikelet.start
139
138
  end
140
139
  end
141
140
 
142
141
  def remove_pikelets(piks, opts = {})
143
- Fiber.new {
144
- piks.map(&:stop)
145
-
146
- loop do
147
- # only prints state changes, otherwise pikelets not closing promptly can
148
- # cause everything else to be spammy
149
- piks.each do |pik|
150
- old_status = pik.status
151
- pik.update_status
152
- status = pik.status
153
- next if old_status.eql?(status)
154
- # # can't log on exit w/Ruby 2.0
155
- # @logger.info "#{pik.type}: #{old_status} -> #{status}"
156
- end
142
+ piks.map(&:stop)
143
+
144
+ loop do
145
+ # only prints state changes, otherwise pikelets not closing promptly can
146
+ # cause everything else to be spammy
147
+ piks.each do |pik|
148
+ old_status = pik.status
149
+ pik.update_status
150
+ status = pik.status
151
+ next if old_status.eql?(status)
152
+ @logger.info "#{pik.type}: #{old_status} -> #{status}"
153
+ end
157
154
 
158
- if piks.any? {|p| p.status == 'stopping' }
159
- EM::Synchrony.sleep 0.25
160
- else
161
- EM.stop if opts[:shutdown]
162
- @pikelets -= piks
163
- break
164
- end
155
+ if piks.any? {|p| p.status == 'stopping' }
156
+ EM::Synchrony.sleep 0.25
157
+ else
158
+ @pikelets -= piks
159
+ break
165
160
  end
166
- }.resume
161
+ end
167
162
  end
168
163
 
169
164
  def pikelets(config_env)
@@ -10,6 +10,44 @@ module Flapjack
10
10
 
11
11
  attr_reader :check, :summary, :details, :acknowledgement_id
12
12
 
13
+ REQUIRED_KEYS = ['type', 'state', 'entity', 'check', 'summary']
14
+ OPTIONAL_KEYS = ['time', 'details', 'acknowledgement_id', 'duration']
15
+
16
+ VALIDATIONS = {
17
+ proc {|e| ['service', 'action'].include?(e['type']) } =>
18
+ "type must be either 'service' or 'action'",
19
+
20
+ proc {|e| ['ok', 'warning', 'critical', 'unknown', 'acknowledgement', 'test_notifications'].include?(e['state']) } =>
21
+ "state must be one of 'ok', 'warning', 'critical', 'unknown', 'acknowledgement' or 'test_notifications'",
22
+
23
+ proc {|e| e['entity'].is_a?(String) } =>
24
+ "entity must be a string",
25
+
26
+ proc {|e| e['check'].is_a?(String) } =>
27
+ "check must be a string",
28
+
29
+ proc {|e| e['summary'].is_a?(String) } =>
30
+ "summary must be a string",
31
+
32
+ proc {|e| e['time'].nil? ||
33
+ e['time'].is_a?(Integer) ||
34
+ (e['time'].is_a?(String) && !!(parsed_duration =~ /^\d+$/)) } =>
35
+ "time must be a positive integer, or a string castable to one",
36
+
37
+ proc {|e| e['details'].nil? || e['details'].is_a?(String) } =>
38
+ "details must be a string",
39
+
40
+ proc {|e| e['acknowledgement_id'].nil? ||
41
+ e['acknowledgement_id'].is_a?(String) ||
42
+ e['acknowledgement_id'].is_a?(Integer) } =>
43
+ "acknowledgement_id must be a string or an integer",
44
+
45
+ proc {|e| e['duration'].nil? ||
46
+ e['duration'].is_a?(Integer) ||
47
+ (e['duration'].is_a?(String) && !!(parsed_duration =~ /^\d+$/)) } =>
48
+ "duration must be a positive integer, or a string castable to one",
49
+ }
50
+
13
51
  # Helper method for getting the next event.
14
52
  #
15
53
  # Has a blocking and non-blocking method signature.
@@ -19,7 +57,6 @@ module Flapjack
19
57
  #
20
58
  # Calling next with :block => false, will return a nil if there are no
21
59
  # events on the queue.
22
- #
23
60
  def self.next(queue, opts = {})
24
61
  raise "Redis connection not set" unless redis = opts[:redis]
25
62
 
@@ -28,15 +65,17 @@ module Flapjack
28
65
  :events_archive_maxage => (3 * 60 * 60) }
29
66
  options = defaults.merge(opts)
30
67
 
68
+ archive_dest = nil
69
+ base_time_str = Time.now.utc.strftime("%Y%m%d%H")
70
+
31
71
  if options[:archive_events]
32
- dest = "events_archive:#{Time.now.utc.strftime "%Y%m%d%H"}"
72
+ archive_dest = "events_archive:#{base_time_str}"
33
73
  if options[:block]
34
- raw = redis.brpoplpush(queue, dest, 0)
74
+ raw = redis.brpoplpush(queue, archive_dest, 0)
35
75
  else
36
- raw = redis.rpoplpush(queue, dest)
76
+ raw = redis.rpoplpush(queue, archive_dest)
37
77
  return unless raw
38
78
  end
39
- redis.expire(dest, options[:events_archive_maxage])
40
79
  else
41
80
  if options[:block]
42
81
  raw = redis.brpop(queue, 0)[1]
@@ -45,15 +84,67 @@ module Flapjack
45
84
  return unless raw
46
85
  end
47
86
  end
48
- begin
49
- parsed = ::Oj.load( raw )
50
- rescue Oj::Error => e
51
- if options[:logger]
52
- options[:logger].warn("Error deserialising event json: #{e}, raw json: #{raw.inspect}")
87
+ parsed = parse_and_validate(raw, :logger => options[:logger])
88
+ if parsed.nil?
89
+ # either bad json or invalid data -- in either case we'll
90
+ # store the raw data in a rejected list
91
+ rejected_dest = "events_rejected:#{base_time_str}"
92
+ if options[:archive_events]
93
+ redis.multi
94
+ redis.lrem(archive_dest, 1, raw)
95
+ redis.lpush(rejected_dest, raw)
96
+ redis.exec
97
+ redis.expire(archive_dest, options[:events_archive_maxage])
98
+ else
99
+ redis.lpush(rejected_dest, raw)
100
+ end
101
+ return
102
+ elsif options[:archive_events]
103
+ redis.expire(archive_dest, options[:events_archive_maxage])
104
+ end
105
+ self.new(parsed)
106
+ end
107
+
108
+ def self.parse_and_validate(raw, opts = {})
109
+ errors = []
110
+ if parsed = ::Oj.load(raw)
111
+
112
+ if parsed.is_a?(Hash)
113
+ missing_keys = REQUIRED_KEYS.select {|k|
114
+ !parsed.has_key?(k) || parsed[k].nil? || parsed[k].empty?
115
+ }
116
+ unless missing_keys.empty?
117
+ errors << "Event hash has missing keys '#{missing_keys.join('\', \'')}'"
118
+ end
119
+
120
+ unknown_keys = parsed.keys - (REQUIRED_KEYS + OPTIONAL_KEYS)
121
+ unless unknown_keys.empty?
122
+ errors << "Event hash has unknown key(s) '#{unknown_keys.join('\', \'')}'"
123
+ end
124
+ else
125
+ errors << "Event must be a JSON hash, see https://github.com/flpjck/flapjack/wiki/DATA_STRUCTURES#event-queue"
53
126
  end
54
- return nil
127
+
128
+ if errors.empty?
129
+ errors += VALIDATIONS.keys.inject([]) {|ret,vk|
130
+ ret << "Event #{VALIDATIONS[vk]}" unless vk.call(parsed)
131
+ ret
132
+ }
133
+ end
134
+
135
+ return parsed if errors.empty?
136
+ end
137
+
138
+ if opts[:logger]
139
+ error_str = errors.nil? ? '' : errors.join(', ')
140
+ opts[:logger].error("Invalid event data received #{error_str}#{parsed.inspect}")
141
+ end
142
+ nil
143
+ rescue Oj::Error => e
144
+ if opts[:logger]
145
+ opts[:logger].error("Error deserialising event json: #{e}, raw json: #{raw.inspect}")
55
146
  end
56
- self.new( parsed )
147
+ nil
57
148
  end
58
149
 
59
150
  # creates, or modifies, an event object and adds it to the events list in redis
@@ -142,7 +142,13 @@ module Flapjack
142
142
  # for time, entity and tags
143
143
  matchers = rules.select do |rule|
144
144
  logger.debug("considering rule with entities: #{rule.entities} and tags: #{rule.tags.to_json}")
145
- (rule.match_entity?(@event_id) || rule.match_tags?(@tags) || ! rule.is_specific?) &&
145
+ rule_has_tags = rule.tags ? (rule.tags.length > 0) : false
146
+ rule_has_entities = rule.entities ? (rule.entities.length > 0) : false
147
+
148
+ matches_tags = rule_has_tags ? rule.match_tags?(@tags) : true
149
+ matches_entity = rule_has_entities ? rule.match_entity?(@event_id) : true
150
+
151
+ ((matches_entity && matches_tags) || ! rule.is_specific?) &&
146
152
  rule_occurring_now?(rule, :contact => contact, :default_timezone => default_timezone)
147
153
  end
148
154
 
@@ -233,66 +233,68 @@ module Flapjack
233
233
  tr
234
234
  end
235
235
 
236
+ VALIDATION_PROCS = {
237
+ proc {|d| !d.has_key?(:entities) ||
238
+ ( d[:entities].nil? ||
239
+ d[:entities].is_a?(Array) &&
240
+ d[:entities].all? {|e| e.is_a?(String)} ) } =>
241
+ "entities must be a list of strings",
242
+
243
+ proc {|d| !d.has_key?(:tags) ||
244
+ ( d[:tags].nil? ||
245
+ d[:tags].is_a?(Flapjack::Data::TagSet) &&
246
+ d[:tags].all? {|et| et.is_a?(String)} ) } =>
247
+ "tags must be a tag_set of strings",
248
+
249
+ proc {|d| !d.has_key?(:time_restrictions) ||
250
+ ( d[:time_restrictions].nil? ||
251
+ d[:time_restrictions].all? {|tr|
252
+ !!prepare_time_restriction(symbolize(tr))
253
+ } )
254
+ } =>
255
+ "time restrictions are invalid",
256
+
257
+ # TODO should the media types be checked against a whitelist?
258
+ proc {|d| !d.has_key?(:unknown_media) ||
259
+ ( d[:unknown_media].nil? ||
260
+ d[:unknown_media].is_a?(Array) &&
261
+ d[:unknown_media].all? {|et| et.is_a?(String)} ) } =>
262
+ "unknown_media must be a list of strings",
263
+
264
+ proc {|d| !d.has_key?(:warning_media) ||
265
+ ( d[:warning_media].nil? ||
266
+ d[:warning_media].is_a?(Array) &&
267
+ d[:warning_media].all? {|et| et.is_a?(String)} ) } =>
268
+ "warning_media must be a list of strings",
269
+
270
+ proc {|d| !d.has_key?(:critical_media) ||
271
+ ( d[:critical_media].nil? ||
272
+ d[:critical_media].is_a?(Array) &&
273
+ d[:critical_media].all? {|et| et.is_a?(String)} ) } =>
274
+ "critical_media must be a list of strings",
275
+
276
+ proc {|d| !d.has_key?(:unknown_blackhole) ||
277
+ [TrueClass, FalseClass].include?(d[:unknown_blackhole].class) } =>
278
+ "unknown_blackhole must be true or false",
279
+
280
+ proc {|d| !d.has_key?(:warning_blackhole) ||
281
+ [TrueClass, FalseClass].include?(d[:warning_blackhole].class) } =>
282
+ "warning_blackhole must be true or false",
283
+
284
+ proc {|d| !d.has_key?(:critical_blackhole) ||
285
+ [TrueClass, FalseClass].include?(d[:critical_blackhole].class) } =>
286
+ "critical_blackhole must be true or false",
287
+ }
288
+
236
289
  def self.validate_data(d, options = {})
237
290
  id_not_required = !!options[:id_not_required]
238
291
  # hash with validation => error_message
239
292
  validations = {}
240
293
  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
- })
294
+ validations.merge!(VALIDATION_PROCS)
293
295
 
294
296
  errors = validations.keys.inject([]) {|ret,vk|
295
- ret << "Rule #{validations[vk]}" unless vk.call
297
+ ret << "Rule #{validations[vk]}" unless vk.call(d)
296
298
  ret
297
299
  }
298
300
 
@@ -23,7 +23,7 @@
23
23
  <%
24
24
  row_colour = case status
25
25
  when 'critical', 'unknown'
26
- 'error'
26
+ 'danger'
27
27
  when 'ok', 'up'
28
28
  'success'
29
29
  else
@@ -33,23 +33,23 @@
33
33
  check_link = "/check?entity=" << u(entity) << "&amp;check=" << u(check)
34
34
 
35
35
  %>
36
- <tr class="<%= row_colour %>">
36
+ <tr>
37
37
  <% unless row_entity && entity == row_entity %>
38
- <td rowspan=<%= @states[entity].length %>>
38
+ <td rowspan="<%= @states[entity].length %>">
39
39
  <a href="<%= entity_link %>"><%= h entity %></a>
40
40
  </td>
41
41
  <% row_entity = entity %>
42
42
  <% end %>
43
- <td><a href="<%= check_link %>" title="check detail"><%= h check %></a></td>
44
- <td class="<%= status %>">
43
+ <td class="<%= row_colour %>"><a href="<%= check_link %>" title="check detail"><%= h check %></a></td>
44
+ <td class="<%= row_colour %>">
45
45
  <%= h status.upcase %>
46
46
  <% if in_unscheduled_outage%> (Ack'd)<% end %>
47
47
  <% if in_scheduled_outage %> (Sched)<% end %>
48
48
  </td>
49
- <td><%= h summary %></td>
50
- <td><%= h changed %></td>
51
- <td><%= h updated %></td>
52
- <td><%= h notified %></td>
49
+ <td class="<%= row_colour %>"><%= h summary %></td>
50
+ <td class="<%= row_colour %>"><%= h changed %></td>
51
+ <td class="<%= row_colour %>"><%= h updated %></td>
52
+ <td class="<%= row_colour %>"><%= h notified %></td>
53
53
  </tr>
54
54
 
55
55
  <% end %>
@@ -143,7 +143,7 @@
143
143
  <td><a href="/entity/<%= u(entity.name) %>" title="entity status"><%= h entity.name %></a></td>
144
144
  <td>
145
145
  <% checks.each do |check| %>
146
- <%= "<a href=\"/check?entity=#{u(entity.name)}&amp;check=#{u(check)}\" title=\"check status\">#{ h check }</a>" %>
146
+ <%= "<a href=\"/check?entity=#{u(entity.name)}&amp;check=#{u(check)}\" title=\"check status\">#{ h check }</a>" %>
147
147
  <% end %>
148
148
  </td>
149
149
  </tr>
@@ -38,10 +38,10 @@
38
38
  <% if in_unscheduled_outage%> (Ack'd)<% end %>
39
39
  <% if in_scheduled_outage %> (Sched)<% end %>
40
40
  </td>
41
- <td><%= summary %></td>
42
- <td><%= changed %></td>
43
- <td><%= updated %></td>
44
- <td><%= notified %></td>
41
+ <td><%= h summary %></td>
42
+ <td><%= h changed %></td>
43
+ <td><%= h updated %></td>
44
+ <td><%= h notified %></td>
45
45
  </tr>
46
46
  <% end %>
47
47
  </table>
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <title><%= include_page_title %></title>
4
+ <title><%= h include_page_title %></title>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <link rel="stylesheet" href="/css/bootstrap.min.css" media="screen">
7
7
  <link rel="stylesheet" href="/css/bootstrap-responsive.min.css" media="screen">
@@ -44,7 +44,7 @@
44
44
  <td>
45
45
  <ul>
46
46
  <% @current_checks_ages.each_pair do |age, check_count| %>
47
- <li>&gt;= <%= age %>: <%= check_count %></li>
47
+ <li>&gt;= <%= h age %>: <%= h check_count %></li>
48
48
  <% end %>
49
49
  </ul>
50
50
  </td>
@@ -25,7 +25,6 @@ module Flapjack
25
25
  @config = opts[:config]
26
26
  @redis_config = opts[:redis_config] || {}
27
27
  @logger = opts[:logger]
28
- @coordinator = opts[:coordinator]
29
28
 
30
29
  @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
31
30
 
@@ -102,11 +101,8 @@ module Flapjack
102
101
  if @exit_on_queue_empty && event.nil? && Flapjack::Data::Event.pending_count(@queue, :redis => @redis)
103
102
  # SHUT IT ALL DOWN!!!
104
103
  @logger.warn "Shutting down as exit_on_queue_empty is true, and the queue is empty"
105
- @should_quit = true
106
- @coordinator.stop
107
- # FIXME: seems the above call doesn't block until the remove_pikelets fiber exits...
108
- EM::Synchrony.sleep(1)
109
- exit
104
+ Process.kill('INT', Process.pid)
105
+ break
110
106
  end
111
107
 
112
108
  process_event(event) unless event.nil?
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.8.1"
4
+ VERSION = "0.8.2"
5
5
  end
@@ -29,19 +29,18 @@ describe Flapjack::Coordinator do
29
29
 
30
30
  fc = Flapjack::Coordinator.new(config)
31
31
  expect(Flapjack::Pikelet).to receive(:create).with('processor',
32
- :config => cfg['processor'], :redis_config => {}, :boot_time => time, :coordinator => fc).
32
+ :config => cfg['processor'], :redis_config => {}, :boot_time => time).
33
33
  and_return(processor)
34
34
 
35
- expect(fiber).to receive(:resume)
36
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
37
-
38
35
  expect(EM).to receive(:stop)
36
+ expect(EM::Synchrony).to receive(:sleep).and_return {
37
+ fc.instance_variable_set('@received_signals', ['INT'])
38
+ }
39
39
 
40
- # Syslog.should_receive(:opened?).and_return(true)
41
- # Syslog.should_receive(:close)
40
+ expect(Syslog).to receive(:opened?).and_return(true)
41
+ expect(Syslog).to receive(:close)
42
42
 
43
43
  fc.start(:signals => false)
44
- fc.stop
45
44
  end
46
45
 
47
46
  it "handles an exception raised by a pikelet and shuts down" do
@@ -63,19 +62,15 @@ describe Flapjack::Coordinator do
63
62
 
64
63
  fc = Flapjack::Coordinator.new(config)
65
64
  expect(Flapjack::Pikelet).to receive(:create).with('processor',
66
- :config => cfg['processor'], :redis_config => {}, :boot_time => time, :coordinator => fc)
65
+ :config => cfg['processor'], :redis_config => {}, :boot_time => time)
67
66
  .and_return(processor)
68
67
 
69
- expect(fiber).to receive(:resume)
70
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
71
-
72
68
  expect(EM).to receive(:stop)
73
69
 
74
- # Syslog.should_receive(:opened?).and_return(true)
75
- # Syslog.should_receive(:close)
70
+ expect(Syslog).to receive(:opened?).and_return(true)
71
+ expect(Syslog).to receive(:close)
76
72
 
77
73
  fc.start(:signals => false)
78
- fc.stop
79
74
  end
80
75
 
81
76
  it "loads an old executive pikelet config block with no new data" do
@@ -100,22 +95,21 @@ describe Flapjack::Coordinator do
100
95
 
101
96
  fc = Flapjack::Coordinator.new(config)
102
97
  expect(Flapjack::Pikelet).to receive(:create).with('processor',
103
- :config => cfg['executive'], :redis_config => {}, :boot_time => time, :coordinator => fc).
98
+ :config => cfg['executive'], :redis_config => {}, :boot_time => time).
104
99
  and_return(processor)
105
100
  expect(Flapjack::Pikelet).to receive(:create).with('notifier',
106
- :config => cfg['executive'], :redis_config => {}, :boot_time => time, :coordinator => fc).
101
+ :config => cfg['executive'], :redis_config => {}, :boot_time => time).
107
102
  and_return(notifier)
108
103
 
109
- expect(fiber).to receive(:resume)
110
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
111
-
112
104
  expect(EM).to receive(:stop)
105
+ expect(EM::Synchrony).to receive(:sleep).and_return {
106
+ fc.instance_variable_set('@received_signals', ['INT'])
107
+ }
113
108
 
114
- # Syslog.should_receive(:opened?).and_return(true)
115
- # Syslog.should_receive(:close)
109
+ expect(Syslog).to receive(:opened?).and_return(true)
110
+ expect(Syslog).to receive(:close)
116
111
 
117
112
  fc.start(:signals => false)
118
- fc.stop
119
113
  end
120
114
 
121
115
  it "loads an old executive pikelet config block with some new data" do
@@ -138,19 +132,18 @@ describe Flapjack::Coordinator do
138
132
  fc = Flapjack::Coordinator.new(config)
139
133
  expect(Flapjack::Pikelet).to receive(:create).with('processor',
140
134
  :config => cfg['executive'].merge(cfg['processor']),
141
- :redis_config => {}, :boot_time => time, :coordinator => fc).
135
+ :redis_config => {}, :boot_time => time).
142
136
  and_return(processor)
143
137
 
144
- expect(fiber).to receive(:resume)
145
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
146
-
147
138
  expect(EM).to receive(:stop)
139
+ expect(EM::Synchrony).to receive(:sleep).and_return {
140
+ fc.instance_variable_set('@received_signals', ['INT'])
141
+ }
148
142
 
149
- # Syslog.should_receive(:opened?).and_return(true)
150
- # Syslog.should_receive(:close)
143
+ expect(Syslog).to receive(:opened?).and_return(true)
144
+ expect(Syslog).to receive(:close)
151
145
 
152
146
  fc.start(:signals => false)
153
- fc.stop
154
147
  end
155
148
 
156
149
  it "traps system signals and shuts down" do
@@ -166,10 +159,9 @@ describe Flapjack::Coordinator do
166
159
  expect(config).to receive(:all).and_return({})
167
160
  expect(config).to receive(:for_redis).and_return({})
168
161
  fc = Flapjack::Coordinator.new(config)
169
- expect(fc).to receive(:stop).exactly(3).times
170
- expect(fc).to receive(:reload)
171
162
 
172
163
  fc.send(:setup_signals)
164
+ expect(fc.instance_variable_get('@received_signals')).to eq(['INT', 'TERM', 'QUIT', 'HUP'])
173
165
  end
174
166
 
175
167
  it "only traps two system signals on Windows" do
@@ -185,9 +177,9 @@ describe Flapjack::Coordinator do
185
177
  expect(config).to receive(:all).and_return({})
186
178
  expect(config).to receive(:for_redis).and_return({})
187
179
  fc = Flapjack::Coordinator.new(config)
188
- expect(fc).to receive(:stop).twice
189
180
 
190
181
  fc.send(:setup_signals)
182
+ expect(fc.instance_variable_get('@received_signals')).to eq(['INT', 'TERM'])
191
183
  end
192
184
 
193
185
  it "stops one pikelet and starts another on reload" do
@@ -218,19 +210,14 @@ describe Flapjack::Coordinator do
218
210
  jabber = double('jabber')
219
211
  expect(Flapjack::Pikelet).to receive(:create).
220
212
  with('jabber', :config => {"enabled" => true}, :redis_config => {},
221
- :boot_time => time, :coordinator => fc).
213
+ :boot_time => time).
222
214
  and_return(jabber)
223
215
  expect(jabber).to receive(:start)
224
216
 
225
- expect(fiber).to receive(:resume)
226
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
227
-
228
217
  fc.instance_variable_set('@boot_time', time)
229
218
  fc.instance_variable_set('@pikelets', [processor])
230
- fc.reload
219
+ fc.send(:reload)
231
220
  expect(fc.instance_variable_get('@pikelets')).to eq([jabber])
232
-
233
-
234
221
  end
235
222
 
236
223
  it "reloads a pikelet config without restarting it" do
@@ -259,7 +246,7 @@ describe Flapjack::Coordinator do
259
246
  fc = Flapjack::Coordinator.new(config)
260
247
  fc.instance_variable_set('@boot_time', time)
261
248
  fc.instance_variable_set('@pikelets', [processor])
262
- fc.reload
249
+ fc.send(:reload)
263
250
  expect(fc.instance_variable_get('@pikelets')).to eq([processor])
264
251
  end
265
252
 
@@ -286,9 +273,6 @@ describe Flapjack::Coordinator do
286
273
  expect(processor).to receive(:update_status)
287
274
  expect(processor).to receive(:status).exactly(3).times.and_return('stopped')
288
275
 
289
- expect(fiber).to receive(:resume)
290
- expect(Fiber).to receive(:new).and_yield.and_return(fiber)
291
-
292
276
  new_exec = double('new_executive')
293
277
  expect(new_exec).to receive(:start)
294
278
 
@@ -297,12 +281,12 @@ describe Flapjack::Coordinator do
297
281
 
298
282
  expect(Flapjack::Pikelet).to receive(:create).
299
283
  with('processor', :config => new_cfg['processor'], :redis_config => {},
300
- :boot_time => time, :coordinator => fc).
284
+ :boot_time => time).
301
285
  and_return(new_exec)
302
286
 
303
287
  fc.instance_variable_set('@boot_time', time)
304
288
  fc.instance_variable_set('@pikelets', [processor])
305
- fc.reload
289
+ fc.send(:reload)
306
290
  expect(fc.instance_variable_get('@pikelets')).to eq([new_exec])
307
291
  end
308
292
 
@@ -23,36 +23,163 @@ describe Flapjack::Data::Event do
23
23
  context 'class' do
24
24
 
25
25
  it "returns the next event (blocking, archiving)" do
26
- expect(mock_redis).to receive(:brpoplpush).with('events', /^events_archive:/, 0).and_return(event_data.to_json)
26
+ expect(mock_redis).to receive(:brpoplpush).
27
+ with('events', /^events_archive:/, 0).and_return(event_data.to_json)
27
28
  expect(mock_redis).to receive(:expire)
28
29
 
29
- result = Flapjack::Data::Event.next('events', :block => true, :archive_events => true, :redis => mock_redis)
30
+ result = Flapjack::Data::Event.next('events', :block => true,
31
+ :archive_events => true, :redis => mock_redis)
30
32
  expect(result).to be_an_instance_of(Flapjack::Data::Event)
31
33
  end
32
34
 
33
35
  it "returns the next event (blocking, not archiving)" do
34
- expect(mock_redis).to receive(:brpop).with('events', 0).and_return(['events', event_data.to_json])
36
+ expect(mock_redis).to receive(:brpop).with('events', 0).
37
+ and_return(['events', event_data.to_json])
35
38
 
36
- result = Flapjack::Data::Event.next('events', :block => true, :archive_events => false, :redis => mock_redis)
39
+ result = Flapjack::Data::Event.next('events',:block => true,
40
+ :archive_events => false, :redis => mock_redis)
37
41
  expect(result).to be_an_instance_of(Flapjack::Data::Event)
38
42
  end
39
43
 
40
44
  it "returns the next event (non-blocking, archiving)" do
41
- expect(mock_redis).to receive(:rpoplpush).with('events', /^events_archive:/).and_return(event_data.to_json)
45
+ expect(mock_redis).to receive(:rpoplpush).
46
+ with('events', /^events_archive:/).and_return(event_data.to_json)
42
47
  expect(mock_redis).to receive(:expire)
43
48
 
44
- result = Flapjack::Data::Event.next('events', :block => false, :archive_events => true, :redis => mock_redis)
49
+ result = Flapjack::Data::Event.next('events', :block => false,
50
+ :archive_events => true, :redis => mock_redis)
45
51
  expect(result).to be_an_instance_of(Flapjack::Data::Event)
46
52
  end
47
53
 
48
54
  it "returns the next event (non-blocking, not archiving)" do
49
- expect(mock_redis).to receive(:rpop).with('events').and_return(event_data.to_json)
55
+ expect(mock_redis).to receive(:rpop).with('events').
56
+ and_return(event_data.to_json)
50
57
 
51
- result = Flapjack::Data::Event.next('events', :block => false, :archive_events => false, :redis => mock_redis)
58
+ result = Flapjack::Data::Event.next('events', :block => false,
59
+ :archive_events => false, :redis => mock_redis)
52
60
  expect(result).to be_an_instance_of(Flapjack::Data::Event)
53
61
  end
54
62
 
55
- it "handles invalid event JSON"
63
+ it "rejects invalid event JSON (archiving)" do
64
+ bad_event_json = '{{{'
65
+ expect(mock_redis).to receive(:brpoplpush).
66
+ with('events', /^events_archive:/, 0).and_return(bad_event_json)
67
+ expect(mock_redis).to receive(:multi)
68
+ expect(mock_redis).to receive(:lrem).with(/^events_archive:/, 1, bad_event_json)
69
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
70
+ expect(mock_redis).to receive(:exec)
71
+ expect(mock_redis).to receive(:expire)
72
+
73
+ result = Flapjack::Data::Event.next('events', :block => true,
74
+ :archive_events => true, :redis => mock_redis)
75
+ expect(result).to be_nil
76
+ end
77
+
78
+ it "rejects invalid event JSON (not archiving)" do
79
+ bad_event_json = '{{{'
80
+ expect(mock_redis).to receive(:brpop).with('events', 0).
81
+ and_return(['events', bad_event_json])
82
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
83
+
84
+ result = Flapjack::Data::Event.next('events', :block => true,
85
+ :archive_events => false, :redis => mock_redis)
86
+ expect(result).to be_nil
87
+ end
88
+
89
+ ['type', 'state', 'entity', 'check', 'summary'].each do |required_key|
90
+
91
+ it "rejects an event with missing '#{required_key}' key (archiving)" do
92
+ bad_event_data = event_data.clone
93
+ bad_event_data.delete(required_key)
94
+ bad_event_json = bad_event_data.to_json
95
+ expect(mock_redis).to receive(:brpoplpush).
96
+ with('events', /^events_archive:/, 0).and_return(bad_event_json)
97
+ expect(mock_redis).to receive(:multi)
98
+ expect(mock_redis).to receive(:lrem).with(/^events_archive:/, 1, bad_event_json)
99
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
100
+ expect(mock_redis).to receive(:exec)
101
+ expect(mock_redis).to receive(:expire)
102
+
103
+ result = Flapjack::Data::Event.next('events', :block => true,
104
+ :archive_events => true, :redis => mock_redis)
105
+ expect(result).to be_nil
106
+ end
107
+
108
+ it "rejects an event with missing '#{required_key}' key (not archiving)" do
109
+ bad_event_data = event_data.clone
110
+ bad_event_data.delete(required_key)
111
+ bad_event_json = bad_event_data.to_json
112
+ expect(mock_redis).to receive(:brpop).with('events', 0).
113
+ and_return(['events', bad_event_json])
114
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
115
+
116
+ result = Flapjack::Data::Event.next('events', :block => true,
117
+ :archive_events => false, :redis => mock_redis)
118
+ expect(result).to be_nil
119
+ end
120
+
121
+ it "rejects an event with invalid '#{required_key}' key (archiving)" do
122
+ bad_event_data = event_data.clone
123
+ bad_event_data[required_key] = {'hello' => 'there'}
124
+ bad_event_json = bad_event_data.to_json
125
+ expect(mock_redis).to receive(:brpoplpush).
126
+ with('events', /^events_archive:/, 0).and_return(bad_event_json)
127
+ expect(mock_redis).to receive(:multi)
128
+ expect(mock_redis).to receive(:lrem).with(/^events_archive:/, 1, bad_event_json)
129
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
130
+ expect(mock_redis).to receive(:exec)
131
+ expect(mock_redis).to receive(:expire)
132
+
133
+ result = Flapjack::Data::Event.next('events', :block => true,
134
+ :archive_events => true, :redis => mock_redis)
135
+ expect(result).to be_nil
136
+ end
137
+
138
+ it "rejects an event with invalid '#{required_key}' key (not archiving)" do
139
+ bad_event_data = event_data.clone
140
+ bad_event_data[required_key] = {'hello' => 'there'}
141
+ bad_event_json = bad_event_data.to_json
142
+ expect(mock_redis).to receive(:brpop).with('events', 0).
143
+ and_return(['events', bad_event_json])
144
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
145
+
146
+ result = Flapjack::Data::Event.next('events', :block => true,
147
+ :archive_events => false, :redis => mock_redis)
148
+ expect(result).to be_nil
149
+ end
150
+ end
151
+
152
+ ['time', 'details', 'acknowledgement_id', 'duration'].each do |optional_key|
153
+ it "rejects an event with invalid '#{optional_key}' key (archiving)" do
154
+ bad_event_data = event_data.clone
155
+ bad_event_data[optional_key] = {'hello' => 'there'}
156
+ bad_event_json = bad_event_data.to_json
157
+ expect(mock_redis).to receive(:brpoplpush).
158
+ with('events', /^events_archive:/, 0).and_return(bad_event_json)
159
+ expect(mock_redis).to receive(:multi)
160
+ expect(mock_redis).to receive(:lrem).with(/^events_archive:/, 1, bad_event_json)
161
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
162
+ expect(mock_redis).to receive(:exec)
163
+ expect(mock_redis).to receive(:expire)
164
+
165
+ result = Flapjack::Data::Event.next('events', :block => true,
166
+ :archive_events => true, :redis => mock_redis)
167
+ expect(result).to be_nil
168
+ end
169
+
170
+ it "rejects an event with invalid '#{optional_key}' key (archiving)" do
171
+ bad_event_data = event_data.clone
172
+ bad_event_data[optional_key] = {'hello' => 'there'}
173
+ bad_event_json = bad_event_data.to_json
174
+ expect(mock_redis).to receive(:brpop).with('events', 0).
175
+ and_return(['events', bad_event_json])
176
+ expect(mock_redis).to receive(:lpush).with(/^events_rejected:/, bad_event_json)
177
+
178
+ result = Flapjack::Data::Event.next('events', :block => true,
179
+ :archive_events => false, :redis => mock_redis)
180
+ expect(result).to be_nil
181
+ end
182
+ end
56
183
 
57
184
  it "returns a count of pending events" do
58
185
  events_len = 23
@@ -54,10 +54,12 @@ namespace :benchmarks do
54
54
  end
55
55
  time_flapjack_start = Time.now.to_f
56
56
  puts "Starting flapjack..."
57
- if system({"FLAPJACK_ENV" => FLAPJACK_ENV,
57
+ result = system({"FLAPJACK_ENV" => FLAPJACK_ENV,
58
58
  "CPUPROFILE" => "artifacts/flapjack-perftools-cpuprofile",
59
59
  "RUBYOPT" => "-r#{perftools}"},
60
60
  "bin/flapjack start --no-daemonize --config tasks/support/flapjack_config_benchmark.yaml")
61
+ status = $?
62
+ if status.exited? && (Signal.list['INT'] + 128).eql?($?.exitstatus)
61
63
  puts "Flapjack run completed successfully"
62
64
  else
63
65
  raise "Problem starting flapjack: #{$?}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flapjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lindsay Holmwood
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-01-07 00:00:00.000000000 Z
13
+ date: 2014-01-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: dante