flapjack 0.7.20 → 0.7.21

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 (49) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.md +10 -0
  4. data/Gemfile +1 -0
  5. data/bin/flapjack +11 -0
  6. data/bin/simulate-failed-check +5 -5
  7. data/features/notification_rules.feature +77 -19
  8. data/features/steps/events_steps.rb +15 -3
  9. data/lib/flapjack/coordinator.rb +3 -3
  10. data/lib/flapjack/data/contact.rb +1 -1
  11. data/lib/flapjack/data/entity.rb +12 -1
  12. data/lib/flapjack/data/entity_check.rb +9 -2
  13. data/lib/flapjack/data/event.rb +4 -4
  14. data/lib/flapjack/data/notification.rb +27 -20
  15. data/lib/flapjack/data/notification_rule.rb +26 -24
  16. data/lib/flapjack/data/tag.rb +5 -0
  17. data/lib/flapjack/gateways/api.rb +1 -1
  18. data/lib/flapjack/gateways/api/contact_methods.rb +3 -3
  19. data/lib/flapjack/gateways/email.rb +73 -46
  20. data/lib/flapjack/gateways/email/alert.html.erb +13 -4
  21. data/lib/flapjack/gateways/email/alert.text.erb +2 -2
  22. data/lib/flapjack/gateways/jabber.rb +22 -16
  23. data/lib/flapjack/gateways/pagerduty.rb +7 -3
  24. data/lib/flapjack/gateways/web.rb +1 -1
  25. data/lib/flapjack/gateways/web/views/check.html.erb +2 -2
  26. data/lib/flapjack/gateways/web/views/contact.html.erb +3 -3
  27. data/lib/flapjack/logger.rb +67 -35
  28. data/lib/flapjack/notifier.rb +9 -3
  29. data/lib/flapjack/pikelet.rb +3 -1
  30. data/lib/flapjack/processor.rb +34 -10
  31. data/lib/flapjack/version.rb +1 -1
  32. data/spec/lib/flapjack/coordinator_spec.rb +17 -13
  33. data/spec/lib/flapjack/data/contact_spec.rb +4 -3
  34. data/spec/lib/flapjack/data/entity_check_spec.rb +10 -0
  35. data/spec/lib/flapjack/data/entity_spec.rb +60 -5
  36. data/spec/lib/flapjack/data/event_spec.rb +4 -4
  37. data/spec/lib/flapjack/data/notification_rule_spec.rb +9 -2
  38. data/spec/lib/flapjack/data/tag_spec.rb +0 -1
  39. data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +1 -1
  40. data/spec/lib/flapjack/gateways/email_spec.rb +2 -1
  41. data/spec/lib/flapjack/gateways/jabber_spec.rb +5 -3
  42. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +3 -1
  43. data/spec/lib/flapjack/logger_spec.rb +5 -5
  44. data/spec/lib/flapjack/pikelet_spec.rb +4 -2
  45. data/spec/lib/flapjack/processor_spec.rb +16 -7
  46. data/tasks/benchmarks.rake +228 -0
  47. data/tasks/events.rake +11 -10
  48. data/tasks/support/flapjack_config_benchmark.yaml +58 -0
  49. metadata +6 -4
@@ -39,7 +39,7 @@ module Flapjack
39
39
 
40
40
  class << self
41
41
  def start
42
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 1)
42
+ @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
43
43
 
44
44
  @logger.info "starting web - class"
45
45
 
@@ -180,7 +180,7 @@
180
180
  </tr>
181
181
  <% @contacts.sort_by {|c| [c.first_name, c.last_name] }.each do |contact| %>
182
182
  <tr>
183
- <td><a href="/contacts/<% contact.id %>" title="contact details"><%= h contact.name %></a></td>
183
+ <td><a href="/contacts/<%= contact.id %>" title="contact details"><%= h contact.name %></a></td>
184
184
  <td>
185
185
  <% if contact.media && !contact.media.empty? %>
