flapjack 1.2.1 → 1.2.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +11 -12
  4. data/CHANGELOG.md +10 -0
  5. data/Gemfile +0 -7
  6. data/Rakefile +0 -1
  7. data/bin/flapjack +2 -0
  8. data/etc/flapjack_config.yaml.example +20 -0
  9. data/features/ack_after_sched_maint.feature +1 -1
  10. data/features/cli.feature +1 -1
  11. data/features/notification_rules.feature +1 -1
  12. data/features/notifications.feature +0 -9
  13. data/features/rollup.feature +1 -1
  14. data/features/steps/events_steps.rb +20 -8
  15. data/features/steps/notifications_steps.rb +62 -75
  16. data/features/support/env.rb +17 -8
  17. data/flapjack.gemspec +4 -4
  18. data/lib/flapjack.rb +3 -0
  19. data/lib/flapjack/cli/import.rb +1 -0
  20. data/lib/flapjack/cli/maintenance.rb +1 -0
  21. data/lib/flapjack/cli/purge.rb +2 -0
  22. data/lib/flapjack/cli/receiver.rb +1 -0
  23. data/lib/flapjack/cli/simulate.rb +1 -0
  24. data/lib/flapjack/data/alert.rb +28 -1
  25. data/lib/flapjack/data/contact.rb +1 -1
  26. data/lib/flapjack/data/entity.rb +18 -8
  27. data/lib/flapjack/data/entity_check.rb +17 -0
  28. data/lib/flapjack/data/event.rb +33 -15
  29. data/lib/flapjack/data/migration.rb +46 -23
  30. data/lib/flapjack/filters/delays.rb +13 -6
  31. data/lib/flapjack/gateways/aws_sns.rb +115 -88
  32. data/lib/flapjack/gateways/aws_sns/alert.text.erb +2 -1
  33. data/lib/flapjack/gateways/email.rb +145 -135
  34. data/lib/flapjack/gateways/email/alert.html.erb +6 -4
  35. data/lib/flapjack/gateways/email/alert.text.erb +2 -0
  36. data/lib/flapjack/gateways/jabber.rb +61 -1
  37. data/lib/flapjack/gateways/jabber/alert.text.erb +1 -1
  38. data/lib/flapjack/gateways/pagerduty/alert.text.erb +1 -1
  39. data/lib/flapjack/gateways/sms_gammu.rb +119 -0
  40. data/lib/flapjack/gateways/sms_messagenet.rb +95 -67
  41. data/lib/flapjack/gateways/sms_messagenet/alert.text.erb +2 -1
  42. data/lib/flapjack/gateways/sms_twilio.rb +102 -74
  43. data/lib/flapjack/gateways/sms_twilio/alert.text.erb +2 -1
  44. data/lib/flapjack/logger.rb +1 -1
  45. data/lib/flapjack/notifier.rb +5 -14
  46. data/lib/flapjack/patches.rb +0 -58
  47. data/lib/flapjack/pikelet.rb +8 -78
  48. data/lib/flapjack/processor.rb +3 -1
  49. data/lib/flapjack/redis_pool.rb +2 -0
  50. data/lib/flapjack/version.rb +1 -1
  51. data/spec/lib/flapjack/data/contact_spec.rb +2 -2
  52. data/spec/lib/flapjack/data/entity_spec.rb +15 -0
  53. data/spec/lib/flapjack/data/event_spec.rb +2 -2
  54. data/spec/lib/flapjack/data/migration_spec.rb +11 -0
  55. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +12 -8
  56. data/spec/lib/flapjack/gateways/email_spec.rb +56 -51
  57. data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +17 -12
  58. data/spec/lib/flapjack/gateways/sms_twilio_spec.rb +17 -12
  59. data/spec/lib/flapjack/pikelet_spec.rb +9 -23
  60. data/spec/lib/flapjack/redis_pool_spec.rb +1 -0
  61. data/tasks/profile.rake +25 -109
  62. metadata +37 -39
  63. data/Gemfile-ruby1.9 +0 -30
  64. data/Gemfile-ruby1.9.lock +0 -250
  65. data/tasks/benchmarks.rake +0 -237
