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.
- data/.gitignore +10 -0
- data/.rbenv-version +1 -0
- data/.rspec +10 -0
- data/Gemfile +18 -0
- data/Guardfile +14 -0
- data/README.md +152 -173
- data/Rakefile +53 -150
- data/bin/flapjack +72 -0
- data/bin/flapjack-nagios-receiver +111 -0
- data/bin/flapjack-nagios-receiver-control +15 -0
- data/bin/flapjack-netsaint-parser +0 -2
- data/bin/flapjack-populator +133 -16
- data/bin/install-flapjack-systemwide +2 -2
- data/config.ru +11 -0
- data/dist/etc/init.d/flapjack +46 -0
- data/dist/etc/init.d/flapjack-nagios-receiver +36 -0
- data/doc/GLOSSARY.md +19 -0
- data/etc/flapjack_config.yaml.example +90 -0
- data/features/events.feature +132 -0
- data/features/notifications.feature +57 -0
- data/features/packaging-lintian.feature +5 -3
- data/features/steps/events_steps.rb +164 -0
- data/features/steps/flapjack-importer_steps.rb +2 -5
- data/features/steps/flapjack-worker_steps.rb +13 -6
- data/features/steps/notifications_steps.rb +178 -0
- data/features/steps/packaging-lintian_steps.rb +14 -0
- data/features/steps/time_travel_steps.rb +34 -0
- data/features/support/env.rb +63 -36
- data/flapjack.gemspec +35 -186
- data/lib/flapjack.rb +2 -0
- data/lib/flapjack/api.rb +274 -0
- data/lib/flapjack/api/entity_check_presenter.rb +184 -0
- data/lib/flapjack/api/entity_presenter.rb +66 -0
- data/lib/flapjack/cli/worker_manager.rb +1 -2
- data/lib/flapjack/configuration.rb +11 -0
- data/lib/flapjack/coordinator.rb +288 -0
- data/lib/flapjack/daemonizing.rb +186 -0
- data/lib/flapjack/data/contact.rb +45 -0
- data/lib/flapjack/data/entity.rb +89 -0
- data/lib/flapjack/data/entity_check.rb +396 -0
- data/lib/flapjack/data/event.rb +144 -0
- data/lib/flapjack/data/notification.rb +13 -0
- data/lib/flapjack/executive.rb +289 -0
- data/lib/flapjack/filters/acknowledgement.rb +39 -0
- data/lib/flapjack/filters/{any_parents_failed.rb → base.rb} +6 -4
- data/lib/flapjack/filters/delays.rb +53 -0
- data/lib/flapjack/filters/detect_mass_client_failures.rb +44 -0
- data/lib/flapjack/filters/ok.rb +25 -5
- data/lib/flapjack/filters/scheduled_maintenance.rb +17 -0
- data/lib/flapjack/filters/unscheduled_maintenance.rb +17 -0
- data/lib/flapjack/jabber.rb +294 -0
- data/lib/flapjack/notification/common.rb +23 -0
- data/lib/flapjack/notification/email.rb +107 -0
- data/lib/flapjack/notification/email/alert.html.haml +48 -0
- data/lib/flapjack/notification/email/alert.text.erb +14 -0
- data/lib/flapjack/notification/sms.rb +42 -0
- data/lib/flapjack/notification/sms/messagenet.rb +49 -0
- data/lib/flapjack/notifier_engine.rb +4 -4
- data/lib/flapjack/notifiers/mailer/mailer.rb +6 -7
- data/lib/flapjack/notifiers/xmpp/xmpp.rb +12 -12
- data/lib/flapjack/pagerduty.rb +230 -0
- data/lib/flapjack/patches.rb +108 -19
- data/lib/flapjack/persistence/data_mapper/models/check.rb +5 -3
- data/lib/flapjack/persistence/data_mapper/models/check_template.rb +2 -0
- data/lib/flapjack/persistence/data_mapper/models/event.rb +2 -0
- data/lib/flapjack/persistence/data_mapper/models/node.rb +3 -1
- data/lib/flapjack/persistence/data_mapper/models/related_check.rb +3 -1
- data/lib/flapjack/pikelet.rb +56 -0
- data/lib/flapjack/transports/beanstalkd.rb +1 -1
- data/lib/flapjack/transports/result.rb +6 -6
- data/lib/flapjack/utility.rb +46 -0
- data/lib/flapjack/version.rb +5 -0
- data/lib/flapjack/web.rb +198 -0
- data/lib/flapjack/web/views/acknowledge.haml +55 -0
- data/lib/flapjack/web/views/check.haml +162 -0
- data/lib/flapjack/web/views/index.haml +92 -0
- data/lib/flapjack/web/views/self_stats.haml +56 -0
- data/lib/flapjack/{applications/worker.rb → worker/application.rb} +0 -0
- data/lib/flapjack/worker/cli.rb +49 -0
- data/{spec → spec.old}/check_sandbox/echo +0 -0
- data/{spec → spec.old}/check_sandbox/sandboxed_check +0 -0
- data/{spec → spec.old}/configs/flapjack-notifier-couchdb.ini +0 -0
- data/{spec → spec.old}/configs/flapjack-notifier.ini +0 -0
- data/{spec → spec.old}/configs/recipients.ini +0 -0
- data/{spec → spec.old}/helpers.rb +0 -0
- data/{spec → spec.old}/inifile_spec.rb +0 -0
- data/{spec → spec.old}/mock-notifiers/mock/init.rb +0 -0
- data/{spec → spec.old}/mock-notifiers/mock/mock.rb +0 -0
- data/{spec → spec.old}/notifier-directories/spoons/testmailer/init.rb +0 -0
- data/{spec → spec.old}/notifier_application_spec.rb +0 -0
- data/{spec → spec.old}/notifier_filters_spec.rb +0 -0
- data/{spec → spec.old}/notifier_options_multiplexer_spec.rb +0 -0
- data/{spec → spec.old}/notifier_options_spec.rb +0 -0
- data/{spec → spec.old}/notifier_spec.rb +0 -0
- data/{spec → spec.old}/notifiers/mailer_spec.rb +0 -0
- data/{spec → spec.old}/notifiers/xmpp_spec.rb +0 -0
- data/{spec → spec.old}/persistence/datamapper_spec.rb +0 -0
- data/{spec → spec.old}/persistence/mock_persistence_backend.rb +0 -0
- data/{spec → spec.old}/simple.ini +0 -0
- data/{spec → spec.old}/spec.opts +0 -0
- data/{spec → spec.old}/test-filters/blocker.rb +0 -0
- data/{spec → spec.old}/test-filters/mock.rb +0 -0
- data/{spec → spec.old}/transports/beanstalkd_spec.rb +0 -0
- data/{spec → spec.old}/transports/mock_transport.rb +0 -0
- data/{spec → spec.old}/worker_application_spec.rb +0 -0
- data/{spec → spec.old}/worker_options_spec.rb +0 -0
- data/spec/lib/flapjack/api/entity_check_presenter_spec.rb +117 -0
- data/spec/lib/flapjack/api/entity_presenter_spec.rb +92 -0
- data/spec/lib/flapjack/api_spec.rb +170 -0
- data/spec/lib/flapjack/coordinator_spec.rb +16 -0
- data/spec/lib/flapjack/data/entity_check_spec.rb +398 -0
- data/spec/lib/flapjack/data/entity_spec.rb +71 -0
- data/spec/lib/flapjack/data/event_spec.rb +6 -0
- data/spec/lib/flapjack/executive_spec.rb +59 -0
- data/spec/lib/flapjack/filters/acknowledgement_spec.rb +6 -0
- data/spec/lib/flapjack/filters/delays_spec.rb +6 -0
- data/spec/lib/flapjack/filters/detect_mass_client_failures_spec.rb +6 -0
- data/spec/lib/flapjack/filters/ok_spec.rb +6 -0
- data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +6 -0
- data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +6 -0
- data/spec/lib/flapjack/jabber_spec.rb +150 -0
- data/spec/lib/flapjack/notification/email_spec.rb +6 -0
- data/spec/lib/flapjack/notification/sms_spec.rb +6 -0
- data/spec/lib/flapjack/pikelet_spec.rb +28 -0
- data/spec/lib/flapjack/web_spec.rb +188 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/profile_all_formatter.rb +44 -0
- data/spec/support/uncolored_doc_formatter.rb +9 -0
- data/tasks/events.rake +85 -0
- data/tmp/acknowledge.rb +14 -0
- data/tmp/create_config_yaml.rb +16 -0
- data/tmp/create_events_failure.rb +33 -0
- data/tmp/create_events_ok.rb +33 -0
- data/tmp/create_events_ok_fail_ack_ok.rb +54 -0
- data/tmp/create_events_ok_failure.rb +40 -0
- data/tmp/create_events_ok_failure_ack.rb +54 -0
- data/tmp/dummy_entities.json +1 -0
- data/tmp/generate_nagios_test_hosts.rb +16 -0
- data/tmp/parse_config_yaml.rb +7 -0
- data/tmp/redis_delete_all_keys.rb +11 -0
- data/tmp/test_entities.json +1 -0
- metadata +482 -221
- data/TODO.md +0 -36
- data/VERSION +0 -1
- data/bin/flapjack-benchmark +0 -50
- data/bin/flapjack-notifier +0 -21
- data/bin/flapjack-notifier-manager +0 -43
- data/bin/flapjack-stats +0 -27
- data/bin/flapjack-worker +0 -13
- data/bin/flapjack-worker-manager +0 -35
- data/dist/etc/init.d/flapjack-notifier +0 -47
- data/dist/etc/init.d/flapjack-workers +0 -44
- data/features/flapjack-notifier-manager.feature +0 -19
- data/features/flapjack-worker-manager.feature +0 -27
- data/features/flapjack-worker.feature +0 -27
- data/features/netsaint-config-converter.feature +0 -126
- data/features/persistence/couch.feature +0 -105
- data/features/persistence/sqlite3.feature +0 -105
- data/features/persistence/steps/couch_steps.rb +0 -25
- data/features/persistence/steps/generic_steps.rb +0 -102
- data/features/persistence/steps/sqlite3_steps.rb +0 -13
- data/features/steps/flapjack-notifier-manager_steps.rb +0 -24
- data/features/steps/flapjack-worker-manager_steps.rb +0 -48
- data/lib/flapjack/applications/notifier.rb +0 -222
- data/lib/flapjack/cli/notifier.rb +0 -108
- data/lib/flapjack/cli/notifier_manager.rb +0 -86
- 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
|
|
@@ -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
|