186
186
  <p><%= h contact.media.keys.collect(&:capitalize).join(", ") %></p>
@@ -201,4 +201,4 @@
201
201
  <%= foot %>
202
202
  </div>
203
203
  </body>
204
- </html>
204
+ </html>
@@ -86,7 +86,7 @@
86
86
  <tr>
87
87
  <th>ID</th>
88
88
  <th>Entities</th>
89
- <th>Entity Tags</th>
89
+ <th>Tags</th>
90
90
  <th>Warning Media</th>
91
91
  <th>Critical Media</th>
92
92
  <th>Time Restrictions</th>
@@ -95,7 +95,7 @@
95
95
  <tr>
96
96
  <td><%= h rule.id %></td>
97
97
  <td><%= h( (rule.entities && !rule.entities.empty?) ? rule.entities.join(', ') : '-') %></td>
98
- <td><%= h( (rule.entity_tags && !rule.entity_tags.empty?) ? rule.entity_tags.join(', ') : '-') %></td>
98
+ <td><%= h( (rule.tags && !rule.tags.empty?) ? rule.tags.to_a.join(', ') : '-') %></td>
99
99
  <td><%= h( (rule.warning_media && !rule.warning_media.empty?) ? rule.warning_media.join(', ') : '-')%></td>
100
100
  <td><%= h( (rule.critical_media && !rule.critical_media.empty?) ? rule.critical_media.join(', ') : '-') %></td>
101
101
  <td><%= h(rule.time_restrictions) %></td>
@@ -111,4 +111,4 @@
111
111
  <%= foot %>
112
112
  </div>
113
113
  </body>
114
- </html>
114
+ </html>
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'logger'
4
+ require 'syslog'
4
5
 
5
6
  begin
6
7
  # Ruby 2.0+
7
8
  require 'syslog/logger'
8
9
  rescue LoadError
9
- # Ruby 1.9
10
- require 'syslog'
11
10
  end
12
11
 
13
12
  module Flapjack
@@ -16,14 +15,14 @@ module Flapjack
16
15
 
17
16
  LEVELS = [:debug, :info, :warn, :error, :fatal]
18
17
 
19
- # only used for 1.9
20
- SYSLOG_LEVELS_MAP = {
21
- :debug => Syslog::Constants::LOG_DEBUG,
22
- :info => Syslog::Constants::LOG_INFO,
23
- :warn => Syslog::Constants::LOG_WARNING,
24
- :error => Syslog::Constants::LOG_ERR,
25
- :fatal => Syslog::Constants::LOG_CRIT
26
- }
18
+ SEVERITY_LABELS = %w(DEBUG INFO WARN ERROR FATAL)
19
+
20
+ SYSLOG_LEVELS = [::Syslog::Constants::LOG_DEBUG,
21
+ ::Syslog::Constants::LOG_INFO,
22
+ ::Syslog::Constants::LOG_WARNING,
23
+ ::Syslog::Constants::LOG_ERR,
24
+ ::Syslog::Constants::LOG_CRIT
25
+ ]
27
26
 
28
27
  def initialize(name, config = {})
29
28
  config ||= {}
@@ -31,7 +30,14 @@ module Flapjack
31
30
  @name = name
32
31
 
33
32
  @formatter = proc do |severity, datetime, progname, msg|
34
- "#{datetime.iso8601} [#{severity}] :: #{name} :: #{msg}\n"
33
+ t = datetime.iso8601
34
+ "#{t} [#{severity}] :: #{name} :: #{msg}\n"
35
+ end
36
+
37
+ @syslog_formatter = proc do |severity, datetime, progname, msg|
38
+ t = datetime.iso8601
39
+ l = SEVERITY_LABELS[severity]
40
+ "#{t} [#{l}] :: #{name} :: #{msg}\n"
35
41
  end
36
42
 
37
43
  @logger = ::Logger.new(STDOUT)
@@ -40,19 +46,15 @@ module Flapjack
40
46
  if Syslog.const_defined?('Logger', false)