@@ -4,6 +4,8 @@ require 'em-synchrony'
4
4
  require 'em-synchrony/em-http'
5
5
  require 'active_support/inflector'
6
6
 
7
+ require 'flapjack/redis_pool'
8
+
7
9
  require 'flapjack/data/alert'
8
10
  require 'flapjack/utility'
9
11
 
@@ -17,98 +19,124 @@ module Flapjack
17
19
  # --data-urlencode 'Body=Sausage' \
18
20
  # -u [AccountSid]:[AuthToken]
19
21
 
20
- class << self
22
+ include Flapjack::Utility
21
23
 
22
- include Flapjack::Utility
24
+ def initialize(opts = {})
25
+ @config = opts[:config]
26
+ @logger = opts[:logger]
27
+ @redis_config = opts[:redis_config] || {}
28
+ @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 1, :logger => @logger)
23
29
 
24
- def start
25
- @sent = 0
26
- end
30
+ @logger.info("starting")
31
+ @logger.debug("new sms_twilio gateway pikelet with the following options: #{@config.inspect}")
27
32
 
28
- def perform(contents)
29
- @logger.debug "Woo, got a notification to send out: #{contents.inspect}"
30
- alert = Flapjack::Data::Alert.new(contents, :logger => @logger)
31
-
32
- account_sid = @config["account_sid"]
33
- auth_token = @config["auth_token"]
34
- from = @config["from"]
35
- endpoint = @config["endpoint"] || "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
36
-
37
- address = alert.address
38
- notification_id = alert.notification_id
39
- message_type = alert.rollup ? 'rollup' : 'alert'
40
-
41
- my_dir = File.dirname(__FILE__)
42
- sms_template_path = case
43
- when @config.has_key?('templates') && @config['templates']["#{message_type}.text"]
44
- @config['templates']["#{message_type}.text"]
45
- else
46
- my_dir + "/sms_twilio/#{message_type}.text.erb"
47
- end
48
- sms_template = ERB.new(File.read(sms_template_path), nil, '-')
33
+ @sent = 0
34
+ end
35
+
36
+ def stop
37
+ @logger.info("stopping")
38
+ @should_quit = true
39
+
40
+ redis_uri = @redis_config[:path] ||
41
+ "redis://#{@redis_config[:host] || '127.0.0.1'}:#{@redis_config[:port] || '6379'}/#{@redis_config[:db] || '0'}"
42
+ shutdown_redis = EM::Hiredis.connect(redis_uri)
43
+ shutdown_redis.rpush(@config['queue'], Flapjack.dump_json('notification_type' => 'shutdown'))
44
+ end
49
45
 
50
- @alert = alert
51
- bnd = binding
46
+ def start
47
+ queue = @config['queue']
52
48
 
49
+ until @should_quit
53
50
  begin
54
- message = sms_template.result(bnd).chomp
51
+ @logger.debug("sms_twilio gateway is going into blpop mode on #{queue}")
52
+ deliver( Flapjack::Data::Alert.next(queue, :redis => @redis, :logger => @logger) )
55
53
  rescue => e
56
- @logger.error "Error while excuting the ERB for an sms: " +
57
- "ERB being executed: #{sms_template_path}"
58
- raise
54
+ @logger.error "Error generating or dispatching SMS Twilio message: #{e.class}: #{e.message}\n" +
55
+ e.backtrace.join("\n")
59
56
  end
57
+ end
58
+ end
60
59
 
61
- if @config.nil? || (@config.respond_to?(:empty?) && @config.empty?)
62
- @logger.error "sms_twilio config is missing"
63
- return
64
- end
60
+ def deliver(alert)
61
+ account_sid = @config["account_sid"]
62
+ auth_token = @config["auth_token"]
63
+ from = @config["from"]
64
+ endpoint = @config["endpoint"] || "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
65
+
66
+ address = alert.address
67
+ notification_id = alert.notification_id
68
+ message_type = alert.rollup ? 'rollup' : 'alert'
69
+
70
+ my_dir = File.dirname(__FILE__)
71
+ sms_template_path = case
72
+ when @config.has_key?('templates') && @config['templates']["#{message_type}.text"]
73
+ @config['templates']["#{message_type}.text"]
74
+ else
75
+ my_dir + "/sms_twilio/#{message_type}.text.erb"
76
+ end
77
+ sms_template = ERB.new(File.read(sms_template_path), nil, '-')
65
78
 
