flapjack 0.6.53 → 0.6.54

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/bin/flapjack +103 -19
  2. data/bin/flapjack-nagios-receiver +166 -52
  3. data/bin/flapper +107 -18
  4. data/etc/flapjack_config.yaml.example +16 -0
  5. data/features/events.feature +63 -0
  6. data/features/steps/events_steps.rb +5 -5
  7. data/features/steps/notifications_steps.rb +8 -6
  8. data/features/steps/time_travel_steps.rb +4 -4
  9. data/features/support/env.rb +1 -2
  10. data/flapjack.gemspec +1 -1
  11. data/lib/flapjack/configuration.rb +11 -13
  12. data/lib/flapjack/coordinator.rb +100 -220
  13. data/lib/flapjack/data/entity_check.rb +2 -2
  14. data/lib/flapjack/data/event.rb +3 -3
  15. data/lib/flapjack/executive.rb +30 -40
  16. data/lib/flapjack/filters/delays.rb +1 -1
  17. data/lib/flapjack/gateways/api.rb +6 -23
  18. data/lib/flapjack/gateways/email.rb +4 -10
  19. data/lib/flapjack/gateways/email/alert.html.haml +0 -5
  20. data/lib/flapjack/gateways/email/alert.text.erb +0 -1
  21. data/lib/flapjack/gateways/jabber.rb +80 -67
  22. data/lib/flapjack/gateways/oobetet.rb +29 -25
  23. data/lib/flapjack/gateways/pagerduty.rb +26 -45
  24. data/lib/flapjack/gateways/sms_messagenet.rb +10 -17
  25. data/lib/flapjack/gateways/web.rb +7 -21
  26. data/lib/flapjack/gateways/web/views/_css.haml +3 -0
  27. data/lib/flapjack/gateways/web/views/check.haml +1 -1
  28. data/lib/flapjack/logger.rb +57 -0
  29. data/lib/flapjack/patches.rb +0 -10
  30. data/lib/flapjack/pikelet.rb +214 -30
  31. data/lib/flapjack/redis_pool.rb +2 -17
  32. data/lib/flapjack/version.rb +1 -1
  33. data/spec/lib/flapjack/coordinator_spec.rb +116 -136
  34. data/spec/lib/flapjack/data/entity_check_spec.rb +3 -3
  35. data/spec/lib/flapjack/executive_spec.rb +33 -34
  36. data/spec/lib/flapjack/gateways/api_spec.rb +4 -2
  37. data/spec/lib/flapjack/gateways/jabber_spec.rb +39 -36
  38. data/spec/lib/flapjack/gateways/oobetet_spec.rb +14 -24
  39. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +43 -45
  40. data/spec/lib/flapjack/gateways/web_spec.rb +42 -35
  41. data/spec/lib/flapjack/logger_spec.rb +32 -0
  42. data/spec/lib/flapjack/pikelet_spec.rb +124 -15
  43. data/spec/lib/flapjack/redis_pool_spec.rb +1 -3
  44. data/spec/spec_helper.rb +34 -1
  45. data/tasks/events.rake +1 -0
  46. data/tmp/create_event_ok.rb +31 -0
  47. data/tmp/create_event_unknown.rb +31 -0
  48. data/tmp/create_events_ok.rb +1 -1
  49. metadata +10 -11
  50. data/bin/flapjack-nagios-receiver-control +0 -15
  51. data/bin/flapper-control +0 -15
  52. data/lib/flapjack/daemonizing.rb +0 -186
  53. data/lib/flapjack/gateways/base.rb +0 -38
@@ -35,10 +35,14 @@ development:
35
35
  sms_queue: sms_notifications
36
36
  jabber_queue: jabber_notifications
37
37
  notification_log_file: log/flapjack-notification.log
38
+ logger:
39
+ level: INFO
38
40
  gateways:
39
41
  email:
40
42
  enabled: yes
41
43
  queue: email_notifications
44
+ logger:
45
+ level: INFO
42
46
  smtp_config:
43
47
  port: 2525
44
48
  # address: "localhost"
@@ -53,6 +57,8 @@ development:
53
57
  queue: sms_notifications
54
58
  username: "ermahgerd"
55
59
  password: "xxxx"
60
+ logger:
61
+ level: INFO
56
62
  jabber:
