flapjack 0.5.5 → 0.6.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. data/.gitignore +10 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +10 -0
  4. data/Gemfile +18 -0
  5. data/Guardfile +14 -0
  6. data/README.md +152 -173
  7. data/Rakefile +53 -150
  8. data/bin/flapjack +72 -0
  9. data/bin/flapjack-nagios-receiver +111 -0
  10. data/bin/flapjack-nagios-receiver-control +15 -0
  11. data/bin/flapjack-netsaint-parser +0 -2
  12. data/bin/flapjack-populator +133 -16
  13. data/bin/install-flapjack-systemwide +2 -2
  14. data/config.ru +11 -0
  15. data/dist/etc/init.d/flapjack +46 -0
  16. data/dist/etc/init.d/flapjack-nagios-receiver +36 -0
  17. data/doc/GLOSSARY.md +19 -0
  18. data/etc/flapjack_config.yaml.example +90 -0
  19. data/features/events.feature +132 -0
  20. data/features/notifications.feature +57 -0
  21. data/features/packaging-lintian.feature +5 -3
  22. data/features/steps/events_steps.rb +164 -0
  23. data/features/steps/flapjack-importer_steps.rb +2 -5
  24. data/features/steps/flapjack-worker_steps.rb +13 -6
  25. data/features/steps/notifications_steps.rb +178 -0
  26. data/features/steps/packaging-lintian_steps.rb +14 -0
  27. data/features/steps/time_travel_steps.rb +34 -0
  28. data/features/support/env.rb +63 -36
  29. data/flapjack.gemspec +35 -186
  30. data/lib/flapjack.rb +2 -0
  31. data/lib/flapjack/api.rb +274 -0
  32. data/lib/flapjack/api/entity_check_presenter.rb +184 -0
  33. data/lib/flapjack/api/entity_presenter.rb +66 -0
  34. data/lib/flapjack/cli/worker_manager.rb +1 -2
  35. data/lib/flapjack/configuration.rb +11 -0
  36. data/lib/flapjack/coordinator.rb +288 -0
  37. data/lib/flapjack/daemonizing.rb +186 -0
  38. data/lib/flapjack/data/contact.rb +45 -0
  39. data/lib/flapjack/data/entity.rb +89 -0
  40. data/lib/flapjack/data/entity_check.rb +396 -0
  41. data/lib/flapjack/data/event.rb +144 -0
  42. data/lib/flapjack/data/notification.rb +13 -0
  43. data/lib/flapjack/executive.rb +289 -0
  44. data/lib/flapjack/filters/acknowledgement.rb +39 -0
  45. data/lib/flapjack/filters/{any_parents_failed.rb → base.rb} +6 -4
  46. data/lib/flapjack/filters/delays.rb +53 -0
  47. data/lib/flapjack/filters/detect_mass_client_failures.rb +44 -0
  48. data/lib/flapjack/filters/ok.rb +25 -5
  49. data/lib/flapjack/filters/scheduled_maintenance.rb +17 -0
  50. data/lib/flapjack/filters/unscheduled_maintenance.rb +17 -0
  51. data/lib/flapjack/jabber.rb +294 -0
  52. data/lib/flapjack/notification/common.rb +23 -0
  53. data/lib/flapjack/notification/email.rb +107 -0
  54. data/lib/flapjack/notification/email/alert.html.haml +48 -0
  55. data/lib/flapjack/notification/email/alert.text.erb +14 -0
  56. data/lib/flapjack/notification/sms.rb +42 -0
  57. data/lib/flapjack/notification/sms/messagenet.rb +49 -0
  58. data/lib/flapjack/notifier_engine.rb +4 -4
  59. data/lib/flapjack/notifiers/mailer/mailer.rb +6 -7
  60. data/lib/flapjack/notifiers/xmpp/xmpp.rb +12 -12
  61. data/lib/flapjack/pagerduty.rb +230 -0
  62. data/lib/flapjack/patches.rb +108 -19
  63. data/lib/flapjack/persistence/data_mapper/models/check.rb +5 -3
  64. data/lib/flapjack/persistence/data_mapper/models/check_template.rb +2 -0
  65. data/lib/flapjack/persistence/data_mapper/models/event.rb +2 -0
  66. data/lib/flapjack/persistence/data_mapper/models/node.rb +3 -1
  67. data/lib/flapjack/persistence/data_mapper/models/related_check.rb +3 -1
  68. data/lib/flapjack/pikelet.rb +56 -0
  69. data/lib/flapjack/transports/beanstalkd.rb +1 -1
  70. data/lib/flapjack/transports/result.rb +6 -6
  71. data/lib/flapjack/utility.rb +46 -0
  72. data/lib/flapjack/version.rb +5 -0
  73. data/lib/flapjack/web.rb +198 -0
  74. data/lib/flapjack/web/views/acknowledge.haml +55 -0
  75. data/lib/flapjack/web/views/check.haml +162 -0
  76. data/lib/flapjack/web/views/index.haml +92 -0
  77. data/lib/flapjack/web/views/self_stats.haml +56 -0
  78. data/lib/flapjack/{applications/worker.rb → worker/application.rb} +0 -0
  79. data/lib/flapjack/worker/cli.rb +49 -0
  80. data/{spec → spec.old}/check_sandbox/echo +0 -0
  81. data/{spec → spec.old}/check_sandbox/sandboxed_check +0 -0
  82. data/{spec → spec.old}/configs/flapjack-notifier-couchdb.ini +0 -0
  83. data/{spec → spec.old}/configs/flapjack-notifier.ini +0 -0
  84. data/{spec → spec.old}/configs/recipients.ini +0 -0
  85. data/{spec → spec.old}/helpers.rb +0 -0
  86. data/{spec → spec.old}/inifile_spec.rb +0 -0
  87. data/{spec → spec.old}/mock-notifiers/mock/init.rb +0 -0
  88. data/{spec → spec.old}/mock-notifiers/mock/mock.rb +0 -0
  89. data/{spec → spec.old}/notifier-directories/spoons/testmailer/init.rb +0 -0
  90. data/{spec → spec.old}/notifier_application_spec.rb +0 -0
  91. data/{spec → spec.old}/notifier_filters_spec.rb +0 -0
  92. data/{spec → spec.old}/notifier_options_multiplexer_spec.rb +0 -0
  93. data/{spec → spec.old}/notifier_options_spec.rb +0 -0
  94. data/{spec → spec.old}/notifier_spec.rb +0 -0
  95. data/{spec → spec.old}/notifiers/mailer_spec.rb +0 -0
  96. data/{spec → spec.old}/notifiers/xmpp_spec.rb +0 -0
  97. data/{spec → spec.old}/persistence/datamapper_spec.rb +0 -0
  98. data/{spec → spec.old}/persistence/mock_persistence_backend.rb +0 -0
  99. data/{spec → spec.old}/simple.ini +0 -0
  100. data/{spec → spec.old}/spec.opts +0 -0
  101. data/{spec → spec.old}/test-filters/blocker.rb +0 -0
  102. data/{spec → spec.old}/test-filters/mock.rb +0 -0
  103. data/{spec → spec.old}/transports/beanstalkd_spec.rb +0 -0
  104. data/{spec → spec.old}/transports/mock_transport.rb +0 -0
  105. data/{spec → spec.old}/worker_application_spec.rb +0 -0
  106. data/{spec → spec.old}/worker_options_spec.rb +0 -0
  107. data/spec/lib/flapjack/api/entity_check_presenter_spec.rb +117 -0
  108. data/spec/lib/flapjack/api/entity_presenter_spec.rb +92 -0
  109. data/spec/lib/flapjack/api_spec.rb +170 -0
  110. data/spec/lib/flapjack/coordinator_spec.rb +16 -0
  111. data/spec/lib/flapjack/data/entity_check_spec.rb +398 -0
  112. data/spec/lib/flapjack/data/entity_spec.rb +71 -0
  113. data/spec/lib/flapjack/data/event_spec.rb +6 -0
  114. data/spec/lib/flapjack/executive_spec.rb +59 -0
  115. data/spec/lib/flapjack/filters/acknowledgement_spec.rb +6 -0
  116. data/spec/lib/flapjack/filters/delays_spec.rb +6 -0
  117. data/spec/lib/flapjack/filters/detect_mass_client_failures_spec.rb +6 -0
  118. data/spec/lib/flapjack/filters/ok_spec.rb +6 -0
  119. data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +6 -0
  120. data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +6 -0
  121. data/spec/lib/flapjack/jabber_spec.rb +150 -0
  122. data/spec/lib/flapjack/notification/email_spec.rb +6 -0
  123. data/spec/lib/flapjack/notification/sms_spec.rb +6 -0
  124. data/spec/lib/flapjack/pikelet_spec.rb +28 -0
  125. data/spec/lib/flapjack/web_spec.rb +188 -0
  126. data/spec/spec_helper.rb +44 -0
  127. data/spec/support/profile_all_formatter.rb +44 -0
  128. data/spec/support/uncolored_doc_formatter.rb +9 -0
  129. data/tasks/events.rake +85 -0
  130. data/tmp/acknowledge.rb +14 -0
  131. data/tmp/create_config_yaml.rb +16 -0
  132. data/tmp/create_events_failure.rb +33 -0
  133. data/tmp/create_events_ok.rb +33 -0
  134. data/tmp/create_events_ok_fail_ack_ok.rb +54 -0
  135. data/tmp/create_events_ok_failure.rb +40 -0
  136. data/tmp/create_events_ok_failure_ack.rb +54 -0
  137. data/tmp/dummy_entities.json +1 -0
  138. data/tmp/generate_nagios_test_hosts.rb +16 -0
  139. data/tmp/parse_config_yaml.rb +7 -0
  140. data/tmp/redis_delete_all_keys.rb +11 -0
  141. data/tmp/test_entities.json +1 -0
  142. metadata +482 -221
  143. data/TODO.md +0 -36
  144. data/VERSION +0 -1
  145. data/bin/flapjack-benchmark +0 -50
  146. data/bin/flapjack-notifier +0 -21
  147. data/bin/flapjack-notifier-manager +0 -43
  148. data/bin/flapjack-stats +0 -27
  149. data/bin/flapjack-worker +0 -13
  150. data/bin/flapjack-worker-manager +0 -35
  151. data/dist/etc/init.d/flapjack-notifier +0 -47
  152. data/dist/etc/init.d/flapjack-workers +0 -44
  153. data/features/flapjack-notifier-manager.feature +0 -19
  154. data/features/flapjack-worker-manager.feature +0 -27
  155. data/features/flapjack-worker.feature +0 -27
  156. data/features/netsaint-config-converter.feature +0 -126
  157. data/features/persistence/couch.feature +0 -105
  158. data/features/persistence/sqlite3.feature +0 -105
  159. data/features/persistence/steps/couch_steps.rb +0 -25
  160. data/features/persistence/steps/generic_steps.rb +0 -102
  161. data/features/persistence/steps/sqlite3_steps.rb +0 -13
  162. data/features/steps/flapjack-notifier-manager_steps.rb +0 -24
  163. data/features/steps/flapjack-worker-manager_steps.rb +0 -48
  164. data/lib/flapjack/applications/notifier.rb +0 -222
  165. data/lib/flapjack/cli/notifier.rb +0 -108
  166. data/lib/flapjack/cli/notifier_manager.rb +0 -86
  167. data/lib/flapjack/cli/worker.rb +0 -51
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Formats entity data for presentation by the API methods in Flapjack::API.
4
+ # Currently this just aggregates all of the check data for an entity, leaving
5
+ # clients to make any further calculations for themselves.
6
+
7
+ require 'sinatra/base'
8
+
9
+ require 'flapjack/api/entity_check_presenter'
10
+ require 'flapjack/data/entity_check'
11
+
12
+ module Flapjack
13
+
14
+ class API < Sinatra::Base
15
+
16
+ class EntityPresenter
17
+
18
+ def initialize(entity, options = {})
19
+ @entity = entity
20
+ @redis = options[:redis]
21
+ end
22
+
23
+ def outages(start_time, end_time)
24
+ checks.collect {|c|
25
+ {:check => c, :outages => check_presenter(c).outages(start_time, end_time)}
26
+ }
27
+ end
28
+
29
+ def unscheduled_maintenance(start_time, end_time)
30
+ checks.collect {|c|
31
+ {:check => c, :unscheduled_maintenance =>
32
+ check_presenter(c).unscheduled_maintenance(start_time, end_time)}
33
+ }
34
+ end
35
+
36
+ def scheduled_maintenance(start_time, end_time)
37
+ checks.collect {|c|
38
+ {:check => c, :scheduled_maintenance =>
39
+ check_presenter(c).scheduled_maintenance(start_time, end_time)}
40
+ }
41
+ end
42
+
43
+ def downtime(start_time, end_time)
44
+ checks.collect {|c|
45
+ {:check => c, :downtime =>
46
+ check_presenter(c).downtime(start_time, end_time)}
47
+ }
48
+ end
49
+
50
+ private
51
+
52
+ def checks
53
+ @check_list ||= @entity.check_list
54
+ end
55
+
56
+ def check_presenter(check)
57
+ entity_check = Flapjack::Data::EntityCheck.for_entity(@entity, check,
58
+ :redis => @redis)
59
+ presenter = Flapjack::API::EntityCheckPresenter.new(entity_check)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
3
  require 'ostruct'