66
- errors = []
79
+ @alert = alert
80
+ bnd = binding
67
81
 
68
- safe_message = truncate(message, 159)
82
+ begin
83
+ message = sms_template.result(bnd).chomp
84
+ rescue => e
85
+ @logger.error "Error while excuting the ERB for an sms: " +
86
+ "ERB being executed: #{sms_template_path}"
87
+ raise
88
+ end
69
89
 
70
- [[account_sid, "Twilio account_sid is missing"],
71
- [auth_token, "Twilio auth_token is missing"],
72
- [from, "SMS from address is missing"],
73
- [address, "SMS address is missing"],
74
- [notification_id, "Notification id is missing"]].each do |val_err|
90
+ if @config.nil? || (@config.respond_to?(:empty?) && @config.empty?)
91
+ @logger.error "sms_twilio config is missing"
92
+ return
93
+ end
75
94
 
76
- next unless val_err.first.nil? || (val_err.first.respond_to?(:empty?) && val_err.first.empty?)
77
- errors << val_err.last
78
- end
95
+ errors = []
79
96
 
80
- unless errors.empty?
81
- errors.each {|err| @logger.error err }
82
- return
83
- end
97
+ safe_message = truncate(message, 159)
84
98
 
85
- body_data = {'To' => address,
86
- 'From' => from,
87
- 'Body' => safe_message}
88
- @logger.debug "body_data: #{body_data.inspect}"
89
- @logger.debug "authorization: [#{account_sid}, #{auth_token[0..2]}...#{auth_token[-3..-1]}]"
90
-
91
- http = EM::HttpRequest.new(endpoint).post(:body => body_data, :head => {'authorization' => [account_sid, auth_token]})
92
-
93
- @logger.debug "server response: #{http.response}"
94
-
95
- status = (http.nil? || http.response_header.nil?) ? nil : http.response_header.status
96
- if (status >= 200) && (status <= 206)
97
- @sent += 1
98
- alert.record_send_success!
99
- @logger.debug "Sent SMS via Twilio, response status is #{status}, " +
100
- "notification_id: #{notification_id}"
101
- else
102
- @logger.error "Failed to send SMS via Twilio, response status is #{status}, " +
103
- "notification_id: #{notification_id}"
104
- end
105
- rescue => e
106
- @logger.error "Error generating or delivering sms to #{contents['address']}: #{e.class}: #{e.message}"
107
- @logger.error e.backtrace.join("\n")
108
- raise
99
+ [[account_sid, "Twilio account_sid is missing"],
100
+ [auth_token, "Twilio auth_token is missing"],
101
+ [from, "SMS from address is missing"],
102
+ [address, "SMS address is missing"],
103
+ [notification_id, "Notification id is missing"]].each do |val_err|
104
+
105
+ next unless val_err.first.nil? || (val_err.first.respond_to?(:empty?) && val_err.first.empty?)
106
+ errors << val_err.last
107
+ end
108
+
109
+ unless errors.empty?
110
+ errors.each {|err| @logger.error err }
111
+ return
109
112
  end
110
113
 
114
+ body_data = {'To' => address,
115
+ 'From' => from,
116
+ 'Body' => safe_message}
117
+ @logger.debug "body_data: #{body_data.inspect}"
118
+ @logger.debug "authorization: [#{account_sid}, #{auth_token[0..2]}...#{auth_token[-3..-1]}]"
119
+
120
+ http = EM::HttpRequest.new(endpoint).post(:body => body_data, :head => {'authorization' => [account_sid, auth_token]})
121
+
122
+ @logger.debug "server response: #{http.response}"
123
+
124
+ status = (http.nil? || http.response_header.nil?) ? nil : http.response_header.status
125
+ if (status >= 200) && (status <= 206)
126
+ @sent += 1
127
+ alert.record_send_success!
128
+ @logger.debug "Sent SMS via Twilio, response status is #{status}, " +
129
+ "notification_id: #{notification_id}"
130
+ else
131
+ @logger.error "Failed to send SMS via Twilio, response status is #{status}, " +
132
+ "notification_id: #{notification_id}"
133
+ end
134
+ rescue => e
135
+ @logger.error "Error generating or delivering sms to #{alert.address}: #{e.class}: #{e.message}"
136
+ @logger.error e.backtrace.join("\n")
137
+ raise
111
138
  end
