aws-codedeploy-agent 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGES.md +3 -0
- data/Gemfile +13 -0
- data/LICENSE +177 -0
- data/NOTICE +2 -0
- data/README.md +16 -0
- data/aws-codedeploy-agent.gemspec +39 -0
- data/bin/codedeploy-agent +78 -0
- data/bin/codedeploy-install +15 -0
- data/bin/codedeploy-uninstall +13 -0
- data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
- data/conf/codedeployagent.yml +9 -0
- data/init.d/codedeploy-agent +61 -0
- data/lib/core_ext.rb +71 -0
- data/lib/instance_agent.rb +35 -0
- data/lib/instance_agent/agent/base.rb +34 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
- data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
- data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
- data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
- data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
- data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
- data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
- data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
- data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
- data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
- data/lib/instance_agent/config.rb +43 -0
- data/lib/instance_agent/log.rb +3 -0
- data/lib/instance_agent/platform.rb +17 -0
- data/lib/instance_agent/platform/linux_util.rb +57 -0
- data/lib/instance_agent/runner/child.rb +57 -0
- data/lib/instance_agent/runner/master.rb +103 -0
- data/lib/instance_metadata.rb +47 -0
- data/test/certificate_helper.rb +120 -0
- data/test/helpers/instance_agent_helper.rb +25 -0
- data/test/instance_agent/agent/base_test.rb +49 -0
- data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
- data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
- data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
- data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
- data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
- data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
- data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
- data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
- data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
- data/test/instance_agent/config_test.rb +64 -0
- data/test/instance_agent/runner/child_test.rb +87 -0
- data/test/instance_metadata_test.rb +97 -0
- data/test/test_helper.rb +16 -0
- data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
- data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
- data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
- data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
- data/vendor/gems/process_manager/README.md +1 -0
- data/vendor/gems/process_manager/lib/blank.rb +153 -0
- data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
- data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
- data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
- data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
- data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
- data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
- data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
- data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
- data/vendor/specifications/builder-3.2.2.gemspec +29 -0
- data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
- data/vendor/specifications/gli-2.5.6.gemspec +51 -0
- data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
- data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
- data/vendor/specifications/logging-1.8.1.gemspec +44 -0
- data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
- data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
- data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
- data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
- data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
- metadata +377 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module ProcessManager
|
3
|
+
class Config
|
4
|
+
|
5
|
+
def self.init
|
6
|
+
@config = Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.config(options = {})
|
10
|
+
init unless @config
|
11
|
+
@config.config(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.validate_config
|
15
|
+
init unless @config
|
16
|
+
@config.validate
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load_config
|
20
|
+
if File.exists?(config[:config_file]) && File.readable?(config[:config_file])
|
21
|
+
file_config = YAML.load(File.read(config[:config_file])).symbolize_keys
|
22
|
+
config.update(file_config)
|
23
|
+
config_loaded_callbacks.each{|c| c.call}
|
24
|
+
else
|
25
|
+
raise "The config file #{config[:config_file]} does not exist or is not readable"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.on_config_load(&block)
|
30
|
+
@@_config_callbacks ||= []
|
31
|
+
@@_config_callbacks << block
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.config_loaded_callbacks
|
36
|
+
@@_config_callbacks ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@config = {
|
41
|
+
:program_name => 'process_manager',
|
42
|
+
:max_runs_per_worker => 0, # unlimited
|
43
|
+
:children => 4,
|
44
|
+
:log_dir => '/tmp',
|
45
|
+
:pid_dir => '/tmp',
|
46
|
+
:verbose => false,
|
47
|
+
:wait_after_throttle_error => 60, # wait time in seconds after a we got a throttling exception from SWF
|
48
|
+
:wait_between_runs => 5, # wait time in seconds after a run so that we don't run into throttling exceptions
|
49
|
+
:wait_after_connection_problem => 5, # wait time in seconds after a connection problem as we don't want to build a fork-bomb
|
50
|
+
:wait_between_spawning_children => 10, # wait time in seconds after spawning a child so that we don't overhelm SWF with our requests
|
51
|
+
:user => nil,
|
52
|
+
:group => nil,
|
53
|
+
|
54
|
+
# global config file to read
|
55
|
+
:config_file => nil
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def config(options = {})
|
60
|
+
@config.update(options) unless options.nil? || options.empty?
|
61
|
+
@config
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate
|
65
|
+
errors = []
|
66
|
+
|
67
|
+
errors << "Invalid max_runs_per_worker #{config[:max_runs_per_worker].inspect}" unless config[:max_runs_per_worker].to_s.match(/\d+/) && config[:max_runs_per_worker].to_i >= 0
|
68
|
+
config[:max_runs_per_worker] = config[:max_runs_per_worker].to_i
|
69
|
+
errors << "Invalid number of children #{config[:children].inspect}" unless config[:children].to_s.match(/\d+/) && config[:children].to_i > 0
|
70
|
+
config[:children] = config[:children].to_i
|
71
|
+
|
72
|
+
normalize_log_and_pid_dir
|
73
|
+
validate_log_and_pid_dir(errors)
|
74
|
+
validate_user(errors)
|
75
|
+
errors
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_log_and_pid_dir(errors)
|
79
|
+
FileUtils.mkdir_p(ProcessManager::Config.config[:log_dir]) unless File.exists?(ProcessManager::Config.config[:log_dir])
|
80
|
+
FileUtils.mkdir_p(ProcessManager::Config.config[:pid_dir]) unless File.exists?(ProcessManager::Config.config[:pid_dir])
|
81
|
+
errors << "Please make sure the path of the log directory exists and is writable: #{config[:log_dir].inspect}" unless file_writable?(config[:log_dir]) && File.directory?(config[:log_dir])
|
82
|
+
errors << "Please make sure the path of the PID directory exists and is writable: #{config[:pid_dir].inspect}" unless file_writable?(config[:pid_dir]) && File.directory?(config[:pid_dir])
|
83
|
+
errors
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_user(errors)
|
87
|
+
if config[:user].present?
|
88
|
+
errors << "The system user does not exist: #{config[:user].inspect}" unless (Etc.getpwnam(config[:user]).uid rescue false)
|
89
|
+
end
|
90
|
+
errors
|
91
|
+
end
|
92
|
+
|
93
|
+
def normalize_log_and_pid_dir
|
94
|
+
if config[:pid_dir]
|
95
|
+
config[:pid_dir] = File.expand_path(config[:pid_dir])
|
96
|
+
end
|
97
|
+
if config[:log_dir]
|
98
|
+
config[:log_dir] = File.expand_path(config[:log_dir])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def file_writable?(path)
|
103
|
+
return false unless path.present?
|
104
|
+
if File.exists?(path)
|
105
|
+
File.writable?(path)
|
106
|
+
else
|
107
|
+
File.writable?(File.dirname(path))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'logging'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module ProcessManager
|
6
|
+
class Log
|
7
|
+
NORMAL_SEVERITIES = %W{debug info warn unknown}
|
8
|
+
ERROR_SEVERITIES = %W{error fatal}
|
9
|
+
SEVERITIES = NORMAL_SEVERITIES + ERROR_SEVERITIES
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :logger
|
13
|
+
end
|
14
|
+
|
15
|
+
class Logger
|
16
|
+
attr_accessor :logger
|
17
|
+
|
18
|
+
# Initializes a logger that will roll log files every hour and keeps a week's worth of logs
|
19
|
+
# in the disk. The log layout represents,
|
20
|
+
# "ISO8601_format_date log_level [program_name(pid)]: actual_log_message"
|
21
|
+
# Note: Rolling file appender only works with regular files
|
22
|
+
|
23
|
+
def initialize(log_device)
|
24
|
+
@logger = Logging.logger[ProcessManager::Config.config[:program_name]]
|
25
|
+
@logger.add_appenders(
|
26
|
+
Logging.appenders.rolling_file('rolling_file_appender',
|
27
|
+
:filename => log_device,
|
28
|
+
:age => 'daily',
|
29
|
+
:keep => 7,
|
30
|
+
:layout => Logging.layouts.pattern(:pattern => '%d %-5l [%c(%p)]: %m\n')
|
31
|
+
)
|
32
|
+
)
|
33
|
+
if ProcessManager::Config.config[:verbose]
|
34
|
+
self.level = 'debug'
|
35
|
+
else
|
36
|
+
self.level = 'info'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def level=(level)
|
41
|
+
if level.is_a?(Fixnum)
|
42
|
+
@logger.level = level
|
43
|
+
else
|
44
|
+
@logger.level = ::Logger.const_get(level.to_s.upcase)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
NORMAL_SEVERITIES.each do |level|
|
49
|
+
log_level = ::Logger.const_get(level.upcase)
|
50
|
+
define_method(level) do |message|
|
51
|
+
raise "No logger available" unless @logger
|
52
|
+
@logger.add(log_level, message)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ERROR_SEVERITIES.each do |level|
|
57
|
+
log_level = ::Logger.const_get(level.upcase)
|
58
|
+
define_method(level) do |message|
|
59
|
+
@logger.add(log_level, message)
|
60
|
+
ProcessManager.on_error_callbacks.each do |callback|
|
61
|
+
begin
|
62
|
+
callback.call(message)
|
63
|
+
rescue Exception
|
64
|
+
# error callbacks shouldn't break the main flow
|
65
|
+
end
|
66
|
+
end if level == 'error'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.[](logger_name)
|
72
|
+
logger_name = logger_name.to_sym
|
73
|
+
@logger_collection ||= {}
|
74
|
+
@logger_collection[logger_name] ||= ProcessManager::Log::Logger.new(log_device(logger_name))
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.log_device(logger_name)
|
78
|
+
if logger_name.is_a?(String) || logger_name.is_a?(Symbol)
|
79
|
+
raise 'Please init ProcessManager::Log with a base log file!' unless @base_log_file
|
80
|
+
@base_log_file.gsub(/\.log/, ".#{logger_name.to_s.demodulize}.log")
|
81
|
+
else # IO?
|
82
|
+
logger_name
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.init(log_device)
|
87
|
+
@base_log_file = log_device
|
88
|
+
if @logger.nil? || ((@logger.logger.logdev.dev.path != log_device) rescue true)
|
89
|
+
@logger = ::ProcessManager::Log::Logger.new(log_device)
|
90
|
+
end
|
91
|
+
@logger
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.level=(level)
|
95
|
+
@logger.level = ::Logger.const_get(level.to_s.upcase)
|
96
|
+
end
|
97
|
+
|
98
|
+
(NORMAL_SEVERITIES + ERROR_SEVERITIES).each do |level|
|
99
|
+
singleton_class.instance_eval do
|
100
|
+
define_method(level) do |message|
|
101
|
+
raise "No logger available" unless @logger
|
102
|
+
@logger.send(level, message)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'simple_pid'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'blank'
|
5
|
+
|
6
|
+
module ProcessManager
|
7
|
+
module Daemon
|
8
|
+
class Master
|
9
|
+
|
10
|
+
attr_accessor :children
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@children = {}
|
14
|
+
ProcessManager.set_program_name(description)
|
15
|
+
ensure_validate_configuration
|
16
|
+
dropped_privileges = drop_privileges
|
17
|
+
ProcessManager::Log.init(log_file)
|
18
|
+
if dropped_privileges
|
19
|
+
ProcessManager::Log.info("Dropped privileges to group: #{Etc.getgrgid(Process.gid).name} (gid = #{Process.gid})")
|
20
|
+
ProcessManager::Log.info("Dropped privileges to user: #{Etc.getpwuid(Process.uid).name} (uid = #{Process.uid})")
|
21
|
+
end
|
22
|
+
after_initialize
|
23
|
+
end
|
24
|
+
|
25
|
+
# please override
|
26
|
+
def after_initialize
|
27
|
+
# hook
|
28
|
+
end
|
29
|
+
|
30
|
+
# please override
|
31
|
+
def validate_ssl_config
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.start
|
35
|
+
pid = fork do
|
36
|
+
new.start
|
37
|
+
end
|
38
|
+
Process.detach pid
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.stop
|
42
|
+
new.stop
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.restart
|
46
|
+
stop
|
47
|
+
sleep 1
|
48
|
+
start
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.status
|
52
|
+
if pid = find_pid
|
53
|
+
if ProcessManager::process_running?(pid)
|
54
|
+
pid
|
55
|
+
else
|
56
|
+
clean_stale_pid
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
else
|
60
|
+
# does not run
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.pid_file
|
66
|
+
File.join(ProcessManager::Config.config[:pid_dir], "#{ProcessManager::Config.config[:program_name]}.#{self.pid_description}.pid")
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.pid_lock_file
|
70
|
+
File.join(ProcessManager::Config.config[:pid_dir], "#{ProcessManager::Config.config[:program_name]}.pid.lock")
|
71
|
+
end
|
72
|
+
|
73
|
+
def pid_lock_file
|
74
|
+
self.class.pid_lock_file
|
75
|
+
end
|
76
|
+
|
77
|
+
def pid_file
|
78
|
+
self.class.pid_file
|
79
|
+
end
|
80
|
+
|
81
|
+
# please override
|
82
|
+
def self.pid_description
|
83
|
+
"ProcessManager"
|
84
|
+
end
|
85
|
+
|
86
|
+
def pid_description
|
87
|
+
self.class.pid_description
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.log_file
|
91
|
+
File.join(ProcessManager::Config.config[:log_dir], "#{ProcessManager::Config.config[:program_name]}.#{pid_description}.log")
|
92
|
+
end
|
93
|
+
|
94
|
+
def log_file
|
95
|
+
self.class.log_file
|
96
|
+
end
|
97
|
+
|
98
|
+
def drop_privileges
|
99
|
+
|
100
|
+
runas_user = ProcessManager::Config.config[:user]
|
101
|
+
return false if runas_user.blank?
|
102
|
+
|
103
|
+
if runas_user == Etc.getpwuid(Process.uid).name
|
104
|
+
return false
|
105
|
+
elsif Process.uid != 0
|
106
|
+
raise "Can't drop privileges as unprivileged user. Please run this command as a privileged user."
|
107
|
+
end
|
108
|
+
|
109
|
+
if runas_user.present?
|
110
|
+
uid = Etc.getpwnam(runas_user).uid
|
111
|
+
if (group = ProcessManager::Config.config[:group]) && group.present?
|
112
|
+
gid = Etc.getgrnam(group).gid
|
113
|
+
else
|
114
|
+
gid = Etc.getpwuid(uid).gid
|
115
|
+
end
|
116
|
+
Process.initgroups(runas_user, gid)
|
117
|
+
Process::GID.change_privilege(gid)
|
118
|
+
Process::UID.change_privilege(uid)
|
119
|
+
true
|
120
|
+
end
|
121
|
+
false
|
122
|
+
rescue Exception => e
|
123
|
+
$stderr.puts "Failed to drop privileges: #{e.class} - #{e.message} - #{e.backtrace.join("\n")}"
|
124
|
+
exit 1
|
125
|
+
end
|
126
|
+
|
127
|
+
def start
|
128
|
+
handle_pid_file
|
129
|
+
validate_ssl_config
|
130
|
+
trap_signals
|
131
|
+
|
132
|
+
spawn_children
|
133
|
+
puts "Started #{description} with #{ProcessManager::Config.config[:children]} children"
|
134
|
+
ProcessManager::Log.info("Started #{description} with #{ProcessManager::Config.config[:children]} children")
|
135
|
+
|
136
|
+
loop do
|
137
|
+
# master does nothing apart from replacing dead children
|
138
|
+
# and forwarding signals
|
139
|
+
sleep 1
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def stop
|
144
|
+
if (pid = self.class.find_pid)
|
145
|
+
puts "Stopping #{description(pid)}"
|
146
|
+
ProcessManager::Log.info("Stopping #{description(pid)}")
|
147
|
+
begin
|
148
|
+
Process.kill('TERM', pid)
|
149
|
+
rescue Errno::ESRCH
|
150
|
+
end
|
151
|
+
else
|
152
|
+
puts "Nothing running that could be stopped"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def handle_pid_file
|
157
|
+
@file_lock ||= File.open(pid_lock_file, File::RDWR|File::CREAT, 0644)
|
158
|
+
lock_aquired = @file_lock.flock(File::LOCK_EX|File::LOCK_NB)
|
159
|
+
|
160
|
+
if lock_aquired == false
|
161
|
+
ProcessManager::Log.info("Could not aquire lock on #{pid_lock_file} - aborting start!")
|
162
|
+
self.class.abort
|
163
|
+
|
164
|
+
elsif File.exists?(pid_file)
|
165
|
+
pid = self.class.find_pid
|
166
|
+
if ProcessManager.process_running?(pid)
|
167
|
+
puts "Pidfile #{pid_file} exists and process #{pid} is running - aborting start!"
|
168
|
+
ProcessManager::Log.info("Pidfile #{pid_file} exists and process #{pid} is running - aborting start!")
|
169
|
+
@file_lock.close
|
170
|
+
self.class.abort
|
171
|
+
else
|
172
|
+
self.class.clean_stale_pid
|
173
|
+
end
|
174
|
+
end
|
175
|
+
::SimplePid.drop(pid_file)
|
176
|
+
end
|
177
|
+
|
178
|
+
def spawn_children
|
179
|
+
ProcessManager::Config.config[:children].times do |i|
|
180
|
+
spawn_child(i)
|
181
|
+
sleep ProcessManager::Config.config[:wait_between_spawning_children].to_i
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# spawn a new child and pass down out PID so that it can check if we are alive
|
186
|
+
def spawn_child(index)
|
187
|
+
master_pid = $$ # need to store in order to pass down to child
|
188
|
+
child_pid = fork do
|
189
|
+
child_class.new(index, master_pid).start
|
190
|
+
end
|
191
|
+
children[index] = child_pid
|
192
|
+
ProcessManager::Log.info "#{description}: Spawned child #{index + 1}/#{ProcessManager::Config.config[:children]}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def trap_signals
|
196
|
+
# The QUIT & INT signals triggers a graceful shutdown.
|
197
|
+
# The master shuts down immediately and forwards the signal to each child
|
198
|
+
[:INT, :QUIT, :TERM].each do |sig|
|
199
|
+
trap(sig) do
|
200
|
+
ProcessManager::Log.info "#{description}: Received #{sig} - stopping children and shutting down"
|
201
|
+
kill_children(sig)
|
202
|
+
cleanup_and_exit
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
trap(:CHLD) do
|
207
|
+
handle_chld
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def cleanup_and_exit
|
212
|
+
SimplePid.cleanup!(pid_file)
|
213
|
+
@file_lock.close
|
214
|
+
exit
|
215
|
+
end
|
216
|
+
|
217
|
+
def handle_chld
|
218
|
+
if child = reap_child
|
219
|
+
ProcessManager::Log.info "#{description}: Received CHLD - cleaning dead child process"
|
220
|
+
cleanup_dead_child(child)
|
221
|
+
else
|
222
|
+
ProcessManager::Log.debug "#{description}: Received CHLD - ignoring as it looks like a child of a child"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def reap_child
|
227
|
+
dead_child = nil
|
228
|
+
begin
|
229
|
+
dead_child = Process.wait
|
230
|
+
rescue Errno::ECHILD
|
231
|
+
end
|
232
|
+
dead_child
|
233
|
+
end
|
234
|
+
|
235
|
+
def cleanup_dead_child(dead_child)
|
236
|
+
ProcessManager::Log.info "#{description}: been told to replace child #{dead_child.inspect}"
|
237
|
+
# delete given child
|
238
|
+
if index = children.key(dead_child)
|
239
|
+
children.delete(index)
|
240
|
+
end
|
241
|
+
|
242
|
+
# check all other children
|
243
|
+
children.each do |child_index, child_pid|
|
244
|
+
begin
|
245
|
+
dead_child = Process.waitpid(child_pid, Process::WNOHANG)
|
246
|
+
if index = children.key(dead_child)
|
247
|
+
children.delete(index)
|
248
|
+
end
|
249
|
+
rescue Errno::ECHILD
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
replace_terminated_children
|
254
|
+
end
|
255
|
+
|
256
|
+
# make sure we have again as many child we need
|
257
|
+
def replace_terminated_children
|
258
|
+
missing_children = ProcessManager::Config.config[:children] - children.values.size
|
259
|
+
if missing_children > 0
|
260
|
+
ProcessManager::Log.info "#{description}: not enough child processes running - missing at least #{missing_children} - respawning"
|
261
|
+
0.upto(ProcessManager::Config.config[:children] - 1).each do |i|
|
262
|
+
if children.has_key?(i)
|
263
|
+
ProcessManager::Log.debug "#{description}: child #{i+1}/#{ProcessManager::Config.config[:children]} is still there"
|
264
|
+
else
|
265
|
+
spawn_child(i)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
else
|
269
|
+
ProcessManager::Log.debug "#{description}: no need to replace child processes"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def kill_children(sig)
|
274
|
+
children.each do |index, child_pid|
|
275
|
+
begin
|
276
|
+
Process.kill(sig, child_pid)
|
277
|
+
rescue Errno::ESRCH
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def ensure_validate_configuration
|
283
|
+
if (errors = ProcessManager::Config.validate_config)
|
284
|
+
errors.each{|error| puts error}
|
285
|
+
end
|
286
|
+
cleanup_and_exit unless errors.empty?
|
287
|
+
end
|
288
|
+
|
289
|
+
def self.abort
|
290
|
+
Kernel.abort
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.clean_stale_pid
|
294
|
+
puts "Pidfile #{pid_file} present but no matching process running - cleaning up"
|
295
|
+
::FileUtils.rm(pid_file)
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.find_pid
|
299
|
+
File.read(pid_file).chomp.to_i rescue nil
|
300
|
+
end
|
301
|
+
|
302
|
+
def description(pid = $$)
|
303
|
+
self.class.description(pid)
|
304
|
+
end
|
305
|
+
|
306
|
+
def child_class
|
307
|
+
self.class.child_class
|
308
|
+
end
|
309
|
+
|
310
|
+
# please override
|
311
|
+
def self.description(pid = $$)
|
312
|
+
"master #{pid}"
|
313
|
+
end
|
314
|
+
|
315
|
+
# please override
|
316
|
+
def self.child_class
|
317
|
+
::ProcessManager::Daemon::Child
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|