41
47
  # Ruby 2.0+
42
48
  @sys_logger = Syslog.const_get('Logger', false).new('flapjack')
43
- @sys_logger.formatter = @formatter
44
- else
45
- # Ruby 1.9
46
- @syslog = Syslog.opened? ? Syslog :
47
- Syslog.open('flapjack',
48
- (Syslog::Constants::LOG_PID | Syslog::Constants::LOG_CONS),
49
- Syslog::Constants::LOG_USER)
49
+ @sys_logger.formatter = @syslog_formatter
50
50
  end
51
51
 
52
52
  configure(config)
53
53
  end
54
54
 
55
55
  def configure(config)
56
+ raise "Cannot configure closed logger" if @logger.nil?
57
+
56
58
  level = config['level']
57
59
 
58
60
  # we'll let Logger spit the dummy on invalid level values -- but will
@@ -63,7 +65,7 @@ module Flapjack
63
65
 
64
66
  err = nil
65
67
 
66
- new_level = begin
68
+ @level = begin
67
69
  ::Logger.const_get(level.upcase)
68
70
  rescue NameError
69
71
  err = "Unknown Logger severity level '#{level.upcase}', using INFO..."
@@ -72,32 +74,62 @@ module Flapjack
72
74
 
73
75
  @logger.error(err) if err
74
76
 
75
- @logger.level = new_level
77
+ @logger.level = @level
76
78
  if @sys_logger
77
- @sys_logger.level = new_level
78
- elsif @syslog
79
- Syslog.mask = Syslog::LOG_UPTO(SYSLOG_LEVELS_MAP[level.downcase.to_sym])
79
+ @sys_logger.level = @level
80
80
  end
81
+ end
81
82
 
83
+ def close
84
+ raise "Already closed" if @logger.nil?
85
+ @logger.close
86
+ @logger = nil
87
+ if @sys_logger
88
+ @sys_logger.close
89
+ @sys_logger = nil
90
+ end
82
91
  end
83
92
 
84
- LEVELS.each do |level|
85
- define_method(level) {|*args, &block|
86
- @logger.send(level.to_sym, *args, &block)
87
- if @sys_logger
88
- @sys_logger.send(level.to_sym, *args, &block)
89
- elsif @syslog
90
- t = Time.now.iso8601
91
- l = level.to_s.upcase
92
- @syslog.log(SYSLOG_LEVELS_MAP[level],
93
- "#{t} [#{l}] :: #{@name} :: %s",
94
- (block ? block.call : args.first))
93
+ def add(severity, message = nil, progname = nil, &block)
94
+ raise "Cannot log with a closed logger" if @logger.nil?
95
+ @logger.add(severity, message, progname, &block)
96
+ return if severity < @level
97
+
98
+ progname ||= 'flapjack'
99
+ if message.nil?
100
+ if block_given?
101
+ message = yield
102
+ else
103
+ message = progname
104
+ progname = 'flapjack'
95
105
  end
106
+ end
107
+
108
+ if @sys_logger
109
+ @sys_logger.add(severity, message, progname, &block)
110
+ else
111
+ level = SYSLOG_LEVELS[severity]
112
+ t = Time.now.iso8601
113
+ l = SEVERITY_LABELS[severity]
114
+ begin
115
+ Syslog.open('flapjack', (Syslog::Constants::LOG_PID | Syslog::Constants::LOG_CONS),
116
+ Syslog::Constants::LOG_USER)
117
+ Syslog.mask = Syslog::LOG_UPTO(level)
118
+ Syslog.log(level, "#{t} [#{l}] :: #{@name} :: %s", message)
119
+ ensure
120
+ Syslog.close
121
+ end
122
+ end
123
+ end
124
+
125
+ LEVELS.each do |level|
126
+ define_method(level) {|progname, &block|
127
+ add(::Logger.const_get(level.upcase), nil, progname, &block)
96
128
  }
97
129
  end
98
130
 
99
131
  def respond_to?(sym)
100
- (LEVELS + [:configure]).include?(sym)
132
+ (LEVELS + [:configure, :close, :add]).include?(sym)
101
133
  end
102
134
 
103
135
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'active_support/time'
4
4
 
5
+ require 'em-hiredis'
6
+
5
7
  require 'oj'
6
8
 
7
9
  require 'flapjack/data/contact'
@@ -22,9 +24,9 @@ module Flapjack
22
24
 
23
25
  def initialize(opts = {})
24
26
  @config = opts[:config]
25
- @redis_config = opts[:redis_config]
27
+ @redis_config = opts[:redis_config] || {}
26
28
  @logger = opts[:logger]
27
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2) # first will block
29
+ @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
28
30
 