139
+
112
140
  end
113
141
  end
114
142
  end
@@ -1,5 +1,6 @@
1
+ <% summary = @alert.summary -%>
1
2
  <%= @alert.type_sentence_case %>: '<%= @alert.check %>' on <%= @alert.entity -%>
2
3
  <% unless ['acknowledgement', 'test'].include?(@alert.notification_type) -%>
3
4
  is <%= @alert.state_title_case -%>
4
5
  <% end -%>
5
- at <%= Time.at(@alert.time).strftime('%-d %b %H:%M') %>, <%= @alert.summary -%>
6
+ at <%= Time.at(@alert.time).strftime('%-d %b %H:%M') %><%= (summary.nil? || summary.empty?) ? '' : ", #{summary}" -%>
@@ -25,7 +25,7 @@ module Flapjack
25
25
  @name = name
26
26
 
27
27
  @formatter = proc do |severity, datetime, progname, msg|
28
- t = datetime.iso8601
28
+ t = datetime.iso8601(6)
29
29
  "#{t} [#{severity}] :: #{name} :: #{msg}\n"
30
30
  end
31
31
 
@@ -4,6 +4,8 @@ require 'active_support/time'
4
4
 
5
5
  require 'em-hiredis'
6
6
 
7
+ require 'flapjack/data/alert'
8
+
7
9
  require 'flapjack/data/contact'
8
10
  require 'flapjack/data/entity_check'
9
11
  require 'flapjack/data/notification'
@@ -14,6 +16,7 @@ require 'flapjack/utility'
14
16
  require 'flapjack/gateways/email'
15
17
  require 'flapjack/gateways/sms_messagenet'
16
18
  require 'flapjack/gateways/sms_twilio'
19
+ require 'flapjack/gateways/sms_gammu'
17
20
  require 'flapjack/gateways/aws_sns'
18
21
 
19
22
  module Flapjack
@@ -127,7 +130,7 @@ module Flapjack
127
130
  @notifylog.info("#{event_id} | " +
128
131
  "#{notification.type} | #{message.contact.id} | #{media_type} | #{address}")
129
132
 
130
- unless @queues[media_type.to_s]
133
+ if @queues[media_type.to_s].nil?
131
134
  @logger.error("no queue for media type: #{media_type}")
132
135
  return
133
136
  end
@@ -154,19 +157,7 @@ module Flapjack
154
157
  contents_tags = contents['tags']
155
158
  contents['tags'] = contents_tags.is_a?(Set) ? contents_tags.to_a : contents_tags
156
159
 
157
- # FIXME(@auxesis): change Resque jobs to use raw blpop
158
- case media_type.to_sym
159
- when :sms
160
- Resque.enqueue_to(@queues['sms'], Flapjack::Gateways::SmsMessagenet, contents)
161
- when :sms_twilio
162
- Resque.enqueue_to(@queues['sms_twilio'], Flapjack::Gateways::SmsTwilio, contents)
163
- when :email
164
- Resque.enqueue_to(@queues['email'], Flapjack::Gateways::Email, contents)
165
- when :sns
166
- Resque.enqueue_to(@queues['sns'], Flapjack::Gateways::AwsSns, contents)
167
- else
168
- @redis.rpush(@queues[media_type.to_s], Flapjack.dump_json(contents))
169
- end
160
+ Flapjack::Data::Alert.add(@queues[media_type.to_s], contents, :redis => @redis)
170
161
  end
171
162
  end
172
163
 
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'thin'
4
- require 'resque'
5
4
  require 'redis'
6
5
 
7
6
  # we don't want to stop the entire EM reactor when we stop a web server
