flapjack 0.7.20 → 0.7.21

Sign up to get free protection for your applications and to get access to all the features.
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