pazuzu 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +2 -0
- data/Pazuzu.gemspec +29 -0
- data/README.md +171 -0
- data/Rakefile +21 -0
- data/bin/pazuzu +6 -0
- data/bin/pazuzud +6 -0
- data/lib/pazuzu.rb +37 -0
- data/lib/pazuzu/application.rb +114 -0
- data/lib/pazuzu/cgroup.rb +73 -0
- data/lib/pazuzu/command_line/controller.rb +152 -0
- data/lib/pazuzu/control/protocol.rb +304 -0
- data/lib/pazuzu/control/socket_client.rb +30 -0
- data/lib/pazuzu/control/socket_server.rb +75 -0
- data/lib/pazuzu/instance.rb +218 -0
- data/lib/pazuzu/procfiles.rb +16 -0
- data/lib/pazuzu/supervisor.rb +201 -0
- data/lib/pazuzu/supervisor_runner.rb +95 -0
- data/lib/pazuzu/utility/annotated_logger.rb +46 -0
- data/lib/pazuzu/utility/output_tailer.rb +60 -0
- data/lib/pazuzu/utility/process_spawning.rb +40 -0
- data/lib/pazuzu/utility/rate_limiter.rb +68 -0
- data/lib/pazuzu/utility/runnable.rb +120 -0
- data/lib/pazuzu/utility/runnable_pool.rb +62 -0
- data/lib/pazuzu/version.rb +3 -0
- data/lib/pazuzu/worker.rb +173 -0
- metadata +193 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
|
3
|
+
# Runs the supervisor daemon from the command line.
|
4
|
+
class SupervisorRunner
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@run_as_daemon = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!(arguments = [])
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{prog_name} [OPTIONS]"
|
13
|
+
opts.separator ""
|
14
|
+
opts.on("-d", "--daemon", 'Run as daemon') do
|
15
|
+
@run_as_daemon = true
|
16
|
+
end
|
17
|
+
opts.on("-p PATH", "--pid", "Store pid in PATH (defaults to #{DEFAULT_PID_PATH})") do |value|
|
18
|
+
@pid_path = File.expand_path(value)
|
19
|
+
end
|
20
|
+
opts.on("-c FILE", "--config FILE", "Read configuration from FILE (defaults to #{DEFAULT_CONFIG_PATH})") do |value|
|
21
|
+
@config_path = File.expand_path(value)
|
22
|
+
end
|
23
|
+
opts.on("-h", "--help", "Show this help.") do
|
24
|
+
puts opts
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
opts.parse!(arguments)
|
28
|
+
end
|
29
|
+
|
30
|
+
if @run_as_daemon
|
31
|
+
daemonize!
|
32
|
+
else
|
33
|
+
execute!
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
abort "#{prog_name}: #{e.message}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
DEFAULT_PID_PATH = '/var/run/pazuzud.pid'.freeze
|
42
|
+
DEFAULT_CONFIG_PATH = '/etc/pazuzu/pazuzu.conf'.freeze
|
43
|
+
|
44
|
+
def execute!
|
45
|
+
@config_path ||= DEFAULT_CONFIG_PATH
|
46
|
+
with_pid do
|
47
|
+
@supervisor = Pazuzu::Supervisor.new(:config_path => @config_path)
|
48
|
+
Signal.trap 'HUP' do
|
49
|
+
@supervisor.load_configuration!
|
50
|
+
end
|
51
|
+
begin
|
52
|
+
@supervisor.run!
|
53
|
+
rescue SystemExit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def daemonize!(&block)
|
59
|
+
return Process.fork {
|
60
|
+
Process.setsid
|
61
|
+
0.upto(255) do |n|
|
62
|
+
File.for_fd(n, "r").close rescue nil
|
63
|
+
end
|
64
|
+
|
65
|
+
File.umask(27)
|
66
|
+
Dir.chdir('/')
|
67
|
+
$stdin.reopen("/dev/null", 'r')
|
68
|
+
$stdout.reopen("/dev/null", 'w')
|
69
|
+
$stderr.reopen("/dev/null", 'w')
|
70
|
+
|
71
|
+
execute!
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_pid(&block)
|
76
|
+
path = @pid_path
|
77
|
+
path ||= DEFAULT_PID_PATH
|
78
|
+
|
79
|
+
File.open(path, 'w') do |file|
|
80
|
+
file << Process.pid
|
81
|
+
end
|
82
|
+
begin
|
83
|
+
yield
|
84
|
+
ensure
|
85
|
+
File.delete(path) rescue nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def prog_name
|
90
|
+
File.basename($0)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
# Logger which annotates all log messages.
|
5
|
+
class AnnotatedLogger < SimpleDelegator
|
6
|
+
|
7
|
+
def initialize(logger, prefix)
|
8
|
+
super(logger)
|
9
|
+
@prefix = prefix
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(message = nil, &block)
|
13
|
+
__getobj__.debug(format_message(message), &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(message = nil, &block)
|
17
|
+
__getobj__.info(format_message(message), &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(message = nil, &block)
|
21
|
+
__getobj__.warn(format_message(message), &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message = nil, &block)
|
25
|
+
__getobj__.error(format_message(message), &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(severity, message = nil, progname = nil, &block)
|
29
|
+
__getobj__.add(severity, format_message(message), progname, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def format_message(message)
|
35
|
+
if @prefix.is_a?(Proc)
|
36
|
+
prefix = @prefix.call
|
37
|
+
else
|
38
|
+
prefix = @prefix
|
39
|
+
end
|
40
|
+
"[#{prefix}] #{message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
class OutputTailer
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@limit = options[:limit] || 1000
|
8
|
+
@callback = options[:on_line] || proc { }
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@buffer = ''
|
11
|
+
@entries = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def open
|
15
|
+
close
|
16
|
+
@stream, writeable_stream = IO.pipe
|
17
|
+
@thread = Thread.start { tail! }
|
18
|
+
return writeable_stream
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
if @stream
|
23
|
+
@stream.close rescue nil
|
24
|
+
@stream = nil
|
25
|
+
end
|
26
|
+
if @thread
|
27
|
+
@thread.terminate
|
28
|
+
@thread = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def tail!
|
33
|
+
while @stream
|
34
|
+
data = @stream.readpartial(4096)
|
35
|
+
@mutex.synchronize do
|
36
|
+
@buffer << data
|
37
|
+
while @buffer =~ /\A([^\r\n]*)\r?\n(.*)/
|
38
|
+
@buffer = $2
|
39
|
+
add_line!($1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :entries
|
46
|
+
attr_reader :limit
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def add_line!(line)
|
51
|
+
pair = [Time.now, line]
|
52
|
+
@entries << pair
|
53
|
+
@entries.shift while @entries.length > @limit
|
54
|
+
@callback.call(pair)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
module ProcessSpawning
|
5
|
+
|
6
|
+
def self.prepare_child_process!
|
7
|
+
# Close file handles
|
8
|
+
3.upto(255) do |n|
|
9
|
+
File.for_fd(n, 'r').close rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# Reopen standard file handles
|
13
|
+
$stdin.reopen("/dev/null", 'r')
|
14
|
+
$stdout.reopen("/dev/null", 'w')
|
15
|
+
$stderr.reopen("/dev/null", 'w')
|
16
|
+
|
17
|
+
# Ignore SIGHUP
|
18
|
+
Signal.trap("HUP") { }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.set_user_and_group!(user, group)
|
22
|
+
current_uid, current_gid = Process.euid, Process.egid
|
23
|
+
|
24
|
+
new_uid = Etc.getpwnam(user).uid if user
|
25
|
+
new_uid ||= current_uid
|
26
|
+
new_gid = Etc.getgrnam(group).gid if group
|
27
|
+
new_gid ||= current_gid
|
28
|
+
if new_uid != current_uid or new_gid != current_gid
|
29
|
+
user_name = user
|
30
|
+
user_name ||= Etc.getpwnam(new_uid).name
|
31
|
+
Process.initgroups(user_name, new_gid)
|
32
|
+
Process::GID.change_privilege(new_gid)
|
33
|
+
Process::UID.change_privilege(new_uid)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
# Simple limiter that can be used to limit the rate of processing. Uses
|
5
|
+
# a short time window to calculate current rate.
|
6
|
+
class RateLimiter
|
7
|
+
|
8
|
+
# Initialize limiter with +max_hertz+ as the maximum frequency per second,
|
9
|
+
# and +window_seconds+ as the number of seconds to calculate frequency
|
10
|
+
# based on.
|
11
|
+
def initialize(max_hertz, window_seconds = 3.0)
|
12
|
+
@max_hertz = max_hertz
|
13
|
+
@history_seconds = window_seconds
|
14
|
+
@window = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns true if current rate exceeds limit.
|
18
|
+
def limited?
|
19
|
+
iterate
|
20
|
+
rate > @max_hertz
|
21
|
+
end
|
22
|
+
|
23
|
+
# Count a cycle. If the current rate exceeds the permitted limit, it will
|
24
|
+
# sleep until the rate goes below the limit. Returns true if not currently
|
25
|
+
# rate-limited, otherwise false.
|
26
|
+
def count!
|
27
|
+
iterate
|
28
|
+
limited = false
|
29
|
+
@window[-1][1] += 1
|
30
|
+
while rate > @max_hertz
|
31
|
+
sleep(0.5)
|
32
|
+
limited = true
|
33
|
+
iterate
|
34
|
+
end
|
35
|
+
!limited
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns current rate, in hertz.
|
39
|
+
def rate
|
40
|
+
rate = 0
|
41
|
+
window = @window
|
42
|
+
unless window.empty?
|
43
|
+
range = window[-1][0] - window[0][0]
|
44
|
+
if range > 0
|
45
|
+
total = window.inject(0.0) { |sum, (t, count)| sum + count }
|
46
|
+
rate = total / range if total >= @max_hertz
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rate
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def iterate
|
55
|
+
now = Time.now.to_f.floor
|
56
|
+
window = @window
|
57
|
+
top = window[-1]
|
58
|
+
if !top or top[0] != now
|
59
|
+
window << [now, 0]
|
60
|
+
end
|
61
|
+
time_threshold = now - @history_seconds
|
62
|
+
window.delete_if { |t, count| t < time_threshold }
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
# Mixin that provides a state machine for running state.
|
5
|
+
module Runnable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
_self = self
|
9
|
+
@started_at = nil
|
10
|
+
@stopped_at = nil
|
11
|
+
@state_condition = ConditionVariable.new
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@runnable_state = Statemachine.build do
|
14
|
+
state :stopped do
|
15
|
+
event :start!, :starting
|
16
|
+
on_entry :enter_stopped
|
17
|
+
end
|
18
|
+
state :starting do
|
19
|
+
event :started!, :running
|
20
|
+
event :failed!, :failed
|
21
|
+
event :stop!, :stopping
|
22
|
+
on_entry :enter_starting
|
23
|
+
end
|
24
|
+
state :running do
|
25
|
+
event :stop!, :stopping
|
26
|
+
event :starting!, :starting
|
27
|
+
on_entry :enter_started
|
28
|
+
end
|
29
|
+
state :stopping do
|
30
|
+
event :stopped!, :stopped
|
31
|
+
on_entry :enter_stopping
|
32
|
+
end
|
33
|
+
state :failed do
|
34
|
+
on_entry :enter_failed
|
35
|
+
end
|
36
|
+
context _self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def start!
|
41
|
+
if [:stopped, :failed].include?(@runnable_state.state)
|
42
|
+
@runnable_state.start!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop!
|
47
|
+
if [:starting, :running].include?(@runnable_state.state)
|
48
|
+
@runnable_state.stop!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_state
|
53
|
+
@runnable_state.state
|
54
|
+
end
|
55
|
+
|
56
|
+
def wait_for_state_change!
|
57
|
+
@mutex.synchronize { @state_condition.wait(@mutex) }
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :started_at
|
61
|
+
attr_reader :stopped_at
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
attr_reader :runnable_state
|
66
|
+
|
67
|
+
def on_starting
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_started
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_stopping
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_stopped
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_failed
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def signal_state_change!
|
85
|
+
@mutex.synchronize { @state_condition.signal }
|
86
|
+
end
|
87
|
+
|
88
|
+
def enter_starting
|
89
|
+
signal_state_change!
|
90
|
+
on_starting
|
91
|
+
end
|
92
|
+
|
93
|
+
def enter_started
|
94
|
+
signal_state_change!
|
95
|
+
@stopped_at = nil
|
96
|
+
@started_at = Time.now
|
97
|
+
on_started
|
98
|
+
end
|
99
|
+
|
100
|
+
def enter_stopping
|
101
|
+
signal_state_change!
|
102
|
+
on_stopping
|
103
|
+
end
|
104
|
+
|
105
|
+
def enter_stopped
|
106
|
+
signal_state_change!
|
107
|
+
@stopped_at = Time.now
|
108
|
+
@started_at = nil
|
109
|
+
on_stopped
|
110
|
+
end
|
111
|
+
|
112
|
+
def enter_failed
|
113
|
+
signal_state_change!
|
114
|
+
on_failed
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Pazuzu
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
# Runnable which manages a pool of child runnables that reflect its
|
5
|
+
# parent's running state.
|
6
|
+
class RunnablePool
|
7
|
+
|
8
|
+
include Runnable
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@children_mutex = Mutex.new
|
13
|
+
@children = Set.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def length
|
17
|
+
children.length
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(child)
|
21
|
+
raise ArgumentError, 'Child must be runnable' unless child.is_a?(Runnable)
|
22
|
+
@children_mutex.synchronize do
|
23
|
+
@children.add(child)
|
24
|
+
end
|
25
|
+
if [:running, :starting].include?(run_state)
|
26
|
+
child.start!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unregister(child)
|
31
|
+
if [:running, :starting].include?(child.run_state)
|
32
|
+
child.stop!
|
33
|
+
end
|
34
|
+
@children_mutex.synchronize do
|
35
|
+
@children.delete(child)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def children
|
40
|
+
@children_mutex.synchronize { @children.to_a }
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def on_starting
|
46
|
+
@children_mutex.synchronize { @children.dup }.each do |child|
|
47
|
+
child.start!
|
48
|
+
end
|
49
|
+
runnable_state.started!
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_stopping
|
53
|
+
@children_mutex.synchronize { @children.dup }.each do |child|
|
54
|
+
child.stop!
|
55
|
+
end
|
56
|
+
runnable_state.stopped!
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|