flapjack 0.8.1 → 0.8.2

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