flapjack 0.5.5 → 0.6.23

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 (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