aws-codedeploy-agent 0.0.1
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 +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
|