flapjack 1.2.1 → 1.2.2

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