29
31
  @notifications_queue = @config['queue'] || 'notifications'
30
32
 
@@ -73,7 +75,11 @@ module Flapjack
73
75
  # from a different fiber while the main one is blocking.
74
76
  def stop
75
77
  @should_quit = true
76
- @redis.rpush(@notifications_queue, Oj.dump('type' => 'shutdown'))
78
+
79
+ redis_uri = @redis_config[:path] ||
80
+ "redis://#{@redis_config[:host] || '127.0.0.1'}:#{@redis_config[:port] || '6379'}/#{@redis_config[:db] || '0'}"
81
+ shutdown_redis = EM::Hiredis.connect(redis_uri)
82
+ shutdown_redis.rpush(@notifications_queue, Oj.dump('type' => 'shutdown'))
77
83
  end
78
84
 
79
85
  private
@@ -73,6 +73,7 @@ module Flapjack
73
73
  @config = opts[:config] || {}
74
74
  @redis_config = opts[:redis_config] || {}
75
75
  @boot_time = opts[:boot_time]
76
+ @coordinator = opts[:coordinator]
76
77
 
77
78
  @logger = Flapjack::Logger.new("flapjack-#{type}", @config['logger'])
78
79
 
@@ -104,7 +105,8 @@ module Flapjack
104
105
  def self.create(type, opts = {})
105
106
  self.new(type, PIKELET_TYPES[type], :config => opts[:config],
106
107
  :redis_config => opts[:redis_config],
107
- :boot_time => opts[:boot_time])
108
+ :boot_time => opts[:boot_time],
109
+ :coordinator => opts[:coordinator])
108
110
  end
109
111
 
110
112
  def initialize(type, pikelet_klass, opts = {})
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'chronic_duration'
4
4
 
5
+ require 'em-hiredis'
6
+
5
7
  require 'flapjack/filters/acknowledgement'
6
8
  require 'flapjack/filters/ok'
7
9
  require 'flapjack/filters/scheduled_maintenance'
@@ -22,9 +24,11 @@ module Flapjack
22
24
 
23
25
  def initialize(opts = {})
24
26
  @config = opts[:config]
25
- @redis_config = opts[:redis_config]
27
+ @redis_config = opts[:redis_config] || {}
26
28
  @logger = opts[:logger]
27
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2) # first will block
29
+ @coordinator = opts[:coordinator]
30
+
31
+ @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
28
32
 
29
33
  @queue = @config['queue'] || 'events'
30
34
 
@@ -36,6 +40,8 @@ module Flapjack
36
40
  ncsm_duration_conf = @config['new_check_scheduled_maintenance_duration'] || '100 years'
37
41
  @ncsm_duration = ChronicDuration.parse(ncsm_duration_conf)
38
42
 
43
+ @exit_on_queue_empty = !! @config['exit_on_queue_empty']
44
+
39
45
  options = { :logger => opts[:logger], :redis => @redis }
40
46
  @filters = []
41
47
  @filters << Flapjack::Filters::Ok.new(options)
@@ -94,7 +100,18 @@ module Flapjack
94
100
  :redis => @redis,
95
101
  :archive_events => @archive_events,
96
102
  :events_archive_maxage => @events_archive_maxage,
