procodile 1.0.3 → 1.0.4
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.
- checksums.yaml +4 -4
- data/bin/procodile +1 -1
- data/lib/procodile/cli.rb +139 -88
- data/lib/procodile/config.rb +17 -5
- data/lib/procodile/control_server.rb +7 -0
- data/lib/procodile/control_session.rb +3 -4
- data/lib/procodile/instance.rb +102 -61
- data/lib/procodile/process.rb +61 -3
- data/lib/procodile/status_cli_output.rb +6 -7
- data/lib/procodile/supervisor.rb +102 -73
- data/lib/procodile/tcp_proxy.rb +112 -0
- data/lib/procodile/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6afcf104b9bc4268bc21c634075b7cd4c8ef8d33
|
4
|
+
data.tar.gz: a46fd5857741a36d7d2584a4d4cea4ed07c5dbfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85ea38210c9cf42dde381b7ed2a8966578047356cfdd6deed42942ea49f6880a8fb32317b00d98586054925ab7d8acbcc716577db27b1a8796aaa506dd195a7a
|
7
|
+
data.tar.gz: d43970fcd524920fae6d7980546de10c9f0980354d8e80cf33d2569d8d90253ce1cafd7b85d98db865aac982aaf9ca61123492d0f774f798d7ad0d16024cc3fb
|
data/bin/procodile
CHANGED
data/lib/procodile/cli.rb
CHANGED
@@ -45,6 +45,12 @@ module Procodile
|
|
45
45
|
# Help
|
46
46
|
#
|
47
47
|
|
48
|
+
command def proxy
|
49
|
+
require 'procodile/tcp_proxy'
|
50
|
+
p = Procodile::TCPProxy.new(Supervisor.new(@config, {}))
|
51
|
+
p.run
|
52
|
+
end
|
53
|
+
|
48
54
|
desc "Shows this help output"
|
49
55
|
command def help
|
50
56
|
puts "\e[45;37mWelcome to Procodile v#{Procodile::VERSION}\e[0m"
|
@@ -71,6 +77,18 @@ module Procodile
|
|
71
77
|
cli.options[:processes] = processes
|
72
78
|
end
|
73
79
|
|
80
|
+
opts.on("-t", "--tag TAGNAME", "Tag all started processes with the given tag") do |tag|
|
81
|
+
cli.options[:tag] = tag
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("--no-supervisor", "Do not start a supervisor if its not running") do
|
85
|
+
cli.options[:start_supervisor] = false
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--no-processes", "Do not start any processes (only applicable when supervisor is stopped)") do
|
89
|
+
cli.options[:start_processes] = false
|
90
|
+
end
|
91
|
+
|
74
92
|
opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
|
75
93
|
cli.options[:foreground] = true
|
76
94
|
end
|
@@ -79,61 +97,65 @@ module Procodile
|
|
79
97
|
cli.options[:clean] = true
|
80
98
|
end
|
81
99
|
|
82
|
-
opts.on("-
|
83
|
-
cli.options[:
|
100
|
+
opts.on("--no-respawn", "Disable respawning for all processes") do
|
101
|
+
cli.options[:respawn] = false
|
84
102
|
end
|
85
103
|
|
86
104
|
opts.on("--stop-when-none", "Stop the supervisor when all processes are stopped") do
|
87
105
|
cli.options[:stop_when_none] = true
|
88
106
|
end
|
89
107
|
|
108
|
+
opts.on("-x", "--proxy", "Enables the Procodile proxy service") do
|
109
|
+
cli.options[:proxy] = true
|
110
|
+
end
|
111
|
+
|
90
112
|
opts.on("-d", "--dev", "Run in development mode") do
|
91
113
|
cli.options[:development] = true
|
92
|
-
cli.options[:
|
114
|
+
cli.options[:respawn] = false
|
93
115
|
cli.options[:foreground] = true
|
94
116
|
cli.options[:stop_when_none] = true
|
117
|
+
cli.options[:proxy] = true
|
95
118
|
end
|
96
119
|
end
|
97
120
|
command def start
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
raise Error, "No processes were started. The type you entered might already be running or isn't defined."
|
102
|
-
else
|
103
|
-
instances.each do |instance|
|
104
|
-
puts "Started #{instance['description']} (PID: #{instance['pid']})"
|
105
|
-
end
|
106
|
-
return
|
121
|
+
if supervisor_running?
|
122
|
+
if @options[:foreground]
|
123
|
+
raise Error, "Cannot be started in the foreground because supervisor already running"
|
107
124
|
end
|
108
|
-
end
|
109
125
|
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
if @options.has_key?(:respawn)
|
127
|
+
raise Error, "Cannot disable respawning because supervisor is already running"
|
128
|
+
end
|
113
129
|
|
114
|
-
|
130
|
+
if @options[:stop_when_none]
|
131
|
+
raise Error, "Cannot stop supervisor when none running because supervisor is already running"
|
132
|
+
end
|
115
133
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
puts "Removed all old pid & sock files"
|
120
|
-
end
|
134
|
+
if @options[:proxy]
|
135
|
+
raise Error, "Cannot enable the proxy when the supervisor is running"
|
136
|
+
end
|
121
137
|
|
122
|
-
|
123
|
-
|
124
|
-
|
138
|
+
instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag])
|
139
|
+
if instances.empty?
|
140
|
+
puts "No processes to start."
|
141
|
+
else
|
142
|
+
instances.each do |instance|
|
143
|
+
puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
return
|
125
147
|
else
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
148
|
+
# The supervisor isn't actually running. We need to start it before processes can be
|
149
|
+
# begin being processed
|
150
|
+
if @options[:start_supervisor] == false
|
151
|
+
raise Error, "Supervisor is not running and cannot be started because --no-supervisor is set"
|
152
|
+
else
|
153
|
+
start_supervisor do |supervisor|
|
154
|
+
unless @options[:start_processes] == false
|
155
|
+
supervisor.start_processes(process_names_from_cli_option, :tag => @options[:tag])
|
156
|
+
end
|
157
|
+
end
|
133
158
|
end
|
134
|
-
::Process.detach(pid)
|
135
|
-
File.open(pid_path, 'w') { |f| f.write(pid) }
|
136
|
-
puts "Started #{@config.app_name} supervisor with PID #{pid}"
|
137
159
|
end
|
138
160
|
end
|
139
161
|
|
@@ -147,23 +169,27 @@ module Procodile
|
|
147
169
|
cli.options[:processes] = processes
|
148
170
|
end
|
149
171
|
|
150
|
-
opts.on("-s", "--stop-supervisor", "Stop the ") do
|
172
|
+
opts.on("-s", "--stop-supervisor", "Stop the supervisor process when all processes are stopped") do
|
151
173
|
cli.options[:stop_supervisor] = true
|
152
174
|
end
|
153
175
|
end
|
154
176
|
command def stop
|
155
|
-
if
|
177
|
+
if supervisor_running?
|
156
178
|
options = {}
|
157
179
|
instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option, :stop_supervisor => @options[:stop_supervisor])
|
158
180
|
if instances.empty?
|
159
|
-
puts "
|
181
|
+
puts "No processes were stopped."
|
160
182
|
else
|
161
183
|
instances.each do |instance|
|
162
|
-
puts "
|
184
|
+
puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
|
163
185
|
end
|
164
186
|
end
|
187
|
+
|
188
|
+
if @options[:stop_supervisor]
|
189
|
+
puts "Supervisor will be stopped when processes are stopped."
|
190
|
+
end
|
165
191
|
else
|
166
|
-
raise Error, "
|
192
|
+
raise Error, "Procodile supervisor isn't running"
|
167
193
|
end
|
168
194
|
end
|
169
195
|
|
@@ -176,34 +202,34 @@ module Procodile
|
|
176
202
|
opts.on("-p", "--processes a,b,c", "Only restart the listed processes or process types") do |processes|
|
177
203
|
cli.options[:processes] = processes
|
178
204
|
end
|
205
|
+
|
206
|
+
opts.on("-t", "--tag TAGNAME", "Tag all started processes with the given tag") do |tag|
|
207
|
+
cli.options[:tag] = tag
|
208
|
+
end
|
179
209
|
end
|
180
210
|
command def restart
|
181
|
-
if
|
182
|
-
|
183
|
-
instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option)
|
211
|
+
if supervisor_running?
|
212
|
+
instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option, :tag => @options[:tag])
|
184
213
|
if instances.empty?
|
185
214
|
puts "There are no processes to restart."
|
186
215
|
else
|
187
|
-
instances.each do |
|
188
|
-
|
216
|
+
instances.each do |old_instance, new_instance|
|
217
|
+
if old_instance && new_instance
|
218
|
+
if old_instance['description'] == new_instance['description']
|
219
|
+
puts "Restarted".color(35) + " #{old_instance['description']}"
|
220
|
+
else
|
221
|
+
puts "Restarted".color(35) + " #{old_instance['description']} -> #{new_instance['description']}"
|
222
|
+
end
|
223
|
+
elsif old_instance
|
224
|
+
puts "Stopped".color(31) + " #{old_instance['description']}"
|
225
|
+
elsif new_instance
|
226
|
+
puts "Started".color(32) + " #{new_instance['description']}"
|
227
|
+
end
|
228
|
+
$stdout.flush
|
189
229
|
end
|
190
230
|
end
|
191
231
|
else
|
192
|
-
raise Error, "
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
#
|
197
|
-
# Stop Supervisor
|
198
|
-
#
|
199
|
-
|
200
|
-
desc "Stop the supervisor without stopping processes"
|
201
|
-
command def stop_supervisor
|
202
|
-
if running?
|
203
|
-
::Process.kill('TERM', current_pid)
|
204
|
-
puts "Supervisor will be stopped in a moment."
|
205
|
-
else
|
206
|
-
raise Error, "#{@config.app_name} supervisor isn't running"
|
232
|
+
raise Error, "Procodile supervisor isn't running"
|
207
233
|
end
|
208
234
|
end
|
209
235
|
|
@@ -213,11 +239,11 @@ module Procodile
|
|
213
239
|
|
214
240
|
desc "Reload Procodile configuration"
|
215
241
|
command def reload
|
216
|
-
if
|
242
|
+
if supervisor_running?
|
217
243
|
ControlClient.run(@config.sock_path, 'reload_config')
|
218
|
-
puts "Reloaded
|
244
|
+
puts "Reloaded Procodile config"
|
219
245
|
else
|
220
|
-
raise Error, "
|
246
|
+
raise Error, "Procodile supervisor isn't running"
|
221
247
|
end
|
222
248
|
end
|
223
249
|
|
@@ -232,21 +258,21 @@ module Procodile
|
|
232
258
|
end
|
233
259
|
end
|
234
260
|
command def check_concurrency
|
235
|
-
if
|
261
|
+
if supervisor_running?
|
236
262
|
reply = ControlClient.run(@config.sock_path, 'check_concurrency', :reload => @options[:reload])
|
237
263
|
if reply['started'].empty? && reply['stopped'].empty?
|
238
|
-
puts "
|
264
|
+
puts "Processes are running as configured"
|
239
265
|
else
|
240
266
|
reply['started'].each do |instance|
|
241
|
-
puts "Started #{instance['description']}
|
267
|
+
puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
|
242
268
|
end
|
243
269
|
|
244
270
|
reply['stopped'].each do |instance|
|
245
|
-
puts "Stopped #{instance['description']}
|
271
|
+
puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
|
246
272
|
end
|
247
273
|
end
|
248
274
|
else
|
249
|
-
raise Error, "
|
275
|
+
raise Error, "Procodile supervisor isn't running"
|
250
276
|
end
|
251
277
|
end
|
252
278
|
|
@@ -261,7 +287,7 @@ module Procodile
|
|
261
287
|
end
|
262
288
|
end
|
263
289
|
command def status
|
264
|
-
if
|
290
|
+
if supervisor_running?
|
265
291
|
status = ControlClient.run(@config.sock_path, 'status')
|
266
292
|
if @options[:json]
|
267
293
|
puts status.to_json
|
@@ -270,7 +296,7 @@ module Procodile
|
|
270
296
|
StatusCLIOutput.new(status).print_all
|
271
297
|
end
|
272
298
|
else
|
273
|
-
|
299
|
+
raise Error, "Procodile supervisor isn't running"
|
274
300
|
end
|
275
301
|
end
|
276
302
|
|
@@ -294,18 +320,7 @@ module Procodile
|
|
294
320
|
|
295
321
|
private
|
296
322
|
|
297
|
-
def
|
298
|
-
|
299
|
-
socket = UNIXSocket.new(@config.sock_path)
|
300
|
-
# Get the connection confirmation
|
301
|
-
connection = socket.gets
|
302
|
-
return false unless connection == 'READY'
|
303
|
-
# Send a command.
|
304
|
-
ensure
|
305
|
-
socket.close rescue nil
|
306
|
-
end
|
307
|
-
|
308
|
-
def running?
|
323
|
+
def supervisor_running?
|
309
324
|
if pid = current_pid
|
310
325
|
::Process.getpgid(pid) ? true : false
|
311
326
|
else
|
@@ -334,17 +349,53 @@ module Procodile
|
|
334
349
|
if processes.empty?
|
335
350
|
raise Error, "No process names provided"
|
336
351
|
end
|
337
|
-
processes.each do |process|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
end
|
352
|
+
#processes.each do |process|
|
353
|
+
# process_name, _ = process.split('.', 2)
|
354
|
+
# unless @config.process_list.keys.include?(process_name.to_s)
|
355
|
+
# raise Error, "Process '#{process_name}' is not configured. You may need to reload your config."
|
356
|
+
# end
|
357
|
+
#end
|
343
358
|
processes
|
344
359
|
else
|
345
360
|
nil
|
346
361
|
end
|
347
362
|
end
|
348
363
|
|
364
|
+
def start_supervisor(&after_start)
|
365
|
+
run_options = {}
|
366
|
+
run_options[:respawn] = @options[:respawn]
|
367
|
+
run_options[:stop_when_none] = @options[:stop_when_none]
|
368
|
+
run_options[:proxy] = @options[:proxy]
|
369
|
+
|
370
|
+
processes = process_names_from_cli_option
|
371
|
+
|
372
|
+
if @options[:clean]
|
373
|
+
FileUtils.rm_rf(Dir[File.join(@config.pid_root, '*')])
|
374
|
+
puts "Emptied PID directory"
|
375
|
+
end
|
376
|
+
|
377
|
+
if !Dir[File.join(@config.pid_root, "*")].empty?
|
378
|
+
raise Error, "The PID directory (#{@config.pid_root}) is not empty. Cannot start unless things are clean."
|
379
|
+
end
|
380
|
+
|
381
|
+
$0="[procodile] #{@config.app_name} (#{@config.root})"
|
382
|
+
if @options[:foreground]
|
383
|
+
File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
|
384
|
+
Supervisor.new(@config, run_options).start(&after_start)
|
385
|
+
else
|
386
|
+
FileUtils.rm_f(File.join(@config.pid_root, "*.pid"))
|
387
|
+
pid = fork do
|
388
|
+
STDOUT.reopen(@config.log_path, 'a')
|
389
|
+
STDOUT.sync = true
|
390
|
+
STDERR.reopen(@config.log_path, 'a')
|
391
|
+
STDERR.sync = true
|
392
|
+
Supervisor.new(@config, run_options).start(&after_start)
|
393
|
+
end
|
394
|
+
::Process.detach(pid)
|
395
|
+
File.open(pid_path, 'w') { |f| f.write(pid) }
|
396
|
+
puts "Started Procodile supervisor with PID #{pid}"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
349
400
|
end
|
350
401
|
end
|
data/lib/procodile/config.rb
CHANGED
@@ -15,6 +15,10 @@ module Procodile
|
|
15
15
|
raise Error, "Procfile not found at #{procfile_path}"
|
16
16
|
end
|
17
17
|
FileUtils.mkdir_p(pid_root)
|
18
|
+
|
19
|
+
@processes = process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
|
20
|
+
hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
def reload
|
@@ -27,6 +31,7 @@ module Procodile
|
|
27
31
|
if @processes
|
28
32
|
process_list.each do |name, command|
|
29
33
|
if process = @processes[name]
|
34
|
+
process.removed = false
|
30
35
|
# This command is already in our list. Add it.
|
31
36
|
if process.command != command
|
32
37
|
process.command = command
|
@@ -38,6 +43,15 @@ module Procodile
|
|
38
43
|
@processes[name] = create_process(name, command, COLORS[@processes.size.divmod(COLORS.size)[1]])
|
39
44
|
end
|
40
45
|
end
|
46
|
+
|
47
|
+
removed_processes = @processes.keys - process_list.keys
|
48
|
+
removed_processes.each do |process_name|
|
49
|
+
if p = @processes[process_name]
|
50
|
+
p.removed = true
|
51
|
+
@processes.delete(process_name)
|
52
|
+
Procodile.log nil, 'system', "#{process_name} has been removed to the Procfile. It will be removed when it is stopped."
|
53
|
+
end
|
54
|
+
end
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
@@ -46,13 +60,11 @@ module Procodile
|
|
46
60
|
end
|
47
61
|
|
48
62
|
def processes
|
49
|
-
@processes ||=
|
50
|
-
hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
|
51
|
-
end
|
63
|
+
@processes ||= {}
|
52
64
|
end
|
53
65
|
|
54
66
|
def process_list
|
55
|
-
@process_list ||= YAML.load_file(procfile_path)
|
67
|
+
@process_list ||= YAML.load_file(procfile_path) || {}
|
56
68
|
end
|
57
69
|
|
58
70
|
def options
|
@@ -92,7 +104,7 @@ module Procodile
|
|
92
104
|
end
|
93
105
|
|
94
106
|
def sock_path
|
95
|
-
File.join(pid_root, '
|
107
|
+
File.join(pid_root, 'procodile.sock')
|
96
108
|
end
|
97
109
|
|
98
110
|
private
|
@@ -4,6 +4,13 @@ require 'procodile/control_session'
|
|
4
4
|
module Procodile
|
5
5
|
class ControlServer
|
6
6
|
|
7
|
+
def self.start(supervisor)
|
8
|
+
Thread.new do
|
9
|
+
socket = ControlServer.new(supervisor)
|
10
|
+
socket.listen
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
def initialize(supervisor)
|
8
15
|
@supervisor = supervisor
|
9
16
|
end
|
@@ -14,7 +14,6 @@ module Procodile
|
|
14
14
|
options = JSON.parse(options)
|
15
15
|
if self.class.instance_methods(false).include?(command.to_sym) && command != 'receive_data'
|
16
16
|
begin
|
17
|
-
Procodile.log nil, 'control', "Received #{command} command"
|
18
17
|
public_send(command, options)
|
19
18
|
rescue Procodile::Error => e
|
20
19
|
Procodile.log nil, 'control', "Error: #{e.message}".color(31)
|
@@ -26,7 +25,7 @@ module Procodile
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def start_processes(options)
|
29
|
-
instances = @supervisor.start_processes(options['processes'])
|
28
|
+
instances = @supervisor.start_processes(options['processes'], :tag => options['tag'])
|
30
29
|
"200 " + instances.map(&:to_hash).to_json
|
31
30
|
end
|
32
31
|
|
@@ -36,8 +35,8 @@ module Procodile
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def restart(options)
|
39
|
-
instances = @supervisor.restart(:processes => options['processes'])
|
40
|
-
"200 " + instances.map
|
38
|
+
instances = @supervisor.restart(:processes => options['processes'], :tag => options['tag'])
|
39
|
+
"200 " + instances.map { |a| a.map { |i| i ? i.to_hash : nil } }.to_json
|
41
40
|
end
|
42
41
|
|
43
42
|
def reload_config(options)
|
data/lib/procodile/instance.rb
CHANGED
@@ -6,13 +6,14 @@ module Procodile
|
|
6
6
|
attr_accessor :pid
|
7
7
|
attr_reader :id
|
8
8
|
attr_accessor :process
|
9
|
-
|
9
|
+
attr_reader :tag
|
10
|
+
attr_reader :port
|
10
11
|
|
11
|
-
def initialize(process, id)
|
12
|
+
def initialize(supervisor, process, id)
|
13
|
+
@supervisor = supervisor
|
12
14
|
@process = process
|
13
15
|
@id = id
|
14
16
|
@respawns = 0
|
15
|
-
@respawnable = true
|
16
17
|
@started_at = nil
|
17
18
|
end
|
18
19
|
|
@@ -23,21 +24,33 @@ module Procodile
|
|
23
24
|
"#{@process.name}.#{@id}"
|
24
25
|
end
|
25
26
|
|
27
|
+
#
|
28
|
+
# Return the status of this instance
|
29
|
+
#
|
30
|
+
def status
|
31
|
+
if stopped?
|
32
|
+
'Stopped'
|
33
|
+
elsif stopping?
|
34
|
+
'Stopping'
|
35
|
+
elsif running?
|
36
|
+
'Running'
|
37
|
+
elsif failed?
|
38
|
+
'Failed'
|
39
|
+
else
|
40
|
+
'Unknown'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
26
44
|
#
|
27
45
|
# Return an array of environment variables that should be set
|
28
46
|
#
|
29
47
|
def environment_variables
|
30
|
-
@process.
|
48
|
+
vars = @process.environment_variables.merge({
|
31
49
|
'PID_FILE' => self.pid_file_path,
|
32
50
|
'APP_ROOT' => @process.config.root
|
33
51
|
})
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
# Should this instance still be monitored by the supervisor?
|
38
|
-
#
|
39
|
-
def unmonitored?
|
40
|
-
@monitored == false
|
52
|
+
vars['PORT'] = @port.to_s if @port
|
53
|
+
vars
|
41
54
|
end
|
42
55
|
|
43
56
|
#
|
@@ -62,9 +75,9 @@ module Procodile
|
|
62
75
|
#
|
63
76
|
# Is this process running? Pass an option to check the given PID instead of the instance
|
64
77
|
#
|
65
|
-
def running?
|
66
|
-
if
|
67
|
-
::Process.getpgid(
|
78
|
+
def running?
|
79
|
+
if @pid
|
80
|
+
::Process.getpgid(@pid) ? true : false
|
68
81
|
else
|
69
82
|
false
|
70
83
|
end
|
@@ -75,38 +88,39 @@ module Procodile
|
|
75
88
|
#
|
76
89
|
# Start a new instance of this process
|
77
90
|
#
|
78
|
-
def start
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
def start
|
92
|
+
if stopping?
|
93
|
+
Procodile.log(@process.log_color, description, "Process is stopped/stopping therefore cannot be started again.")
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
update_pid
|
98
|
+
if running?
|
85
99
|
Procodile.log(@process.log_color, description, "Already running with PID #{@pid}")
|
86
|
-
@started_at = File.mtime(self.pid_file_path)
|
87
100
|
nil
|
88
101
|
else
|
102
|
+
|
103
|
+
if @process.proxy? && @supervisor.tcp_proxy
|
104
|
+
allocate_port
|
105
|
+
end
|
106
|
+
|
89
107
|
if self.process.log_path
|
90
108
|
log_destination = File.open(self.process.log_path, 'a')
|
91
|
-
|
109
|
+
io = nil
|
92
110
|
else
|
93
111
|
reader, writer = IO.pipe
|
94
112
|
log_destination = writer
|
95
|
-
|
113
|
+
io = reader
|
96
114
|
end
|
97
|
-
|
115
|
+
@tag = @supervisor.tag.dup if @supervisor.tag
|
98
116
|
Dir.chdir(@process.config.root)
|
99
117
|
@pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
|
100
|
-
|
118
|
+
log_destination.close
|
101
119
|
File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
|
120
|
+
@supervisor.add_instance(self, io)
|
102
121
|
::Process.detach(@pid)
|
122
|
+
Procodile.log(@process.log_color, description, "Started with PID #{@pid}" + (@tag ? " (tagged with #{@tag})" : ''))
|
103
123
|
@started_at = Time.now
|
104
|
-
|
105
|
-
if block_given?
|
106
|
-
block.call(self, return_value)
|
107
|
-
end
|
108
|
-
|
109
|
-
return_value
|
110
124
|
end
|
111
125
|
end
|
112
126
|
|
@@ -114,7 +128,21 @@ module Procodile
|
|
114
128
|
# Is this instance supposed to be stopping/be stopped?
|
115
129
|
#
|
116
130
|
def stopping?
|
117
|
-
@stopping
|
131
|
+
@stopping ? true : false
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Is this stopped?
|
136
|
+
#
|
137
|
+
def stopped?
|
138
|
+
@stopped || false
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Has this failed?
|
143
|
+
#
|
144
|
+
def failed?
|
145
|
+
@failed ? true : false
|
118
146
|
end
|
119
147
|
|
120
148
|
#
|
@@ -122,7 +150,7 @@ module Procodile
|
|
122
150
|
# tells us that we want it to be stopped.
|
123
151
|
#
|
124
152
|
def stop
|
125
|
-
@stopping =
|
153
|
+
@stopping = Time.now
|
126
154
|
update_pid
|
127
155
|
if self.running?
|
128
156
|
Procodile.log(@process.log_color, description, "Sending #{@process.term_signal} to #{@pid}")
|
@@ -138,8 +166,8 @@ module Procodile
|
|
138
166
|
#
|
139
167
|
def on_stop
|
140
168
|
@started_at = nil
|
169
|
+
@stopped = true
|
141
170
|
tidy
|
142
|
-
unmonitor
|
143
171
|
end
|
144
172
|
|
145
173
|
#
|
@@ -153,9 +181,8 @@ module Procodile
|
|
153
181
|
#
|
154
182
|
# Retarts the process using the appropriate method from the process configuraiton
|
155
183
|
#
|
156
|
-
def restart
|
184
|
+
def restart
|
157
185
|
Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode")
|
158
|
-
@restarting = true
|
159
186
|
update_pid
|
160
187
|
case @process.restart_mode
|
161
188
|
when 'usr1', 'usr2'
|
@@ -164,23 +191,24 @@ module Procodile
|
|
164
191
|
Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}")
|
165
192
|
else
|
166
193
|
Procodile.log(@process.log_color, description, "Process not running already. Starting it.")
|
167
|
-
|
194
|
+
on_stop
|
195
|
+
@process.create_instance(@supervisor).start
|
168
196
|
end
|
197
|
+
self
|
169
198
|
when 'start-term'
|
170
|
-
|
171
|
-
start
|
172
|
-
|
173
|
-
|
199
|
+
new_instance = @process.create_instance(@supervisor)
|
200
|
+
new_instance.start
|
201
|
+
stop
|
202
|
+
new_instance
|
174
203
|
when 'term-start'
|
175
204
|
stop
|
205
|
+
new_instance = @process.create_instance(@supervisor)
|
176
206
|
Thread.new do
|
177
|
-
# Wait for this process to stop and when it has, run it.
|
178
207
|
sleep 0.5 while running?
|
179
|
-
start
|
208
|
+
new_instance.start
|
180
209
|
end
|
210
|
+
new_instance
|
181
211
|
end
|
182
|
-
ensure
|
183
|
-
@restarting = false
|
184
212
|
end
|
185
213
|
|
186
214
|
#
|
@@ -190,6 +218,7 @@ module Procodile
|
|
190
218
|
pid_from_file = self.pid_from_file
|
191
219
|
if pid_from_file && pid_from_file != @pid
|
192
220
|
@pid = pid_from_file
|
221
|
+
@started_at = File.mtime(self.pid_file_path)
|
193
222
|
Procodile.log(@process.log_color, description, "PID file changed. Updated pid to #{@pid}")
|
194
223
|
true
|
195
224
|
else
|
@@ -200,10 +229,8 @@ module Procodile
|
|
200
229
|
#
|
201
230
|
# Check the status of this process and handle as appropriate.
|
202
231
|
#
|
203
|
-
def check
|
204
|
-
|
205
|
-
return if @restarting
|
206
|
-
return if unmonitored?
|
232
|
+
def check(options = {})
|
233
|
+
return if failed?
|
207
234
|
|
208
235
|
if self.running?
|
209
236
|
# Everything is OK. The process is running.
|
@@ -213,7 +240,7 @@ module Procodile
|
|
213
240
|
# the file in case the process has changed itself.
|
214
241
|
return check if update_pid
|
215
242
|
|
216
|
-
if @
|
243
|
+
if @supervisor.allow_respawning?
|
217
244
|
if can_respawn?
|
218
245
|
Procodile.log(@process.log_color, description, "Process has stopped. Respawning...")
|
219
246
|
start
|
@@ -221,24 +248,17 @@ module Procodile
|
|
221
248
|
elsif respawns >= @process.max_respawns
|
222
249
|
Procodile.log(@process.log_color, description, "\e[41;37mWarning:\e[0m\e[31m this process has been respawned #{respawns} times and keeps dying.\e[0m")
|
223
250
|
Procodile.log(@process.log_color, description, "It will not be respawned automatically any longer and will no longer be managed.".color(31))
|
251
|
+
@failed = Time.now
|
224
252
|
tidy
|
225
|
-
unmonitor
|
226
253
|
end
|
227
254
|
else
|
228
255
|
Procodile.log(@process.log_color, description, "Process has stopped. Respawning not available.")
|
256
|
+
@failed = Time.now
|
229
257
|
tidy
|
230
|
-
unmonitor
|
231
258
|
end
|
232
259
|
end
|
233
260
|
end
|
234
261
|
|
235
|
-
#
|
236
|
-
# Mark this process as dead and tidy up after it
|
237
|
-
#
|
238
|
-
def unmonitor
|
239
|
-
@monitored = false
|
240
|
-
end
|
241
|
-
|
242
262
|
#
|
243
263
|
# Can this process be respawned if needed?
|
244
264
|
#
|
@@ -277,10 +297,31 @@ module Procodile
|
|
277
297
|
:description => self.description,
|
278
298
|
:pid => self.pid,
|
279
299
|
:respawns => self.respawns,
|
300
|
+
:status => self.status,
|
280
301
|
:running => self.running?,
|
281
|
-
:started_at => @started_at ? @started_at.to_i : nil
|
302
|
+
:started_at => @started_at ? @started_at.to_i : nil,
|
303
|
+
:tag => self.tag,
|
304
|
+
:port => @port
|
282
305
|
}
|
283
306
|
end
|
284
307
|
|
308
|
+
|
309
|
+
#
|
310
|
+
# Find a port number for this instance to listen on. We just check that nothing is already listening on it.
|
311
|
+
# The process is expected to take it straight away if it wants it.
|
312
|
+
#
|
313
|
+
def allocate_port
|
314
|
+
until @port
|
315
|
+
possible_port = rand(10000) + 20000
|
316
|
+
begin
|
317
|
+
server = TCPServer.new('127.0.0.1', possible_port)
|
318
|
+
server.close
|
319
|
+
return @port = possible_port
|
320
|
+
rescue
|
321
|
+
# Nah.
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
285
326
|
end
|
286
327
|
end
|
data/lib/procodile/process.rb
CHANGED
@@ -3,11 +3,14 @@ require 'procodile/instance'
|
|
3
3
|
module Procodile
|
4
4
|
class Process
|
5
5
|
|
6
|
+
MUTEX = Mutex.new
|
7
|
+
|
6
8
|
attr_reader :config
|
7
9
|
attr_reader :name
|
8
10
|
attr_accessor :command
|
9
11
|
attr_accessor :options
|
10
12
|
attr_accessor :log_color
|
13
|
+
attr_accessor :removed
|
11
14
|
|
12
15
|
def initialize(config, name, command, options = {})
|
13
16
|
@config = config
|
@@ -15,6 +18,29 @@ module Procodile
|
|
15
18
|
@command = command
|
16
19
|
@options = options
|
17
20
|
@log_color = 0
|
21
|
+
@instance_index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Increase the instance index and return
|
26
|
+
#
|
27
|
+
def get_instance_id
|
28
|
+
MUTEX.synchronize do
|
29
|
+
@instance_index = 0 if @instance_index == 10000
|
30
|
+
@instance_index += 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Return all environment variables for this process
|
36
|
+
#
|
37
|
+
def environment_variables
|
38
|
+
global_variables = @config.environment_variables
|
39
|
+
process_vars = @config.process_options[@name] ? @config.process_options[@name]['env'] || {} : {}
|
40
|
+
process_local_vars = @config.local_process_options[@name] ? @config.local_process_options[@name]['env'] || {} : {}
|
41
|
+
global_variables.merge(process_vars.merge(process_local_vars)).each_with_object({}) do |(key, value), hash|
|
42
|
+
hash[key.to_s] = value.to_s
|
43
|
+
end
|
18
44
|
end
|
19
45
|
|
20
46
|
#
|
@@ -65,11 +91,40 @@ module Procodile
|
|
65
91
|
@options['restart_mode'] || 'term-start'
|
66
92
|
end
|
67
93
|
|
94
|
+
#
|
95
|
+
# Is this process enabled for proxying?
|
96
|
+
#
|
97
|
+
def proxy?
|
98
|
+
@options.has_key?('proxy_port')
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Return the port for the proxy to listen on for this process type
|
103
|
+
#
|
104
|
+
def proxy_port
|
105
|
+
proxy? ? @options['proxy_port'].to_i : nil
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Return the port for the proxy to listen on for this process type
|
110
|
+
#
|
111
|
+
def proxy_address
|
112
|
+
proxy? ? @options['proxy_address'] || '127.0.0.1' : nil
|
113
|
+
end
|
114
|
+
|
115
|
+
|
68
116
|
#
|
69
117
|
# Generate an array of new instances for this process (based on its quantity)
|
70
118
|
#
|
71
|
-
def generate_instances(quantity = self.quantity
|
72
|
-
quantity.times.map { |i|
|
119
|
+
def generate_instances(supervisor, quantity = self.quantity)
|
120
|
+
quantity.times.map { |i| create_instance(supervisor) }
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Create a new instance
|
125
|
+
#
|
126
|
+
def create_instance(supervisor)
|
127
|
+
Instance.new(supervisor, self, get_instance_id)
|
73
128
|
end
|
74
129
|
|
75
130
|
#
|
@@ -84,7 +139,10 @@ module Procodile
|
|
84
139
|
:respawn_window => self.respawn_window,
|
85
140
|
:command => self.command,
|
86
141
|
:restart_mode => self.restart_mode,
|
87
|
-
:log_path => self.log_path
|
142
|
+
:log_path => self.log_path,
|
143
|
+
:removed => self.removed ? true : false,
|
144
|
+
:proxy_port => proxy_port,
|
145
|
+
:proxy_address => proxy_address
|
88
146
|
}
|
89
147
|
end
|
90
148
|
|
@@ -41,20 +41,19 @@ module Procodile
|
|
41
41
|
puts "||".color(process['log_color']) + " Respawning " + "#{process['max_respawns']} every #{process['respawn_window']} seconds"
|
42
42
|
puts "||".color(process['log_color']) + " Restart mode " + process['restart_mode']
|
43
43
|
puts "||".color(process['log_color']) + " Log path " + (process['log_path'] || "none specified")
|
44
|
+
puts "||".color(process['log_color']) + " Address/Port " + (process['proxy_port'] ? "#{process['proxy_address']}:#{process['proxy_port']}" : "none")
|
44
45
|
instances = @status['instances'][process['name']]
|
45
46
|
if instances.empty?
|
46
47
|
puts "||".color(process['log_color']) + " No processes running."
|
47
48
|
else
|
48
49
|
instances.each do |instance|
|
49
50
|
print "|| => ".color(process['log_color']) + instance['description'].to_s.ljust(17, ' ').color(process['log_color'])
|
50
|
-
|
51
|
-
print 'Running'.color("32")
|
52
|
-
else
|
53
|
-
print 'Stopped'.color("31")
|
54
|
-
end
|
51
|
+
print instance['status'].ljust(10, ' ')
|
55
52
|
print " " + formatted_timestamp(instance['started_at']).ljust(10, ' ')
|
56
|
-
print "
|
57
|
-
print "
|
53
|
+
print " " + instance['pid'].to_s.ljust(6, ' ')
|
54
|
+
print " " + instance['respawns'].to_s.ljust(4, ' ')
|
55
|
+
print " " + (instance['port'] || "-").to_s.ljust(6, ' ')
|
56
|
+
print " " + (instance['tag'] || "-").to_s
|
58
57
|
puts
|
59
58
|
end
|
60
59
|
end
|
data/lib/procodile/supervisor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'procodile/control_server'
|
2
|
+
require 'procodile/tcp_proxy'
|
2
3
|
|
3
4
|
module Procodile
|
4
5
|
class Supervisor
|
@@ -6,6 +7,8 @@ module Procodile
|
|
6
7
|
attr_reader :config
|
7
8
|
attr_reader :processes
|
8
9
|
attr_reader :started_at
|
10
|
+
attr_reader :tag
|
11
|
+
attr_reader :tcp_proxy
|
9
12
|
|
10
13
|
def initialize(config, run_options = {})
|
11
14
|
@config = config
|
@@ -20,28 +23,34 @@ module Procodile
|
|
20
23
|
@signal_handler.register('HUP') { reload_config }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def allow_respawning?
|
27
|
+
@run_options[:respawn] != false
|
28
|
+
end
|
29
|
+
|
30
|
+
def start(&after_start)
|
31
|
+
Procodile.log nil, "system", "Procodile supervisor started with PID #{::Process.pid}"
|
32
|
+
if @run_options[:respawn] == false
|
33
|
+
Procodile.log nil, "system", "Automatic respawning is disabled"
|
27
34
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
ControlServer.start(self)
|
36
|
+
if @run_options[:proxy]
|
37
|
+
Procodile.log nil, "system", "Proxy is enabled"
|
38
|
+
@tcp_proxy = TCPProxy.start(self)
|
31
39
|
end
|
32
|
-
start_processes(options[:processes])
|
33
40
|
watch_for_output
|
34
41
|
@started_at = Time.now
|
42
|
+
after_start.call(self) if block_given?
|
35
43
|
loop { supervise; sleep 3 }
|
36
44
|
end
|
37
45
|
|
38
|
-
def start_processes(types =
|
46
|
+
def start_processes(types = nil, options = {})
|
47
|
+
@tag = options[:tag]
|
39
48
|
reload_config
|
40
49
|
Array.new.tap do |instances_started|
|
41
50
|
@config.processes.each do |name, process|
|
42
|
-
next if types && !types.include?(name.to_s)
|
43
|
-
next if @processes[process] && !@processes[process].empty?
|
44
|
-
instances =
|
51
|
+
next if types && !types.include?(name.to_s) # Not a process we want
|
52
|
+
next if @processes[process] && !@processes[process].empty? # Process type already running
|
53
|
+
instances = process.generate_instances(self).each(&:start)
|
45
54
|
instances_started.push(*instances)
|
46
55
|
end
|
47
56
|
end
|
@@ -51,7 +60,7 @@ module Procodile
|
|
51
60
|
if options[:stop_supervisor]
|
52
61
|
@run_options[:stop_when_none] = true
|
53
62
|
end
|
54
|
-
|
63
|
+
reload_config
|
55
64
|
Array.new.tap do |instances_stopped|
|
56
65
|
if options[:processes].nil?
|
57
66
|
Procodile.log nil, "system", "Stopping all #{@config.app_name} processes"
|
@@ -73,53 +82,48 @@ module Procodile
|
|
73
82
|
end
|
74
83
|
|
75
84
|
def restart(options = {})
|
85
|
+
@tag = options[:tag]
|
76
86
|
reload_config
|
77
87
|
Array.new.tap do |instances_restarted|
|
78
88
|
if options[:processes].nil?
|
79
89
|
Procodile.log nil, "system", "Restarting all #{@config.app_name} processes"
|
80
|
-
@processes.
|
81
|
-
instances.each do |instance|
|
82
|
-
instance.restart { |_, io| add_reader(instance, io) }
|
83
|
-
instances_restarted << instance
|
84
|
-
end
|
85
|
-
end
|
86
|
-
instances_restarted.push(*check_instance_quantities[:started])
|
90
|
+
instances = @processes.values.flatten
|
87
91
|
else
|
88
92
|
instances = process_names_to_instances(options[:processes])
|
89
93
|
Procodile.log nil, "system", "Restarting #{instances.size} process(es)"
|
90
|
-
instances.each do |instance|
|
91
|
-
instance.restart { |_, io| add_reader(instance, io) }
|
92
|
-
instances_restarted << instance
|
93
|
-
end
|
94
|
-
instances_restarted.push(*check_instance_quantities(options[:processes])[:started])
|
95
94
|
end
|
95
|
+
|
96
|
+
# Stop any processes that are no longer wanted at this point
|
97
|
+
instances_restarted.push(*check_instance_quantities(:stopped, options[:processes])[:stopped].map { |i| [i, nil]})
|
98
|
+
|
99
|
+
instances.each do |instance|
|
100
|
+
next if instance.stopping?
|
101
|
+
new_instance = instance.restart
|
102
|
+
instances_restarted << [instance, new_instance]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Start any processes that are needed at this point
|
106
|
+
instances_restarted.push(*check_instance_quantities(:started, options[:processes])[:started].map { |i| [nil, i]})
|
96
107
|
end
|
97
108
|
end
|
98
109
|
|
99
110
|
def stop_supervisor
|
100
|
-
Procodile.log nil, 'system', "Stopping
|
101
|
-
FileUtils.rm_f(File.join(@config.pid_root, '
|
111
|
+
Procodile.log nil, 'system', "Stopping Procodile supervisor"
|
112
|
+
FileUtils.rm_f(File.join(@config.pid_root, 'procodile.pid'))
|
102
113
|
::Process.exit 0
|
103
114
|
end
|
104
115
|
|
105
116
|
def supervise
|
106
|
-
#
|
107
|
-
# be removed from the list.
|
108
|
-
remove_unmonitored_instances
|
109
|
-
|
110
|
-
# Remove processes that have been stopped
|
117
|
+
# Tell instances that have been stopped that they have been stopped
|
111
118
|
remove_stopped_instances
|
112
119
|
|
120
|
+
# Remove removed processes
|
121
|
+
remove_removed_processes
|
122
|
+
|
113
123
|
# Check all instances that we manage and let them do their things.
|
114
124
|
@processes.each do |_, instances|
|
115
125
|
instances.each do |instance|
|
116
126
|
instance.check
|
117
|
-
if instance.unmonitored?
|
118
|
-
if @run_options[:brittle]
|
119
|
-
Procodile.log nil, "system", "Stopping everything because a process has died in brittle mode."
|
120
|
-
return stop
|
121
|
-
end
|
122
|
-
end
|
123
127
|
end
|
124
128
|
end
|
125
129
|
|
@@ -162,16 +166,31 @@ module Procodile
|
|
162
166
|
}
|
163
167
|
end
|
164
168
|
|
165
|
-
private
|
166
|
-
|
167
169
|
def add_reader(instance, io)
|
168
|
-
return unless io
|
169
170
|
@readers[io] = instance
|
170
171
|
@signal_handler.notice
|
171
172
|
end
|
172
173
|
|
174
|
+
def add_instance(instance, io = nil)
|
175
|
+
add_reader(instance, io) if io
|
176
|
+
@processes[instance.process] ||= []
|
177
|
+
unless @processes[instance.process].include?(instance)
|
178
|
+
@processes[instance.process] << instance
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def remove_instance(instance)
|
183
|
+
if @processes[instance.process]
|
184
|
+
@processes[instance.process].delete(instance)
|
185
|
+
@readers.delete(instance)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
173
191
|
def watch_for_output
|
174
192
|
Thread.new do
|
193
|
+
buffer = {}
|
175
194
|
loop do
|
176
195
|
io = IO.select([@signal_handler.pipe[:reader]] + @readers.keys, nil, nil, 30)
|
177
196
|
@signal_handler.handle
|
@@ -184,13 +203,19 @@ module Procodile
|
|
184
203
|
end
|
185
204
|
|
186
205
|
if reader.eof?
|
206
|
+
reader.close
|
207
|
+
buffer.delete(reader)
|
187
208
|
@readers.delete(reader)
|
188
209
|
else
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
210
|
+
buffer[reader] ||= ""
|
211
|
+
buffer[reader] << reader.read_nonblock(4096)
|
212
|
+
while buffer[reader].index("\n")
|
213
|
+
line, buffer[reader] = buffer[reader].split("\n", 2)
|
214
|
+
if instance = @readers[reader]
|
215
|
+
Procodile.log instance.process.log_color, instance.description, "=> ".color(instance.process.log_color) + line
|
216
|
+
else
|
217
|
+
Procodile.log nil, 'unknown', data
|
218
|
+
end
|
194
219
|
end
|
195
220
|
end
|
196
221
|
end
|
@@ -199,45 +224,36 @@ module Procodile
|
|
199
224
|
end
|
200
225
|
end
|
201
226
|
|
202
|
-
def check_instance_quantities(processes = nil)
|
227
|
+
def check_instance_quantities(type = :both, processes = nil)
|
203
228
|
{:started => [], :stopped => []}.tap do |status|
|
204
229
|
@processes.each do |process, instances|
|
205
230
|
next if processes && !processes.include?(process.name)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
status[:started] = start_instances(process.generate_instances(quantity_needed, start_id))
|
231
|
+
active_instances = instances.select(&:running?)
|
232
|
+
|
233
|
+
if type == :both || type == :stopped
|
234
|
+
if active_instances.size > process.quantity
|
235
|
+
quantity_to_stop = active_instances.size - process.quantity
|
236
|
+
Procodile.log nil, "system", "Stopping #{quantity_to_stop} #{process.name} process(es)"
|
237
|
+
status[:stopped] = active_instances.last(quantity_to_stop).each(&:stop)
|
238
|
+
end
|
215
239
|
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
240
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
@processes[instance.process] << instance
|
228
|
-
end
|
229
|
-
end
|
241
|
+
if type == :both || type == :started
|
242
|
+
if active_instances.size < process.quantity
|
243
|
+
quantity_needed = process.quantity - active_instances.size
|
244
|
+
Procodile.log nil, "system", "Starting #{quantity_needed} more #{process.name} process(es)"
|
245
|
+
status[:started] = process.generate_instances(self, quantity_needed).each(&:start)
|
246
|
+
end
|
247
|
+
end
|
230
248
|
|
231
|
-
|
232
|
-
@processes.each do |_, instances|
|
233
|
-
instances.reject!(&:unmonitored?)
|
249
|
+
end
|
234
250
|
end
|
235
251
|
end
|
236
252
|
|
237
253
|
def remove_stopped_instances
|
238
254
|
@processes.each do |_, instances|
|
239
255
|
instances.reject! do |instance|
|
240
|
-
if
|
256
|
+
if instance.stopping? && !instance.running?
|
241
257
|
instance.on_stop
|
242
258
|
true
|
243
259
|
else
|
@@ -247,6 +263,19 @@ module Procodile
|
|
247
263
|
end
|
248
264
|
end
|
249
265
|
|
266
|
+
def remove_removed_processes
|
267
|
+
@processes.reject! do |process, instances|
|
268
|
+
if process.removed && instances.empty?
|
269
|
+
if @tcp_proxy
|
270
|
+
@tcp_proxy.remove_process(process)
|
271
|
+
end
|
272
|
+
true
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
250
279
|
def process_names_to_instances(names)
|
251
280
|
names.each_with_object([]) do |name, array|
|
252
281
|
if name =~ /\A(.*)\.(\d+)\z/
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Procodile
|
2
|
+
class TCPProxy
|
3
|
+
|
4
|
+
def self.start(supervisor)
|
5
|
+
proxy = new(supervisor)
|
6
|
+
proxy.start
|
7
|
+
proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(supervisor)
|
11
|
+
@supervisor = supervisor
|
12
|
+
@thread = nil
|
13
|
+
@listeners = {}
|
14
|
+
@stopped_processes = []
|
15
|
+
@sp_reader, @sp_writer = IO.pipe
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@supervisor.config.processes.each { |_, p| add_process(p) }
|
20
|
+
Thread.new do
|
21
|
+
listen
|
22
|
+
Procodile.log nil, 'proxy', "Stopped listening on all ports"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_process(process)
|
27
|
+
if process.proxy?
|
28
|
+
@listeners[TCPServer.new(process.proxy_address, process.proxy_port)] = process
|
29
|
+
Procodile.log nil, 'proxy', "Proxying traffic on #{process.proxy_address}:#{process.proxy_port} to #{process.name}".color(32)
|
30
|
+
@sp_writer.write_nonblock('.')
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
34
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_process(process)
|
38
|
+
@stopped_processes << process
|
39
|
+
@sp_writer.write_nonblock('.')
|
40
|
+
end
|
41
|
+
|
42
|
+
def listen
|
43
|
+
loop do
|
44
|
+
io = IO.select([@sp_reader] + @listeners.keys, nil, nil, 30)
|
45
|
+
if io && io.first
|
46
|
+
io.first.each do |io|
|
47
|
+
if io == @sp_reader
|
48
|
+
io.read_nonblock(999)
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
Thread.new(io.accept, io) do |client, server|
|
53
|
+
handle_client(client, server)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@stopped_processes.reject do |process|
|
59
|
+
if io = @listeners.key(process)
|
60
|
+
Procodile.log nil, 'proxy', "Stopped proxy listener for #{process.name}"
|
61
|
+
io.close
|
62
|
+
@listeners.delete(io)
|
63
|
+
end
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue => e
|
68
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
69
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_client(client, server)
|
73
|
+
process = @listeners[server]
|
74
|
+
instances = @supervisor.processes[process] || []
|
75
|
+
if instances.empty?
|
76
|
+
Procodile.log nil, 'proxy', "There are no processes running for #{process.name}"
|
77
|
+
else
|
78
|
+
instance = instances[rand(instances.size)]
|
79
|
+
backend_socket = TCPSocket.new('127.0.0.1', instance.port) rescue nil
|
80
|
+
if backend_socket.nil?
|
81
|
+
Procodile.log nil, 'proxy', "Could not connect to #{instance.description}:#{instance.port}"
|
82
|
+
return
|
83
|
+
end
|
84
|
+
readers = {:backend => backend_socket, :client => client}
|
85
|
+
loop do
|
86
|
+
io = IO.select(readers.values, nil, nil, 0.5)
|
87
|
+
if io && io.first
|
88
|
+
io.first.each do |io|
|
89
|
+
readers.keys.each do |key|
|
90
|
+
next unless readers[key] == io
|
91
|
+
opposite_side = key == :client ? :backend : :client
|
92
|
+
if io.eof?
|
93
|
+
readers[opposite_side].shutdown(Socket::SHUT_WR) rescue nil
|
94
|
+
readers.delete(opposite_side)
|
95
|
+
else
|
96
|
+
readers[opposite_side].write(io.readpartial(1024)) rescue nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
rescue => e
|
104
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
105
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
106
|
+
ensure
|
107
|
+
backend_socket.close rescue nil
|
108
|
+
client.close rescue nil
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
data/lib/procodile/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: procodile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/procodile/signal_handler.rb
|
47
47
|
- lib/procodile/status_cli_output.rb
|
48
48
|
- lib/procodile/supervisor.rb
|
49
|
+
- lib/procodile/tcp_proxy.rb
|
49
50
|
- lib/procodile/version.rb
|
50
51
|
homepage: https://github.com/adamcooke/procodile
|
51
52
|
licenses:
|