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.
@@ -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