@@ -46,63 +45,6 @@ module Thin
46
45
  end
47
46
  end
48
47
 
49
- # Resque is really designed around a multiprocess model, so we here we
50
- # stub some that behaviour away.
51
- module Resque
52
-
53
- class Worker
54
-
55
- def procline(string)
56
- # $0 = "resque-#{Resque::Version}: #{string}"
57
- # log! $0
58
- end
59
-
60
- # Redefining the entire method to stop the direct access to $0 :(
61
- def work(interval = 5.0, &block)
62
- interval = Float(interval)
63
- # $0 = "resque: Starting"
64
- startup
65
-
66
- loop do
67
-
68
- break if shutdown?
69
-
70
- if not paused? and job = reserve
71
- log "got: #{job.inspect}"
72
- job.worker = self
73
- run_hook :before_fork, job
74
- working_on job
75
-
76
- if @child = fork
77
- srand # Reseeding
78
- procline "Forked #{@child} at #{Time.now.to_i}"
79
- Process.wait(@child)
80
- else
81
- unregister_signal_handlers if !@cant_fork && term_child
82
- procline "Processing #{job.queue} since #{Time.now.to_i}"
83
- redis.client.reconnect if !@cant_fork # Don't share connection with parent
84
- perform(job, &block)
85
- exit! unless @cant_fork
86
- end
87
-
88
- done_working
89
- @child = nil
90
- else
91
- break if interval.zero?
92
- log! "Sleeping for #{interval} seconds"
93
- procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
94
- sleep interval
95
- end
96
- end
97
-
98
- unregister_worker
99
- rescue Exception => exception
100
- unregister_worker(exception)
101
- end
102
-
103
- end
104
- end
105
-
106
48
  # As Redis::Future objects inherit from BasicObject, it's difficult to
107
49
  # distinguish between them and other objects in collected data from
108
50
  # pipelined queries.
@@ -13,8 +13,6 @@
13
13
  require 'hiredis'
14
14
  require 'redis/connection/synchrony'
15
15
  require 'redis'
16
- require 'em-resque'
17
- require 'em-resque/worker'
18
16
  require 'thin'
19
17
 
20
18
  require 'flapjack/notifier'
@@ -26,6 +24,7 @@ require 'flapjack/gateways/pagerduty'
26
24
  require 'flapjack/gateways/email'
27
25
  require 'flapjack/gateways/sms_messagenet'
28
26
  require 'flapjack/gateways/sms_twilio'
27
+ require 'flapjack/gateways/sms_gammu'
29
28
  require 'flapjack/gateways/aws_sns'
30
29
  require 'flapjack/gateways/web'
31
30
  require 'flapjack/logger'
@@ -45,7 +44,7 @@ module Flapjack
45
44
 
46
45
  # TODO find a better way of expressing these two methods
47
46
  def self.is_pikelet?(type)
48
- type_klass = [Flapjack::Pikelet::Generic, Flapjack::Pikelet::Resque,
47
+ type_klass = [Flapjack::Pikelet::Generic,
49
48
  Flapjack::Pikelet::Thin].detect do |kl|
50
49
 
51
50
  kl::PIKELET_TYPES[type]
@@ -57,7 +56,6 @@ module Flapjack
57
56
  def self.create(type, opts = {})
58
57
  pikelet = nil
59
58
  [Flapjack::Pikelet::Generic,
60
- Flapjack::Pikelet::Resque,
61
59
  Flapjack::Pikelet::Thin].each do |kl|
62
60
  next unless kl::PIKELET_TYPES[type]
63
61
  break if pikelet = kl.create(type, opts)
@@ -95,14 +93,6 @@ module Flapjack
95
93
  @status = 'stopping'
96
94
  end
97
95
 
98
- def configure_resque
99
- unless ::Resque.instance_variable_defined?('@flapjack_pool') && !::Resque.instance_variable_get('@flapjack_pool').nil?
100
- resque_pool = Flapjack::RedisPool.new(:config => @redis_config, :logger => @logger)
101
- ::Resque.instance_variable_set('@flapjack_pool', resque_pool)
102
- ::Resque.redis = resque_pool
103
- end
104
- end
105
-
106
96
  end
107
97
 
108
98
  class Generic < Flapjack::Pikelet::Base
@@ -111,7 +101,12 @@ module Flapjack
111
101
  'processor' => Flapjack::Processor,
112
102
  'jabber' => Flapjack::Gateways::Jabber,
113
103
  'pagerduty' => Flapjack::Gateways::Pagerduty,
114
- 'oobetet' => Flapjack::Gateways::Oobetet}
104
+ 'oobetet' => Flapjack::Gateways::Oobetet,
105
+ 'email' => Flapjack::Gateways::Email,
106
+ 'sms' => Flapjack::Gateways::SmsMessagenet,
107
+ 'sms_gammu' => Flapjack::Gateways::SmsGammu,
108
+ 'sms_twilio' => Flapjack::Gateways::SmsTwilio,
109
+ 'sns' => Flapjack::Gateways::AwsSns}
115
110
 