5
- require 'optparse'
4
+ require 'optparse'
6
5
 
7
6
  class WorkerManagerOptions
8
7
  def self.parse(args)
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Flapjack
4
+
5
+ class Configuration
6
+
7
+ # TODO consolidate configuration code
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env ruby
2
+
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
+ require 'em-synchrony'
8
+ require 'redis/connection/synchrony'
9
+ require 'redis'
10
+ require 'em-resque'
11
+ require 'em-resque/worker'
12
+ require 'thin'
13
+
14
+ require 'flapjack/patches'
15
+
16
+ require 'flapjack/api'
17
+ require 'flapjack/daemonizing'
18
+ require 'flapjack/executive'
19
+ require 'flapjack/jabber'
20
+ require 'flapjack/pagerduty'
21
+ require 'flapjack/notification/email'
22
+ require 'flapjack/notification/sms'
23
+ require 'flapjack/web'
24
+
25
+ module Flapjack
26
+
27
+ class Coordinator
28
+
29
+ include Flapjack::Daemonizable
30
+
31
+ def initialize(config = {})
32
+ @config = config
33
+ @pikelets = []
34
+
35
+ @logger = Log4r::Logger.new("flapjack-coordinator")
36
+ @logger.add(Log4r::StdoutOutputter.new("flapjack-coordinator"))
37
+ @logger.add(Log4r::SyslogOutputter.new("flapjack-coordinator"))
38
+ end
39
+
40
+ def start(options = {})
41
+ # FIXME raise error if config not set, or empty
42
+
43
+ if options[:daemonize]
44
+ daemonize
45
+ else
46
+ setup
47
+ end
48
+ end
49
+
50
+ def after_daemonize
51
+ setup
52
+ end
53
+
54
+ def stop
55
+ shutdown
56
+ end
57
+
58
+ private
59
+
60
+ def setup
61
+
62
+ # FIXME: the following is currently repeated in flapjack-populator and
63
+ # flapjack-nagios-receiver - move to a method in a module and include it
64
+ redis_host = @config['redis']['host'] || '127.0.0.1'
65
+ redis_port = @config['redis']['port'] || 6379
66
+ redis_path = @config['redis']['path'] || nil
67
+ redis_db = @config['redis']['db'] || 0
68
+
69
+ if redis_path
70
+ @redis_options = { :db => redis_db, :path => redis_path }
71
+ else
72
+ @redis_options = { :db => redis_db, :host => redis_host, :port => redis_port }
73
+ end
74
+
75
+ EM.synchrony do
76
+
77
+ @logger.debug "config keys: #{@config.keys}"
78
+
79
+ pikelet_keys = ['executive', 'jabber_gateway', 'pagerduty_gateway',
80
+ 'email_notifier', 'sms_notifier', 'web', 'api']
81
+
82
+ @config.keys.each do |pikelet_type|
83
+ next unless pikelet_keys.include?(pikelet_type) &&
84
+ @config[pikelet_type].is_a?(Hash) &&
85
+ @config[pikelet_type]['enabled']
86
+ @logger.debug "coordinator is now initialising the #{pikelet_type} pikelet"
87
+ pikelet_cfg = @config[pikelet_type]
88
+
89
+ case pikelet_type
90
+ when 'executive', 'jabber_gateway', 'pagerduty_gateway'
91
+ build_pikelet(pikelet_type, pikelet_cfg)
92
+ when 'web', 'api'
93
+ build_thin_pikelet(pikelet_type, pikelet_cfg)
94
+ when 'email_notifier', 'sms_notifier'
95
+ build_resque_pikelet(pikelet_type, pikelet_cfg)
96
+ end
97
+ end
98
+
99
+ setup_signals
100
+ end
101
+
102
+ end
103
+
104
+ def setup_signals
105
+ trap('INT') { stop }
106
+ trap('TERM') { stop }
107
+ unless RUBY_PLATFORM =~ /mswin/
108
+ trap('QUIT') { stop }
109
+ # trap('HUP') { }
110
+ end
111
+ end
112
+
113
+ def build_pikelet(pikelet_type, pikelet_cfg)
114
+ pikelet_class = case pikelet_type
115
+ when 'executive'
116
+ Flapjack::Executive
117
+ when 'jabber_gateway'
118
+ Flapjack::Jabber
119
+ when 'pagerduty_gateway'
120
+ Flapjack::Pagerduty
121
+ end
122
+ return unless pikelet_class
123
+
124
+ f = Fiber.new {
125
+ begin
126
+ pikelet = pikelet_class.new
127
+ @pikelets.detect {|p| p[:type] == pikelet_type}[:instance] = pikelet
128
+ pikelet.bootstrap(:redis => @redis_options, :config => pikelet_cfg)
129
+ pikelet.main
130
+ rescue Exception => e
131
+ trace = e.backtrace.join("\n")
132
+ @logger.fatal "#{e.message}\n#{trace}"
133
+ stop
134
+ end
135
+ }
136
+ @pikelets << {:fiber => f, :type => pikelet_type}
137
+ f.resume
138
+ @logger.debug "new fiber created for #{pikelet_type}"
139
+ end
140
+
141
+ def build_thin_pikelet(pikelet_type, pikelet_cfg)
142
+ pikelet_class = case pikelet_type
143
+ when 'web'
144
+ Flapjack::Web
145
+ when 'api'
146
+ Flapjack::API
147
+ end
148
+ return unless pikelet_class
149
+
150
+ port = nil
151
+ if pikelet_cfg['port']
152
+ port = pikelet_cfg['port'].to_i
153
+ end
154
+
155
+ port = 3001 if (port.nil? || port <= 0 || port > 65535)
156
+
157
+ pikelet_class.class_variable_set('@@redis', build_redis_connection_pool)
158
+
159
+ Thin::Logging.silent = true
160
+
161
+ pikelet = Thin::Server.new('0.0.0.0', port, pikelet_class, :signals => false)
162
+ @pikelets << {:instance => pikelet, :type => pikelet_type}
163
+ pikelet.start
164
+ @logger.debug "new thin server instance started for #{pikelet_type}"
165
+ end
166
+
167
+ def build_resque_pikelet(pikelet_type, pikelet_cfg)
168
+ pikelet_class = case pikelet_type
169
+ when 'email_notifier'
170
+ Flapjack::Notification::Email
171
+ when 'sms_notifier'
172
+ Flapjack::Notification::Sms
173
+ end
174
+ return unless pikelet_class
175
+
176
+ # set up connection pooling, stop resque errors (ensure that it's only
177
+ # done once)
178
+ if (['email_notifier', 'sms_notifier'] & @pikelets.collect {|p| p[:type]}).empty?
179
+ ::Resque.redis = build_redis_connection_pool
180
+ ## NB: can override the default 'resque' namespace like this
181
+ #::Resque.redis.namespace = 'flapjack'
182
+ end
183
+
184
+ # See https://github.com/mikel/mail/blob/master/lib/mail/mail.rb#L53
185
+ # & https://github.com/mikel/mail/blob/master/spec/mail/configuration_spec.rb
186
+ # for details of configuring mail gem. defaults to SMTP, localhost, port 25
187
+
188
+ if pikelet_type.eql?('email_notifier')
189
+ smtp_config = {}
190
+
191
+ if pikelet_cfg['smtp_config']
192
+ smtp_config = pikelet_cfg['smtp_config'].keys.inject({}) do |ret,obj|
193
+ ret[obj.to_sym] = pikelet_cfg['smtp_config'][obj]
194
+ ret
195
+ end
196
+ end
197
+
198
+ Mail.defaults {
199
+ delivery_method :smtp, {:enable_starttls_auto => false}.merge(smtp_config)
200
+ }
201
+ end
202
+
203
+ pikelet_class.class_variable_set('@@config', pikelet_cfg)
204
+
205
+ f = Fiber.new {
206
+ begin
207
+ # TODO error if pikelet_cfg['queue'].nil?
208
+ pikelet = EM::Resque::Worker.new(pikelet_cfg['queue'])
209
+ @pikelets.detect {|p| p[:type] == pikelet_type}[:instance] = pikelet
210
+ # # Use these to debug the resque workers
211
+ # flapjack_rsq.verbose = true
212
+ #flapjack_rsq.very_verbose = true
213
+ pikelet.work(0.1)
214
+ rescue Exception => e
215
+ trace = e.backtrace.join("\n")
216
+ @logger.fatal "#{e.message}\n#{trace}"
217
+ stop
218
+ end
219
+ }
220
+ @pikelets << {:fiber => f, :type => pikelet_type}
221
+ f.resume
222
+ @logger.debug "new fiber created for #{pikelet_type}"
223
+ end
224
+
225
+ def build_redis_connection_pool(options = {})
226
+ EventMachine::Synchrony::ConnectionPool.new(:size => options[:size] || 5) do
227
+ ::Redis.new(@redis_options.merge(:driver => (options[:driver] || 'synchrony')))
228
+ end
229
+ end
230
+
231
+ # # TODO rewrite to be less spammy -- print only initial state and changes
232
+ # def health_check
233
+ # @pikelets.each do |pik|
234
+ # if pik[:instance].is_a?(Thin::Server)
235
+ # s = pik[:instance].backend.size
236
+ # @logger.debug "thin on port #{pik[:instance].port} - #{s} connections"
237
+ # elsif pik[:fiber]
238
+ # @logger.debug "#{pik[:type]}: #{pik[:fiber].alive? ? 'alive' : 'dead'}"
239
+ # end
240
+ # end
241
+ # end
242
+
243
+ def shutdown
244
+ @pikelets.each do |pik|
245
+ case pik[:instance]
246
+ when Flapjack::Executive, Flapjack::Jabber, Flapjack::Pagerduty
247
+ if pik[:fiber] && pik[:fiber].alive?
248
+ pik[:instance].stop
249
+ Fiber.new {
250
+ # this needs to use a separate Redis connection from the pikelet's
251
+ # one, as that's in the middle of its blpop
252
+ r = Redis.new(@redis_options.merge(:driver => 'synchrony'))
253
+ pik[:instance].add_shutdown_event(:redis => r)
254
+ r.quit
255
+ }.resume
256
+ end
257
+ when EM::Resque::Worker
258
+ if pik[:fiber] && pik[:fiber].alive?
259
+ # resque is polling, so we don't need a shutdown object
260
+ pik[:instance].shutdown
261
+ end
262
+ when Thin::Server # web, api
263
+ # drop from this side, as HTTP keepalive etc. means browsers
264
+ # keep connections alive for ages, and we'd be hanging around
265
+ # waiting for them to drop
266
+ pik[:instance].stop!
267
+ end
268
+ end
269
+
270
+ fibers = @pikelets.collect {|p| p[:fiber] }.compact
271
+ thin_pikelets = @pikelets.collect {|p| p[:instance]}.select {|i| i.is_a?(Thin::Server) }
272
+
273
+ Fiber.new {
274
+ loop do
275
+ # health_check
276
+ if fibers.any?(&:alive?) || thin_pikelets.any?{|tp| !tp.backend.empty? }
277
+ EM::Synchrony.sleep 0.25
278
+ else
279
+ EM.stop
280
+ break
281
+ end
282
+ end
283
+ }.resume
284
+ end
285
+
286
+ end
287
+
288
+ end
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copied from thin.
4
+
5
+ require 'etc'
6
+ require 'daemons' unless RUBY_PLATFORM =~ /mswin/
7
+
8
+ module Process
9
+ # Returns +true+ the process identified by +pid+ is running.
10
+ def running?(pid)
11
+ Process.getpgid(pid) != -1
12
+ rescue Errno::EPERM
13
+ true
14
+ rescue Errno::ESRCH
15
+ false
16
+ end
17
+ module_function :running?
18
+ end
19
+
20
+ module Flapjack
21
+ # Raised when the pid file already exist starting as a daemon.
22
+ class PidFileExist < RuntimeError; end
23
+
24
+ # Module included in classes that can be turned into a daemon.
25
+ # Handle stuff like:
26
+ # * storing the PID in a file
27
+ # * redirecting output to the log file
28
+ # * changing process privileges
29
+ # * killing the process gracefully
30
+ module Daemonizable
31
+ attr_accessor :pid_file, :log_file
32
+
33
+ def self.included(base)
34
+ base.extend ClassMethods
35
+ end
36
+
37
+ def pid
38
+ File.exist?(pid_file) ? open(pid_file).read.to_i : nil
39
+ end
40
+
41
+ # Turns the current script into a daemon process that detaches from the console.
42
+ def daemonize
43
+ raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if RUBY_PLATFORM =~ /mswin/
44
+ raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
45
+
46
+ remove_stale_pid_file
47
+
48
+ pwd = Dir.pwd # Current directory is changed during daemonization, so store it
49
+
50
+ # HACK we need to create the directory before daemonization to prevent a bug under 1.9
51
+ # ignoring all signals when the directory is created after daemonization.
52
+ FileUtils.mkdir_p File.dirname(@pid_file)
53
+ FileUtils.mkdir_p File.dirname(@log_file)
54
+
55
+ Daemonize.daemonize(File.expand_path(@log_file), 'flapjack server')
56
+
57
+ Dir.chdir(pwd)
58
+
59
+ write_pid_file
60
+
61
+ self.after_daemonize if self.respond_to? :after_daemonize
62
+
63
+ at_exit do
64
+ puts ">> Exiting!"
65
+ remove_pid_file
66
+ end
67
+ end
68
+
69
+ # Change privileges of the process
70
+ # to the specified user and group.
71
+ def change_privilege(user, group=user)
72
+ puts ">> Changing process privilege to #{user}:#{group}"
73
+
74
+ uid, gid = Process.euid, Process.egid
75
+ target_uid = Etc.getpwnam(user).uid
76
+ target_gid = Etc.getgrnam(group).gid
77
+
78
+ if uid != target_uid || gid != target_gid
79
+ # Change process ownership
80
+ Process.initgroups(user, target_gid)
81
+ Process::GID.change_privilege(target_gid)
82
+ Process::UID.change_privilege(target_uid)
83
+ end
84
+ rescue Errno::EPERM => e
85
+ puts "Couldn't change user and group to #{user}:#{group}: #{e}"
86
+ end
87
+
88
+ # Register a proc to be called to restart the server.
89
+ def on_restart(&block)
90
+ @on_restart = block
91
+ end
92
+
93
+ # Restart the server.
94
+ def restart
95
+ if @on_restart
96
+ puts '>> Restarting ...'
97
+ stop
98
+ remove_pid_file
99
+ @on_restart.call
100
+ exit!
101
+ end
102
+ end
103
+
104
+ module ClassMethods
105
+ # Send a QUIT or INT (if timeout is +0+) signal the process which
106
+ # PID is stored in +pid_file+.
107
+ # If the process is still running after +timeout+, KILL signal is
108
+ # sent.
109
+ def kill(pid_file, timeout=60)
110
+ if timeout == 0
111
+ send_signal('INT', pid_file, timeout)
112
+ else
113
+ send_signal('QUIT', pid_file, timeout)
114
+ end
115
+ end
116
+
117
+ # Restart the server by sending HUP signal.
118
+ def restart(pid_file)
119
+ send_signal('HUP', pid_file)
120
+ end
121
+
122
+ # Send a +signal+ to the process which PID is stored in +pid_file+.
123
+ def send_signal(signal, pid_file, timeout=60)
124
+ if pid = read_pid_file(pid_file)
125
+ puts "Sending #{signal} signal to process #{pid} ... "
126
+ Process.kill(signal, pid)
127
+ Timeout.timeout(timeout) do
128
+ sleep 0.1 while Process.running?(pid)
129
+ end
130
+ else
131
+ puts "Can't stop process, no PID found in #{pid_file}"
132
+ end
133
+ rescue Timeout::Error
134
+ puts "Timeout!"
135
+ force_kill pid_file
136
+ rescue Interrupt
137
+ force_kill pid_file
138
+ rescue Errno::ESRCH # No such process
139
+ puts "process not found!"
140
+ force_kill pid_file
141
+ end
142
+
143
+ def force_kill(pid_file)
144
+ if pid = read_pid_file(pid_file)
145
+ puts "Sending KILL signal to process #{pid} ... "
146
+ Process.kill("KILL", pid)
147
+ File.delete(pid_file) if File.exist?(pid_file)
148
+ else
149
+ puts "Can't stop process, no PID found in #{pid_file}"
150
+ end
151
+ end
152
+
153
+ def read_pid_file(file)
154
+ if File.file?(file) && pid = File.read(file)
155
+ pid.to_i
156
+ else
157
+ nil
158
+ end
159
+ end
160
+ end
161
+
162
+ protected
163
+ def remove_pid_file
164
+ File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
165
+ end
166
+
167
+ def write_pid_file
168
+ puts ">> Writing PID to #{@pid_file}"
169
+ open(@pid_file,"w") { |f| f.write(Process.pid) }
170
+ File.chmod(0644, @pid_file)
171
+ end
172
+
173
+ # If PID file is stale, remove it.
174
+ def remove_stale_pid_file
175
+ if File.exist?(@pid_file)
176
+ if pid && Process.running?(pid)
177
+ raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
178
+ "Stop the process or delete #{@pid_file}."
179
+ else
180
+ puts ">> Deleting stale PID file #{@pid_file}"
181
+ remove_pid_file
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end