57
63
  enabled: yes
58
64
  queue: jabber_notifications
@@ -64,6 +70,8 @@ development:
64
70
  rooms:
65
71
  - "gimp@conference.jabber.domain.tld"
66
72
  - "log@conference.jabber.domain.tld"
73
+ logger:
74
+ level: INFO
67
75
  oobetet:
68
76
  enabled: yes
69
77
  server: "jabber.domain.tld"
@@ -79,17 +87,25 @@ development:
79
87
  - "flapjacktest@conference.jabber.domain.tld"
80
88
  - "gimp@conference.jabber.domain.tld"
81
89
  - "log@conference.jabber.domain.tld"
90
+ logger:
91
+ level: INFO
82
92
  pagerduty:
83
93
  enabled: yes
84
94
  queue: pagerduty_notifications
95
+ logger:
96
+ level: INFO
85
97
  web:
86
98
  enabled: yes
87
99
  port: 5080
88
100
  access_log: "log/web_access.log"
101
+ logger:
102
+ level: INFO
89
103
  api:
90
104
  enabled: yes
91
105
  port: 5081
92
106
  access_log: "log/api_access.log"
107
+ logger:
108
+ level: INFO
93
109
 
94
110
  test:
95
111
  redis:
@@ -160,3 +160,66 @@ Feature: events
160
160
  And an ok event is received for check 'abc' on entity 'def'
161
161
  Then a notification should not be generated for check 'abc' on entity 'def'
162
162
 
163
+ Scenario: Flapper (down for one minute, up for one minute, repeat)
164
+ Given check 'abc' for entity 'def' is in an ok state
165
+ When a failure event is received for check 'abc' on entity 'def'
166
+ Then a notification should not be generated for check 'abc' on entity 'def'
167
+ When 10 seconds passes
168
+ And a failure event is received for check 'abc' on entity 'def'
169
+ Then a notification should not be generated for check 'abc' on entity 'def'
170
+ When 10 seconds passes
171
+ And a failure event is received for check 'abc' on entity 'def'
172
+ Then a notification should not be generated for check 'abc' on entity 'def'
173
+ When 10 seconds passes
174
+ # 30 seconds
175
+ And a failure event is received for check 'abc' on entity 'def'
176
+ Then a notification should be generated for check 'abc' on entity 'def'
177
+ When 10 seconds passes
178
+ And a failure event is received for check 'abc' on entity 'def'
179
+ Then a notification should not be generated for check 'abc' on entity 'def'
180
+ When 10 seconds passes
181
+ And a failure event is received for check 'abc' on entity 'def'
182
+ Then a notification should not be generated for check 'abc' on entity 'def'
183
+ When 10 seconds passes
184
+ # 60 seconds
185
+ And an ok event is received for check 'abc' on entity 'def'
186
+ Then a notification should be generated for check 'abc' on entity 'def'
187
+ When 10 seconds passes
188
+ And an ok event is received for check 'abc' on entity 'def'
189
+ Then a notification should not be generated for check 'abc' on entity 'def'
190
+ When 10 seconds passes
191
+ And an ok event is received for check 'abc' on entity 'def'
192
+ Then a notification should not be generated for check 'abc' on entity 'def'
193
+ When 10 seconds passes
194
+ And an ok event is received for check 'abc' on entity 'def'
195
+ Then a notification should not be generated for check 'abc' on entity 'def'
196
+ When 10 seconds passes
197
+ And an ok event is received for check 'abc' on entity 'def'
198
+ Then a notification should not be generated for check 'abc' on entity 'def'
199
+ When 10 seconds passes
200
+ And an ok event is received for check 'abc' on entity 'def'
201
+ Then a notification should not be generated for check 'abc' on entity 'def'
202
+ When 10 seconds passes
203
+ # 120 seconds
204
+ And a failure event is received for check 'abc' on entity 'def'
205
+ Then a notification should not be generated for check 'abc' on entity 'def'
206
+ When 10 seconds passes
207
+ And a failure event is received for check 'abc' on entity 'def'
208
+ Then a notification should not be generated for check 'abc' on entity 'def'
209
+ When 10 seconds passes
210
+ And a failure event is received for check 'abc' on entity 'def'
211
+ Then a notification should not be generated for check 'abc' on entity 'def'
212
+ When 10 seconds passes
213
+ # 150 seconds
214
+ And a failure event is received for check 'abc' on entity 'def'
215
+ Then a notification should be generated for check 'abc' on entity 'def'
216
+ When 10 seconds passes
217
+ And a failure event is received for check 'abc' on entity 'def'
218
+ Then a notification should not be generated for check 'abc' on entity 'def'
219
+ When 10 seconds passes
220
+ And a failure event is received for check 'abc' on entity 'def'
221
+ Then a notification should not be generated for check 'abc' on entity 'def'
222
+ When 10 seconds passes
223
+ # 180 seconds
224
+ And an ok event is received for check 'abc' on entity 'def'
225
+ Then a notification should be generated for check 'abc' on entity 'def'
@@ -147,17 +147,17 @@ end
147
147
 