116
111
  def self.create(type, opts = {})
117
112
  self.new(type, PIKELET_TYPES[type], :config => opts[:config],
@@ -123,8 +118,6 @@ module Flapjack
123
118
  def initialize(type, pikelet_klass, opts = {})
124
119
  super(type, pikelet_klass, opts)
125
120
 
126
- configure_resque if type == 'notifier'
127
-
128
121
  @pikelet = @klass.new(opts.merge(:logger => @logger))
129
122
  end
130
123
 
@@ -155,69 +148,6 @@ module Flapjack
155
148
 
156
149
  end
157
150
 
158
- class Resque < Flapjack::Pikelet::Base
159
-
160
- PIKELET_TYPES = {'email' => Flapjack::Gateways::Email,
161
- 'sms' => Flapjack::Gateways::SmsMessagenet,
162
- 'sms_twilio' => Flapjack::Gateways::SmsTwilio,
163
- 'sns' => Flapjack::Gateways::AwsSns}
164
-
165
- def self.create(type, opts = {})
166
- self.new(type, PIKELET_TYPES[type], :config => opts[:config],
167
- :redis_config => opts[:redis_config],
168
- :boot_time => opts[:boot_time])
169
- end
170
-
171
- def initialize(type, pikelet_klass, opts = {})
172
- super(type, pikelet_klass, opts)
173
-
174
- configure_resque
175
-
176
- # guard against another Resque pikelet having created the pool already
177
- unless defined?(@@redis_connection) && !@@redis_connection.nil?
178
- @@redis_connection = Flapjack::RedisPool.new(:config => @redis_config, :logger => @logger)
179
- end
180
-
181
- pikelet_klass.instance_variable_set('@config', @config)
182
- pikelet_klass.instance_variable_set('@redis', @@redis_connection)
183
- pikelet_klass.instance_variable_set('@logger', @logger)
184
-
185
- # TODO error if config['queue'].nil?
186
-
187
- @worker = EM::Resque::Worker.new(@config['queue'])
188
- # # Use these to debug the resque workers
189
- # worker.verbose = true
190
- # worker.very_verbose = true
191
- end
192
-
193
- def start
194
- @fiber = Fiber.new {
195
- @worker.work(0.1)
196
- }
197
- super
198
- @klass.start if @klass.respond_to?(:start)
199
- @fiber.resume
200
- end
201
-
202
- # this should only reload if all changes can be applied -- will
203
- # return false to log warning otherwise
204
- def reload(cfg)
205
- @klass.respond_to?(:reload) ?
206
- (@klass.reload(cfg) && super(cfg)) : super(cfg)
207
- end
208
-
209
- def stop
210
- @worker.shutdown if @worker && @fiber && @fiber.alive?
211
- @klass.stop if @klass.respond_to?(:stop)
212
- super
213
- end
214
-
215
- def update_status
216
- return @status unless 'stopping'.eql?(@status)
217
- @status = 'stopped' if @fiber && !@fiber.alive?
218
- end
219
- end
220
-
221
151
  class Thin < Flapjack::Pikelet::Base
222
152
 
223
153
  PIKELET_TYPES = {'web' => Flapjack::Gateways::Web,