pazuzu 0.1.2
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 +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
|