97
- :logger => @logger)
103
+ :logger => @logger,
104
+ :block => ! @exit_on_queue_empty )
105
+ if @exit_on_queue_empty && event.nil? && Flapjack::Data::Event.pending_count(@queue, :redis => @redis)
106
+ # SHUT IT ALL DOWN!!!
107
+ @logger.warn "Shutting down as exit_on_queue_empty is true, and the queue is empty"
108
+ @should_quit = true
109
+ @coordinator.stop
110
+ # FIXME: seems the above call doesn't block until the remove_pikelets fiber exits...
111
+ EM::Synchrony.sleep(1)
112
+ exit
113
+ end
114
+
98
115
  process_event(event) unless event.nil?
99
116
  end
100
117
 
@@ -104,20 +121,25 @@ module Flapjack
104
121
  # this must use a separate connection to the main Executive one, as it's running
105
122
  # from a different fiber while the main one is blocking.
106
123
  def stop
107
- @should_quit = true
108
- @redis.rpush('events', Oj.dump('type' => 'shutdown',
109
- 'host' => '',
110
- 'service' => '',
111
- 'state' => ''))
124
+ unless @should_quit
125
+ @should_quit = true
126
+ redis_uri = @redis_config[:path] ||
127
+ "redis://#{@redis_config[:host] || '127.0.0.1'}:#{@redis_config[:port] || '6379'}/#{@redis_config[:db] || '0'}"
128
+ shutdown_redis = EM::Hiredis.connect(redis_uri)
129
+ shutdown_redis.rpush('events', Oj.dump('type' => 'noop'))
130
+ end
112
131
  end
113
132
 
114
133
  private
115
134
 
116
135
  def process_event(event)
117
- pending = Flapjack::Data::Event.pending_count(:redis => @redis)
136
+ pending = Flapjack::Data::Event.pending_count(@queue, :redis => @redis)
118
137
  @logger.debug("#{pending} events waiting on the queue")
119
138
  @logger.debug("Raw event received: #{event.inspect}")
120
- return if ('shutdown' == event.type)
139
+
140
+ if ('noop' == event.type)
141
+ return
142
+ end
121
143
 
122
144
  event_str = "#{event.id}, #{event.type}, #{event.state}, #{event.summary}"
123
145
  event_str << ", #{Time.at(event.time).to_s}" if event.time
@@ -126,6 +148,8 @@ module Flapjack
126
148
  entity_check = Flapjack::Data::EntityCheck.for_event_id(event.id, :redis => @redis)
127
149
  timestamp = Time.now.to_i
128
150
 
151
+ event.tags = (event.tags || Flapjack::Data::TagSet.new) + entity_check.tags
152
+
129
153
  should_notify = update_keys(event, entity_check, timestamp)
130
154
 
131
155
  if !should_notify
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.7.20"
4
+ VERSION = "0.7.21"
5
5
  end
@@ -7,9 +7,9 @@ describe Flapjack::Coordinator do
7
7
  let(:fiber) { mock(Fiber) }
8
8
  let(:config) { mock(Flapjack::Configuration) }
9
9
 
10
- let(:logger) { mock(Flapjack::Logger) }
10
+ let(:logger) { mock(Flapjack::Logger) }
11
11
 
12
- let!(:time) { Time.now }
12
+ let!(:time) { Time.now }
13
13
 
14
14
  it "starts and stops a pikelet" do
15
15
  Flapjack::Logger.should_receive(:new).and_return(logger)
@@ -29,7 +29,7 @@ describe Flapjack::Coordinator do
29
29
 
30
30
  fc = Flapjack::Coordinator.new(config)
31
31
  Flapjack::Pikelet.should_receive(:create).with('processor',
32
- :config => cfg['processor'], :redis_config => {}, :boot_time => time).
32
+ :config => cfg['processor'], :redis_config => {}, :boot_time => time, :coordinator => fc).
33
33
  and_return(processor)
34
34
 
35
35
  fiber.should_receive(:resume)
@@ -63,7 +63,7 @@ describe Flapjack::Coordinator do
63
63
 