148
148
  # TODO logging is a side-effect, should test for notification generation itself
149
149
  Then /^a notification should not be generated for check '([\w\.\-]+)' on entity '([\w\.\-]+)'$/ do |check, entity|
150
- message = @app.logger.messages.find_all {|m| m =~ /ending notifications for event #{entity}:#{check}/ }.last
151
- message ? happy = message.match(/Not sending notifications/) : happy = false
150
+ message = @logger.messages.find_all {|m| m =~ /enerating notifications for event #{entity}:#{check}/ }.last
151
+ message ? happy = message.match(/Not generating notifications/) : happy = false
152
152
  happy.should be_true
153
153
  end
154
154
 
155
155
  Then /^a notification should be generated for check '([\w\.\-]+)' on entity '([\w\.\-]+)'$/ do |check, entity|
156
- message = @app.logger.messages.find_all {|m| m =~ /ending notifications for event #{entity}:#{check}/ }.last
157
- message ? happy = message.match(/Sending notifications/) : happy = false
156
+ message = @logger.messages.find_all {|m| m =~ /enerating notifications for event #{entity}:#{check}/ }.last
157
+ message ? happy = message.match(/Generating notifications/) : happy = false
158
158
  happy.should be_true
159
159
  end
160
160
 
161
161
  Then /^show me the notifications?$/ do
162
- puts @app.logger.messages.join("\n")
162
+ puts @logger.messages.join("\n")
163
163
  end
@@ -127,7 +127,8 @@ When /^the SMS notification handler runs successfully$/ do
127
127
 
128
128
  Flapjack::Gateways::SmsMessagenet.instance_variable_set('@config', {'username' => 'abcd', 'password' => 'efgh'})
129
129
  Flapjack::Gateways::SmsMessagenet.instance_variable_set('@logger', @logger)
130
- Flapjack::Gateways::SmsMessagenet.instance_variable_set('@sent', 0)
130
+ Flapjack::Gateways::SmsMessagenet.start
131
+
131
132
  Flapjack::Gateways::SmsMessagenet.perform(@sms_notification)
132
133
  end
133
134
 
@@ -135,15 +136,16 @@ When /^the SMS notification handler fails to send an SMS$/ do
135
136
  @request = stub_request(:get, /^#{Regexp.escape(Flapjack::Gateways::SmsMessagenet::MESSAGENET_URL)}/).to_return(:status => [500, "Internal Server Error"])
136
137
  Flapjack::Gateways::SmsMessagenet.instance_variable_set('@config', {'username' => 'abcd', 'password' => 'efgh'})
137
138
  Flapjack::Gateways::SmsMessagenet.instance_variable_set('@logger', @logger)
138
- Flapjack::Gateways::SmsMessagenet.instance_variable_set('@sent', 0)
139
+ Flapjack::Gateways::SmsMessagenet.start
140
+
139
141
  Flapjack::Gateways::SmsMessagenet.perform(@sms_notification)
140
142
  end
141
143
 
142
144
  When /^the email notification handler runs successfully$/ do
143
145
  Resque.redis = @redis
144
- Flapjack::Gateways::Email.bootstrap(:config => {'smtp_config' => {'host' => '127.0.0.1', 'port' => 2525}})
146
+ Flapjack::Gateways::Email.instance_variable_set('@config', {'smtp_config' => {'host' => '127.0.0.1', 'port' => 2525}})
145
147
  Flapjack::Gateways::Email.instance_variable_set('@logger', @logger)
146
- Flapjack::Gateways::Email.instance_variable_set('@sent', 0)
148
+ Flapjack::Gateways::Email.start
147
149
 
148
150
  # poor man's stubbing
149
151
  EM::P::SmtpClient.class_eval {
@@ -159,9 +161,9 @@ end
159
161
 
160
162
  When /^the email notification handler fails to send an email$/ do
161
163
  Resque.redis = @redis
162
- Flapjack::Gateways::Email.bootstrap(:config => {'smtp_config' => {'host' => '127.0.0.1', 'port' => 2525}})
164
+ Flapjack::Gateways::Email.instance_variable_set('@config', {'smtp_config' => {'host' => '127.0.0.1', 'port' => 2525}})
163
165
  Flapjack::Gateways::Email.instance_variable_set('@logger', @logger)
164
- Flapjack::Gateways::Email.instance_variable_set('@sent', 0)
166
+ Flapjack::Gateways::Email.start
165
167
 
166
168
  # poor man's stubbing
167
169
  EM::P::SmtpClient.class_eval {
@@ -6,24 +6,24 @@ require 'chronic'
6
6
  When /^(.+) passes$/ do |time|
7
7
  period = Chronic.parse("#{time} from now")
8
8
  Delorean.time_travel_to(period)
9
- puts "Time Travelled to #{Time.now.to_s}"
9
+ # puts "Time Travelled to #{Time.now.to_s}"
10
10
  end
11
11
 
12
12
  Given /^I time travel to (.+)$/ do |period|
13
13
  Delorean.time_travel_to(period)
14
- puts "Time Travelled to #{Time.now.to_s}"
14
+ # puts "Time Travelled to #{Time.now.to_s}"
15
15
  end
16
16
 
17
17
  Given /^I come back to the present$/ do
18
18
  Delorean.back_to_the_present
19
- puts "Time Travelled to the present, #{Time.now.to_s}"
19
+ # puts "Time Travelled to the present, #{Time.now.to_s}"
20
20
  end
21
21
 
22
22
  Given /^I time travel in (.+) to (.+)$/ do |zone_name, timestamp|
23
23
  zone = ::Time.find_zone!(zone_name)
24
24
  time = zone.parse timestamp
25
25
  Delorean.time_travel_to time
26
- puts "Time Travelled to #{Time.now.to_s}"
26
+ # puts "Time Travelled to #{Time.now.to_s}"
27
27
  end
28
28
 
29
29
  Then /^the time in UTC should be about (.+)$/ do |timestamp|
@@ -61,8 +61,7 @@ end
61
61
  Before do
62
62
  @logger = MockLogger.new
63
63
  # Use a separate database whilst testing
64
- @app = Flapjack::Executive.new
65
- @app.bootstrap(:logger => @logger,
64
+ @app = Flapjack::Executive.new(:logger => @logger,
66
65
  :config => {'email_queue' => 'email_notifications',
67
66
  'sms_queue' => 'sms_notifications'},
68
67
  :redis_config => redis_opts)
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.version = Flapjack::VERSION
19
19
 
20
- gem.add_dependency 'daemons'
20
+ gem.add_dependency 'dante'
21
21
  gem.add_dependency 'log4r'
22
22
  gem.add_dependency 'yajl-ruby'
23
23
  gem.add_dependency 'eventmachine', '~> 1.0.0'
@@ -7,19 +7,12 @@ module Flapjack
7
7
 
8
8
  class Configuration
9
9
 
10
+ attr_reader :filename
11
+
10
12
  def initialize(opts = {})
11
13
  @logger = opts[:logger]
12
- unless @logger
13
- @logger = Logger.new(STDOUT)
14
- @logger.level = Logger::ERROR
15
- end
16
- end
17
-
18
- def logger
19
- @logger
20
14
  end
21
15
 
22
- # TODO reduce/remove the use of this, just access parts
23
16
  def all
24
17
  @config_env
25
18
  end
@@ -53,29 +46,34 @@ module Flapjack
53
46
  end
54
47
 
55
48
  def load(filename)
49
+ @filename = nil
50
+ @config_env = nil
51
+
56
52
  unless File.file?(filename)
57
- logger.error "Could not find file '#{filename}'"
53
+ @logger.error "Could not find file '#{filename}'" if @logger
58
54
  return
59
55
  end
60
56
 
61
57
  unless defined?(FLAPJACK_ENV)
62
- logger.error "Environment variable 'FLAPJACK_ENV' is not set"
58
+ @logger.error "Environment variable 'FLAPJACK_ENV' is not set" if @logger
63
59
  return
64
60
  end
65
61
 
66
62
  config = YAML::load_file(filename)
67
63
 
68
64
  if config.nil?
69
- logger.error "Could not load config file '#{filename}'"
65
+ @logger.error "Could not load config file '#{filename}'" if @logger
70
66
  return
71
67
  end
72
68
 
73
69
  @config_env = config[FLAPJACK_ENV]
74
70
 
75
71
  if @config_env.nil?
76
- logger.error "No config data for environment '#{FLAPJACK_ENV}' found in '#{filename}'"
72
+ @logger.error "No config data for environment '#{FLAPJACK_ENV}' found in '#{filename}'" if @logger
77
73
  return
78
74
  end
75
+
76
+ @filename = filename
79
77
  end
80
78
 
81
79
  end
@@ -1,297 +1,177 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'eventmachine'
4
- # the redis/synchrony gems need to be required in this particular order, see
5
- # the redis-rb README for details
6
- require 'hiredis'
7
4
  require 'em-synchrony'
8
- require 'redis/connection/synchrony'
9
- require 'redis'
10
- require 'em-resque'
11
- require 'em-resque/worker'
12
- require 'thin'
13
5
 
14
6
  require 'flapjack/configuration'
15
7
  require 'flapjack/patches'
16
- require 'flapjack/daemonizing'
17
8
  require 'flapjack/executive'
18
9
  require 'flapjack/redis_pool'
19
10
 
20
11
  require 'flapjack/pikelet'
21
- require 'flapjack/gateways/base'
22
-
23
12
  require 'flapjack/executive'
24
13
 
25
- require 'flapjack/gateways/api'
26
- require 'flapjack/gateways/jabber'
27
- require 'flapjack/gateways/oobetet'
28
- require 'flapjack/gateways/pagerduty'
29
- require 'flapjack/gateways/email'
30
- require 'flapjack/gateways/sms_messagenet'
31
- require 'flapjack/gateways/web'
32
-
33
14
  module Flapjack
34
15
 
35
16
  class Coordinator
36
17
 
37
- include Flapjack::Daemonizable
38
-
39
18
  def initialize(config)
40
19
  @config = config
41
20
  @redis_options = config.for_redis
42
21
  @pikelets = []
43
22
 
44
- @logger = Log4r::Logger.new("flapjack-coordinator")
45
- @logger.add(Log4r::StdoutOutputter.new("flapjack-coordinator"))
46
- @logger.add(Log4r::SyslogOutputter.new("flapjack-coordinator"))
47
- end
23
+ # TODO convert this to use flapjack-logger
24
+ logger_name = "flapjack-coordinator"
25
+ @logger = Log4r::Logger.new(logger_name)
48
26
 
49
- def start(options = {})
50
- @signals = options[:signals]
51
- if options[:daemonize]
52
- @signals = options[:signals]
53
- daemonize
54
- else
55
- run(:signals => options[:signals])
27
+ formatter = Log4r::PatternFormatter.new(:pattern => "%d [%l] :: #{logger_name} :: %m",
28
+ :date_pattern => "%Y-%m-%dT%H:%M:%S%z")
29
+
30
+ [Log4r::StdoutOutputter, Log4r::SyslogOutputter].each do |outp_klass|
31
+ outp = outp_klass.new(logger_name)
32
+ outp.formatter = formatter
33
+ @logger.add(outp)
56
34
  end
57
35
  end
58
36
 
59
- def after_daemonize
60
- run(:signals => @signals)
37
+ def start(options = {})
38
+ EM.synchrony do
39
+ add_pikelets(pikelets(@config.all))
40
+ setup_signals if options[:signals]
41
+ end
61
42
  end
62
43
 
63
44
  def stop
64
45
  return if @stopping
65
46
  @stopping = true
66
- shutdown
47
+ remove_pikelets(@pikelets, :shutdown => true)
67
48
  end
68
49
 
69
- private
50
+ # NB: global config options (e.g. daemonize, pidfile,
51
+ # logfile, redis options) won't be checked on reload.
52
+ # should we do a full restart if some of these change?
53
+ def reload
54
+ prev_pikelet_cfg = pikelets(@config.all)
70
55
 
71
- def run(options = {})
56
+ removed = []
57
+ added = []
58
+ ask_running = []
72
59
 
73
- EM.synchrony do
60
+ cfg_filename = @config.filename
61
+ @config = Flapjack::Configuration.new
62
+ @config.load(cfg_filename)
74
63
 
75
- config_env = @config.all
64
+ enabled_pikelet_cfg = pikelets(@config.all)
76
65
 
77
- pikelets(config_env).each_pair do |pikelet_class, pikelet_cfg|
78
- next unless pikelet_cfg['enabled']
79
- build_pikelet(pikelet_class, pikelet_cfg)
80
- end
66
+ (prev_pikelet_cfg.keys + enabled_pikelet_cfg.keys).each do |type|
81
67
 
82
- gateways(config_env).each_pair do |gateway_class, gateway_cfg|
83
- # TODO split out gateway logic to build_gateway
84
- next unless gateway_cfg['enabled']
85
- build_pikelet(gateway_class, gateway_cfg)
68
+ if prev_pikelet_cfg.keys.include?(type)
69
+ if enabled_pikelet_cfg.keys.include?(type)
70
+ ask_running << type
71
+ else
72
+ removed << type
73
+ end
74
+ elsif enabled_pikelet_cfg.keys.include?(type)
75
+ added << type
86
76
  end
87
77
 
88
- setup_signals if @signals
89
78
  end
90
79
 
91
- end
92
-
93
- # the global nature of this seems at odds with it calling stop
94
- # within a single coordinator instance. Coordinator is essentially
95
- # a singleton anyway...
96
- def setup_signals
97
- Kernel.trap('INT') { stop }
98
- Kernel.trap('TERM') { stop }
99
- unless RbConfig::CONFIG['host_os'] =~ /mswin|windows|cygwin/i
100
- Kernel.trap('QUIT') { stop }
101
- # Kernel.trap('HUP') { }
80
+ @pikelets.select {|pik| ask_running.include?(pik.type) }.each do |pik|
81
+ # for sections previously there and still there, ask them
82
+ # to make the config change; they will if they can, or will signal
83
+ # restart is needed if not
84
+
85
+ # reload() returns trinary value here; true means the change was made, false
86
+ # means the pikelet needs to be restarted, nil means no change
87
+ # was required
88
+ next unless pik.reload(enabled_pikelet_cfg[pik.type]).is_a?(FalseClass)
89
+ removed << pik.type
90
+ added << pik.type
102
91
  end
103
- end
104
92
 
105
- def build_pikelet(pikelet_class, pikelet_cfg)
106
- @logger.debug "coordinator is now initialising the #{pikelet_class} pikelet"
93
+ # puts "removed"
94
+ # p removed
107
95
 
108
- inc_mod = pikelet_class.included_modules
109
- ext_mod = extended_modules(pikelet_class)
96
+ # puts "added"
97
+ # p added
110
98
 
111
- pikelet = nil
112
- fiber = nil
99
+ removed_pikelets = @pikelets.select {|pik| removed.include?(pik.type) }
113
100
 
114
- if inc_mod.include?(Flapjack::GenericPikelet)
115
- pikelet = pikelet_class.new
116
- pikelet.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
117
- else
118
- pikelet_class.bootstrap(:config => pikelet_cfg, :redis_config => @redis_options)
101
+ # puts "removed pikelets"
102
+ # p removed_pikelets
119
103
 
120
- if ext_mod.include?(Flapjack::Gateways::Thin)
121
-
122
- unless @thin_silenced
123
- Thin::Logging.silent = true
124
- @thin_silenced = true
125
- end
104
+ remove_pikelets(removed_pikelets)
126
105
 
127
- pikelet = Thin::Server.new('0.0.0.0',
128
- pikelet_class.instance_variable_get('@port'),
129
- pikelet_class, :signals => false)
106
+ # is there a nicer way to only keep the parts of the hash with matching keys?
107
+ added_pikelets = enabled_pikelet_cfg.select {|k, v| added.include?(k) }
130
108
 
131
- elsif ext_mod.include?(Flapjack::Gateways::Resque)
109
+ # puts "added pikelet configs"
110
+ # p added_pikelets
132
111
 
133
- # set up connection pooling, stop resque errors
134
- unless @resque_pool
135
- @resque_pool = Flapjack::RedisPool.new(:config => @redis_options)
136
- ::Resque.redis = @resque_pool
137
- ## NB: can override the default 'resque' namespace like this
138
- #::Resque.redis.namespace = 'flapjack'
139
- end
140
-
141
- # TODO error if pikelet_cfg['queue'].nil?
142
- pikelet = EM::Resque::Worker.new(pikelet_cfg['queue'])
143
- # # Use these to debug the resque workers
144
- # pikelet.verbose = true
145
- # pikelet.very_verbose = true
146
- end
147
-
148
- end
112
+ add_pikelets(added_pikelets)
113
+ end
149
114
 
150
- pikelet_info = {:class => pikelet_class, :instance => pikelet}
151
-
152
- if inc_mod.include?(Flapjack::GenericPikelet) ||
153
- ext_mod.include?(Flapjack::Gateways::Resque)
154
-
155
- fiber = Fiber.new {
156
- begin
157
- # Can't use local inc_mod/ext_mod variables in the new fiber
158
- if pikelet.is_a?(Flapjack::GenericPikelet)
159
- pikelet.main
160
- elsif extended_modules(pikelet_class).include?(Flapjack::Gateways::Resque)
161
- pikelet.work(0.1)
162
- end
163
- rescue Exception => e
164
- trace = e.backtrace.join("\n")
165
- @logger.fatal "#{e.message}\n#{trace}"
166
- stop
167
- end
168
- }
115
+ private
169
116
 
170
- pikelet_info[:fiber] = fiber
171
- fiber.resume
172
- @logger.debug "new fiber created for #{pikelet_class}"
173
- elsif ext_mod.include?(Flapjack::Gateways::Thin)
174
- pikelet.start
175
- @logger.debug "new thin server instance started for #{pikelet_class}"
117
+ # the global nature of this seems at odds with it calling stop
118
+ # within a single coordinator instance. Coordinator is essentially
119
+ # a singleton anyway...
120
+ def setup_signals
121
+ Kernel.trap('INT') { stop }
122
+ Kernel.trap('TERM') { stop }
123
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|windows|cygwin/i
124
+ Kernel.trap('QUIT') { stop }
125
+ Kernel.trap('HUP') { reload }
176
126
  end
177
-
178
- @pikelets << pikelet_info
179
127
  end
180
128
 
181
- # only prints state changes, otherwise pikelets not closing promptly can
182
- # cause everything else to be spammy
183
- def health_check
184
- @pikelets.each do |pik|
185
- status = if extended_modules(pik[:class]).include?(Flapjack::Gateways::Thin)
186
- pik[:instance].backend.size > 0 ? 'running' : 'stopped'
187
- elsif pik[:fiber]
188
- pik[:fiber].alive? ? 'running' : 'stopped'
189
- end
190
- next if pik.has_key?(:status) && pik[:status].eql?(status)
191
- @logger.info "#{pik[:class].name}: #{status}"
192
- pik[:status] = status
129
+ # passed a hash with {PIKELET_TYPE => PIKELET_CFG, ...}
130
+ def add_pikelets(pikelets_data = {})
131
+ pikelets_data.each_pair do |type, cfg|
132
+ next unless pikelet = Flapjack::Pikelet.create(type,
133
+ :config => cfg, :redis_config => @redis_options)
134
+ @pikelets << pikelet
135
+ pikelet.start
193
136
  end
194
137
  end
195
138
 
196
- def shutdown
197
- @pikelets.each do |pik|
198
-
199
- pik_inst = pik[:instance]
200
- ext_mod = extended_modules(pik[:class])
201
-
202
- # would be neater if we could use something similar for the class << self
203
- # included pikelets as well
204
- if pik_inst.is_a?(Flapjack::GenericPikelet)
205
- if pik[:fiber] && pik[:fiber].alive?
206
- pik_inst.stop
207
- Fiber.new {
208
- # this needs to use a separate Redis connection from the pikelet's
209
- # one, as that's in the middle of its blpop
210
- r = Redis.new(@redis_options.merge(:driver => 'synchrony'))
211
- pik_inst.add_shutdown_event(:redis => r)
212
- r.quit
213
- }.resume
214
- end
215
- elsif ext_mod.include?(Flapjack::Gateways::Resque)
216
- # resque is polling, so we don't need a shutdown object
217
- pik_inst.shutdown if pik[:fiber] && pik[:fiber].alive?
218
- elsif ext_mod.include?(Flapjack::Gateways::Thin)
219
- # drop from this side, as HTTP keepalive etc. means browsers
220
- # keep connections alive for ages, and we'd be hanging around
221
- # waiting for them to drop
222
- pik_inst.stop!
223
- end
224
- end
225
-
139
+ def remove_pikelets(piks, opts = {})
226
140
  Fiber.new {
141
+ piks.map(&:stop)
227
142
 
228
143
  loop do
229
- health_check
144
+ # only prints state changes, otherwise pikelets not closing promptly can
145
+ # cause everything else to be spammy
146
+ piks.each do |pik|
147
+ old_status = pik.status
148
+ pik.update_status
149
+ status = pik.status
150
+ next if old_status.eql?(status)
151
+ @logger.info "#{pik.type}: #{old_status} -> #{status}"
152
+ end
230
153
 
231
- if @pikelets.any? {|p| p[:status] == 'running'}
154
+ if piks.any? {|p| p.status == 'stopping' }
232
155
  EM::Synchrony.sleep 0.25
233
156
  else
234
- @resque_pool.empty! if @resque_pool
235
-
236
- @pikelets.each do |pik|
237
-
238
- pik_inst = pik[:instance]
239
- ext_mod = extended_modules(pik[:class])
240
-
241
- if pik_inst.is_a?(Flapjack::GenericPikelet)
242
-
243
- pik_inst.cleanup
244
-
245
- elsif [Flapjack::Gateways::Resque, Flapjack::Gateways::Thin].any?{|fp|
246
- ext_mod.include?(fp)
247
- }
248
-
249
- pik[:class].cleanup
250
-
251
- end
252
- end
253
-
254
- EM.stop
157
+ EM.stop if opts[:shutdown]
158
+ @pikelets -= piks
255
159
  break
256
160
  end
257
161
  end
258
162
  }.resume
259
163
  end
260
164
 
261
- def extended_modules(klass)
262
- (class << klass; self; end).included_modules
263
- end
264
-
265
- PIKELET_TYPES = {'executive' => Flapjack::Executive}
266
-
267
- GATEWAY_TYPES = {'web' => Flapjack::Gateways::Web,
268
- 'api' => Flapjack::Gateways::API,
269
- 'jabber' => Flapjack::Gateways::Jabber,
270
- 'pagerduty' => Flapjack::Gateways::Pagerduty,
271
- 'oobetet' => Flapjack::Gateways::Oobetet,
272
- 'email' => Flapjack::Gateways::Email,
273
- 'sms' => Flapjack::Gateways::SmsMessagenet}
274
-
275
-
276
165
  def pikelets(config_env)
277
166
  return {} unless config_env
278
- config_env.inject({}) {|memo, (k, v)|
279
- if klass = PIKELET_TYPES[k]
280
- memo[klass] = v
281
- end
282
- memo
283
- }
284
- end
285
-
286
- def gateways(config_env)
287
- return {} unless config_env && config_env['gateways'] &&
167
+ exec_cfg = config_env.has_key?('executive') && config_env['executive']['enabled'] ?
168
+ {'executive' => config_env['executive']} :
169
+ {}
170
+ return exec_cfg unless config_env && config_env['gateways'] &&
288
171
  !config_env['gateways'].nil?
289
- config_env['gateways'].inject({}) {|memo, (k, v)|
290
- if klass = GATEWAY_TYPES[k]
291
- memo[klass] = v
292
- end
293
- memo
294
- }
172
+ exec_cfg.merge( config_env['gateways'].select {|k, v|
173
+ Flapjack::Pikelet.is_pikelet?(k) && v['enabled']
174
+ } )
295
175
  end
296
176
 
297
177
  end