64
64
  fc = Flapjack::Coordinator.new(config)
65
65
  Flapjack::Pikelet.should_receive(:create).with('processor',
66
- :config => cfg['processor'], :redis_config => {}, :boot_time => time)
66
+ :config => cfg['processor'], :redis_config => {}, :boot_time => time, :coordinator => fc)
67
67
  .and_return(processor)
68
68
 
69
69
  fiber.should_receive(:resume)
@@ -100,10 +100,10 @@ describe Flapjack::Coordinator do
100
100
 
101
101
  fc = Flapjack::Coordinator.new(config)
102
102
  Flapjack::Pikelet.should_receive(:create).with('processor',
103
- :config => cfg['executive'], :redis_config => {}, :boot_time => time).
103
+ :config => cfg['executive'], :redis_config => {}, :boot_time => time, :coordinator => fc).
104
104
  and_return(processor)
105
105
  Flapjack::Pikelet.should_receive(:create).with('notifier',
106
- :config => cfg['executive'], :redis_config => {}, :boot_time => time).
106
+ :config => cfg['executive'], :redis_config => {}, :boot_time => time, :coordinator => fc).
107
107
  and_return(notifier)
108
108
 
109
109
  fiber.should_receive(:resume)
@@ -138,7 +138,7 @@ describe Flapjack::Coordinator do
138
138
  fc = Flapjack::Coordinator.new(config)
139
139
  Flapjack::Pikelet.should_receive(:create).with('processor',
140
140
  :config => cfg['executive'].merge(cfg['processor']),
141
- :redis_config => {}, :boot_time => time).
141
+ :redis_config => {}, :boot_time => time, :coordinator => fc).
142
142
  and_return(processor)
143
143
 
144
144
  fiber.should_receive(:resume)
@@ -210,22 +210,25 @@ describe Flapjack::Coordinator do
210
210
  processor.should_receive(:update_status)
211
211
  processor.should_receive(:status).exactly(3).times.and_return('stopped')
212
212
 
213
+ config.should_receive(:for_redis).and_return({})
214
+ fc = Flapjack::Coordinator.new(config)
215
+
213
216
  jabber = mock('jabber')
214
217
  Flapjack::Pikelet.should_receive(:create).
215
218
  with('jabber', :config => {"enabled" => true}, :redis_config => {},
216
- :boot_time => time).
219
+ :boot_time => time, :coordinator => fc).
217
220
  and_return(jabber)
218
221
  jabber.should_receive(:start)
219
222
 
220
223
  fiber.should_receive(:resume)
221
224
  Fiber.should_receive(:new).and_yield.and_return(fiber)
222
225
 
223
- config.should_receive(:for_redis).and_return({})
224
- fc = Flapjack::Coordinator.new(config)
225
226
  fc.instance_variable_set('@boot_time', time)
226
227
  fc.instance_variable_set('@pikelets', [processor])
227
228
  fc.reload
228
229
  fc.instance_variable_get('@pikelets').should == [jabber]
230
+
231
+
229
232
  end
230
233
 
231
234
  it "reloads a pikelet config without restarting it" do
@@ -287,13 +290,14 @@ describe Flapjack::Coordinator do
287
290
  new_exec = mock('new_executive')
288
291
  new_exec.should_receive(:start)
289
292
 
293
+ config.should_receive(:for_redis).and_return({})
294
+ fc = Flapjack::Coordinator.new(config)
295
+
290
296
  Flapjack::Pikelet.should_receive(:create).
291
297
  with('processor', :config => new_cfg['processor'], :redis_config => {},
292
- :boot_time => time).
298
+ :boot_time => time, :coordinator => fc).
293
299
  and_return(new_exec)
294
300
 
295
- config.should_receive(:for_redis).and_return({})
296
- fc = Flapjack::Coordinator.new(config)
297
301
  fc.instance_variable_set('@boot_time', time)
298
302
  fc.instance_variable_set('@pikelets', [processor])
299
303
  fc.reload