resurrected_god 0.14.0
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 +7 -0
- data/Announce.txt +135 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +33 -0
- data/Rakefile +129 -0
- data/bin/god +134 -0
- data/doc/god.asciidoc +1592 -0
- data/doc/intro.asciidoc +20 -0
- data/ext/god/.gitignore +5 -0
- data/ext/god/extconf.rb +56 -0
- data/ext/god/kqueue_handler.c +133 -0
- data/ext/god/netlink_handler.c +182 -0
- data/lib/god/behavior.rb +52 -0
- data/lib/god/behaviors/clean_pid_file.rb +21 -0
- data/lib/god/behaviors/clean_unix_socket.rb +21 -0
- data/lib/god/behaviors/notify_when_flapping.rb +51 -0
- data/lib/god/cli/command.rb +268 -0
- data/lib/god/cli/run.rb +170 -0
- data/lib/god/cli/version.rb +23 -0
- data/lib/god/compat19.rb +33 -0
- data/lib/god/condition.rb +96 -0
- data/lib/god/conditions/always.rb +36 -0
- data/lib/god/conditions/complex.rb +86 -0
- data/lib/god/conditions/cpu_usage.rb +80 -0
- data/lib/god/conditions/degrading_lambda.rb +52 -0
- data/lib/god/conditions/disk_usage.rb +32 -0
- data/lib/god/conditions/file_mtime.rb +28 -0
- data/lib/god/conditions/file_touched.rb +44 -0
- data/lib/god/conditions/flapping.rb +128 -0
- data/lib/god/conditions/http_response_code.rb +184 -0
- data/lib/god/conditions/lambda.rb +25 -0
- data/lib/god/conditions/memory_usage.rb +82 -0
- data/lib/god/conditions/process_exits.rb +66 -0
- data/lib/god/conditions/process_running.rb +63 -0
- data/lib/god/conditions/socket_responding.rb +142 -0
- data/lib/god/conditions/tries.rb +44 -0
- data/lib/god/configurable.rb +57 -0
- data/lib/god/contact.rb +114 -0
- data/lib/god/contacts/airbrake.rb +44 -0
- data/lib/god/contacts/campfire.rb +121 -0
- data/lib/god/contacts/email.rb +130 -0
- data/lib/god/contacts/hipchat.rb +117 -0
- data/lib/god/contacts/jabber.rb +75 -0
- data/lib/god/contacts/prowl.rb +57 -0
- data/lib/god/contacts/scout.rb +55 -0
- data/lib/god/contacts/sensu.rb +59 -0
- data/lib/god/contacts/slack.rb +98 -0
- data/lib/god/contacts/statsd.rb +46 -0
- data/lib/god/contacts/twitter.rb +51 -0
- data/lib/god/contacts/webhook.rb +74 -0
- data/lib/god/driver.rb +238 -0
- data/lib/god/errors.rb +24 -0
- data/lib/god/event_handler.rb +112 -0
- data/lib/god/event_handlers/dummy_handler.rb +13 -0
- data/lib/god/event_handlers/kqueue_handler.rb +17 -0
- data/lib/god/event_handlers/netlink_handler.rb +13 -0
- data/lib/god/logger.rb +109 -0
- data/lib/god/metric.rb +87 -0
- data/lib/god/process.rb +381 -0
- data/lib/god/registry.rb +32 -0
- data/lib/god/simple_logger.rb +59 -0
- data/lib/god/socket.rb +113 -0
- data/lib/god/sugar.rb +62 -0
- data/lib/god/sys_logger.rb +45 -0
- data/lib/god/system/portable_poller.rb +42 -0
- data/lib/god/system/process.rb +50 -0
- data/lib/god/system/slash_proc_poller.rb +92 -0
- data/lib/god/task.rb +552 -0
- data/lib/god/timeline.rb +25 -0
- data/lib/god/trigger.rb +43 -0
- data/lib/god/version.rb +4 -0
- data/lib/god/watch.rb +340 -0
- data/lib/god.rb +777 -0
- data/test/configs/child_events/child_events.god +44 -0
- data/test/configs/child_events/simple_server.rb +3 -0
- data/test/configs/child_polls/child_polls.god +37 -0
- data/test/configs/child_polls/simple_server.rb +12 -0
- data/test/configs/complex/complex.god +59 -0
- data/test/configs/complex/simple_server.rb +3 -0
- data/test/configs/contact/contact.god +118 -0
- data/test/configs/contact/simple_server.rb +3 -0
- data/test/configs/daemon_events/daemon_events.god +37 -0
- data/test/configs/daemon_events/simple_server.rb +8 -0
- data/test/configs/daemon_events/simple_server_stop.rb +11 -0
- data/test/configs/daemon_polls/daemon_polls.god +17 -0
- data/test/configs/daemon_polls/simple_server.rb +6 -0
- data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
- data/test/configs/degrading_lambda/tcp_server.rb +15 -0
- data/test/configs/keepalive/keepalive.god +9 -0
- data/test/configs/keepalive/keepalive.rb +12 -0
- data/test/configs/lifecycle/lifecycle.god +25 -0
- data/test/configs/matias/matias.god +50 -0
- data/test/configs/real.rb +59 -0
- data/test/configs/running_load/running_load.god +16 -0
- data/test/configs/stop_options/simple_server.rb +12 -0
- data/test/configs/stop_options/stop_options.god +39 -0
- data/test/configs/stress/simple_server.rb +3 -0
- data/test/configs/stress/stress.god +15 -0
- data/test/configs/task/logs/.placeholder +0 -0
- data/test/configs/task/task.god +26 -0
- data/test/configs/test.rb +61 -0
- data/test/configs/usr1_trapper.rb +10 -0
- data/test/helper.rb +172 -0
- data/test/suite.rb +6 -0
- data/test/test_airbrake.rb +14 -0
- data/test/test_behavior.rb +18 -0
- data/test/test_campfire.rb +22 -0
- data/test/test_condition.rb +52 -0
- data/test/test_conditions_disk_usage.rb +50 -0
- data/test/test_conditions_http_response_code.rb +109 -0
- data/test/test_conditions_process_running.rb +40 -0
- data/test/test_conditions_socket_responding.rb +176 -0
- data/test/test_conditions_tries.rb +67 -0
- data/test/test_contact.rb +109 -0
- data/test/test_driver.rb +26 -0
- data/test/test_email.rb +34 -0
- data/test/test_event_handler.rb +82 -0
- data/test/test_god.rb +710 -0
- data/test/test_god_system.rb +201 -0
- data/test/test_handlers_kqueue_handler.rb +16 -0
- data/test/test_hipchat.rb +23 -0
- data/test/test_jabber.rb +29 -0
- data/test/test_logger.rb +55 -0
- data/test/test_metric.rb +74 -0
- data/test/test_process.rb +263 -0
- data/test/test_prowl.rb +15 -0
- data/test/test_registry.rb +15 -0
- data/test/test_sensu.rb +11 -0
- data/test/test_slack.rb +57 -0
- data/test/test_socket.rb +34 -0
- data/test/test_statsd.rb +22 -0
- data/test/test_sugar.rb +42 -0
- data/test/test_system_portable_poller.rb +17 -0
- data/test/test_system_process.rb +30 -0
- data/test/test_task.rb +246 -0
- data/test/test_timeline.rb +37 -0
- data/test/test_trigger.rb +63 -0
- data/test/test_watch.rb +286 -0
- data/test/test_webhook.rb +22 -0
- metadata +476 -0
data/lib/god/process.rb
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
module God
|
|
2
|
+
class Process
|
|
3
|
+
WRITES_PID = [:start, :restart]
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :uid, :gid, :log, :log_cmd, :err_log, :err_log_cmd,
|
|
6
|
+
:start, :stop, :restart, :unix_socket, :chroot, :env, :dir,
|
|
7
|
+
:stop_timeout, :stop_signal, :umask
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
self.log = '/dev/null'
|
|
11
|
+
|
|
12
|
+
@pid_file = nil
|
|
13
|
+
@tracking_pid = true
|
|
14
|
+
@user_log = false
|
|
15
|
+
@pid = nil
|
|
16
|
+
@unix_socket = nil
|
|
17
|
+
@log_cmd = nil
|
|
18
|
+
@stop_timeout = God::STOP_TIMEOUT_DEFAULT
|
|
19
|
+
@stop_signal = God::STOP_SIGNAL_DEFAULT
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def alive?
|
|
23
|
+
if self.pid
|
|
24
|
+
System::Process.new(self.pid).exists?
|
|
25
|
+
else
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def file_writable?(file)
|
|
31
|
+
pid = fork do
|
|
32
|
+
begin
|
|
33
|
+
if self.uid
|
|
34
|
+
user_method = self.uid.is_a?(Integer) ? :getpwuid : :getpwnam
|
|
35
|
+
uid_num = Etc.send(user_method, self.uid).uid
|
|
36
|
+
gid_num = Etc.send(user_method, self.uid).gid
|
|
37
|
+
end
|
|
38
|
+
if self.gid
|
|
39
|
+
group_method = self.gid.is_a?(Integer) ? :getgrgid : :getgrnam
|
|
40
|
+
gid_num = Etc.send(group_method, self.gid).gid
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
::Dir.chroot(self.chroot) if self.chroot
|
|
44
|
+
::Process.groups = [gid_num] if gid_num
|
|
45
|
+
::Process.initgroups(self.uid, gid_num) if self.uid && gid_num
|
|
46
|
+
::Process::Sys.setgid(gid_num) if gid_num
|
|
47
|
+
::Process::Sys.setuid(uid_num) if self.uid
|
|
48
|
+
rescue ArgumentError, Errno::EPERM, Errno::ENOENT
|
|
49
|
+
exit(1)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
File.writable?(file_in_chroot(file)) ? exit!(0) : exit!(1)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
wpid, status = ::Process.waitpid2(pid)
|
|
56
|
+
status.exitstatus == 0 ? true : false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def valid?
|
|
60
|
+
# determine if we're tracking pid or not
|
|
61
|
+
self.pid_file
|
|
62
|
+
|
|
63
|
+
valid = true
|
|
64
|
+
|
|
65
|
+
# a start command must be specified
|
|
66
|
+
if self.start.nil?
|
|
67
|
+
valid = false
|
|
68
|
+
applog(self, :error, "No start command was specified")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# uid must exist if specified
|
|
72
|
+
if self.uid
|
|
73
|
+
begin
|
|
74
|
+
Etc.getpwnam(self.uid)
|
|
75
|
+
rescue ArgumentError
|
|
76
|
+
valid = false
|
|
77
|
+
applog(self, :error, "UID for '#{self.uid}' does not exist")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# gid must exist if specified
|
|
82
|
+
if self.gid
|
|
83
|
+
begin
|
|
84
|
+
Etc.getgrnam(self.gid)
|
|
85
|
+
rescue ArgumentError
|
|
86
|
+
valid = false
|
|
87
|
+
applog(self, :error, "GID for '#{self.gid}' does not exist")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# dir must exist and be a directory if specified
|
|
92
|
+
if self.dir
|
|
93
|
+
if !File.exist?(self.dir)
|
|
94
|
+
valid = false
|
|
95
|
+
applog(self, :error, "Specified directory '#{self.dir}' does not exist")
|
|
96
|
+
elsif !File.directory?(self.dir)
|
|
97
|
+
valid = false
|
|
98
|
+
applog(self, :error, "Specified directory '#{self.dir}' is not a directory")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# pid dir must exist if specified
|
|
103
|
+
if !@tracking_pid && !File.exist?(File.dirname(self.pid_file))
|
|
104
|
+
valid = false
|
|
105
|
+
applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# pid dir must be writable if specified
|
|
109
|
+
if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file))
|
|
110
|
+
valid = false
|
|
111
|
+
applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# log dir must exist
|
|
115
|
+
if !File.exist?(File.dirname(self.log))
|
|
116
|
+
valid = false
|
|
117
|
+
applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# log file or dir must be writable
|
|
121
|
+
if File.exist?(self.log)
|
|
122
|
+
unless file_writable?(self.log)
|
|
123
|
+
valid = false
|
|
124
|
+
applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}")
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
unless file_writable?(File.dirname(self.log))
|
|
128
|
+
valid = false
|
|
129
|
+
applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# chroot directory must exist and have /dev/null in it
|
|
134
|
+
if self.chroot
|
|
135
|
+
if !File.directory?(self.chroot)
|
|
136
|
+
valid = false
|
|
137
|
+
applog(self, :error, "CHROOT directory '#{self.chroot}' does not exist")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if !File.exist?(File.join(self.chroot, '/dev/null'))
|
|
141
|
+
valid = false
|
|
142
|
+
applog(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'")
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
valid
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
|
|
150
|
+
# No really, trust me. Use the instance variable.
|
|
151
|
+
def pid_file=(value)
|
|
152
|
+
# if value is nil, do the right thing
|
|
153
|
+
if value
|
|
154
|
+
@tracking_pid = false
|
|
155
|
+
else
|
|
156
|
+
@tracking_pid = true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@pid_file = value
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def pid_file
|
|
163
|
+
@pid_file ||= default_pid_file
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Fetch the PID from pid_file. If the pid_file does not
|
|
167
|
+
# exist, then use the PID from the last time it was read.
|
|
168
|
+
# If it has never been read, then return nil.
|
|
169
|
+
#
|
|
170
|
+
# Returns Integer(pid) or nil
|
|
171
|
+
def pid
|
|
172
|
+
contents = File.read(self.pid_file).strip rescue ''
|
|
173
|
+
real_pid = contents =~ /^\d+$/ ? contents.to_i : nil
|
|
174
|
+
|
|
175
|
+
if real_pid
|
|
176
|
+
@pid = real_pid
|
|
177
|
+
real_pid
|
|
178
|
+
else
|
|
179
|
+
@pid
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Send the given signal to this process.
|
|
184
|
+
#
|
|
185
|
+
# Returns nothing
|
|
186
|
+
def signal(sig)
|
|
187
|
+
sig = sig.to_i if sig.to_i != 0
|
|
188
|
+
applog(self, :info, "#{self.name} sending signal '#{sig}' to pid #{self.pid}")
|
|
189
|
+
::Process.kill(sig, self.pid) rescue nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def start!
|
|
193
|
+
call_action(:start)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def stop!
|
|
197
|
+
call_action(:stop)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def restart!
|
|
201
|
+
call_action(:restart)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def default_pid_file
|
|
205
|
+
File.join(God.pid_file_directory, "#{self.name}.pid")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def call_action(action)
|
|
209
|
+
command = send(action)
|
|
210
|
+
|
|
211
|
+
if action == :stop && command.nil?
|
|
212
|
+
pid = self.pid
|
|
213
|
+
name = self.name
|
|
214
|
+
command = lambda do
|
|
215
|
+
applog(self, :info, "#{self.name} stop: default lambda killer")
|
|
216
|
+
|
|
217
|
+
::Process.kill(@stop_signal, pid) rescue nil
|
|
218
|
+
applog(self, :info, "#{self.name} sent SIG#{@stop_signal}")
|
|
219
|
+
|
|
220
|
+
# Poll to see if it's dead
|
|
221
|
+
pid_not_found = false
|
|
222
|
+
@stop_timeout.times do
|
|
223
|
+
if pid
|
|
224
|
+
begin
|
|
225
|
+
::Process.kill(0, pid)
|
|
226
|
+
rescue Errno::ESRCH
|
|
227
|
+
# It died. Good.
|
|
228
|
+
applog(self, :info, "#{self.name} process stopped")
|
|
229
|
+
return
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
applog(self, :warn, "#{self.name} pid not found in #{self.pid_file}") unless pid_not_found
|
|
233
|
+
pid_not_found = true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
sleep 1
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
::Process.kill('KILL', pid) rescue nil
|
|
240
|
+
applog(self, :warn, "#{self.name} still alive after #{@stop_timeout}s; sent SIGKILL")
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
if command.kind_of?(String)
|
|
245
|
+
pid = nil
|
|
246
|
+
|
|
247
|
+
if [:start, :restart].include?(action) && @tracking_pid
|
|
248
|
+
# double fork god-daemonized processes
|
|
249
|
+
# we don't want to wait for them to finish
|
|
250
|
+
r, w = IO.pipe
|
|
251
|
+
begin
|
|
252
|
+
opid = fork do
|
|
253
|
+
STDOUT.reopen(w)
|
|
254
|
+
r.close
|
|
255
|
+
pid = self.spawn(command)
|
|
256
|
+
puts pid.to_s # send pid back to forker
|
|
257
|
+
exit!(0)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
::Process.waitpid(opid, 0)
|
|
261
|
+
w.close
|
|
262
|
+
pid = r.gets.chomp
|
|
263
|
+
ensure
|
|
264
|
+
# make sure the file descriptors get closed no matter what
|
|
265
|
+
r.close rescue nil
|
|
266
|
+
w.close rescue nil
|
|
267
|
+
end
|
|
268
|
+
else
|
|
269
|
+
# single fork self-daemonizing processes
|
|
270
|
+
# we want to wait for them to finish
|
|
271
|
+
pid = self.spawn(command)
|
|
272
|
+
status = ::Process.waitpid2(pid, 0)
|
|
273
|
+
exit_code = status[1] >> 8
|
|
274
|
+
|
|
275
|
+
if exit_code != 0
|
|
276
|
+
applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
ensure_stop if action == :stop
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
|
|
283
|
+
File.open(default_pid_file, 'w') do |f|
|
|
284
|
+
f.write pid
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
@tracking_pid = true
|
|
288
|
+
@pid_file = default_pid_file
|
|
289
|
+
end
|
|
290
|
+
elsif command.kind_of?(Proc)
|
|
291
|
+
# lambda command
|
|
292
|
+
command.call
|
|
293
|
+
else
|
|
294
|
+
raise NotImplementedError
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Fork/exec the given command, returns immediately
|
|
299
|
+
# +command+ is the String containing the shell command
|
|
300
|
+
#
|
|
301
|
+
# Returns nothing
|
|
302
|
+
def spawn(command)
|
|
303
|
+
fork do
|
|
304
|
+
File.umask self.umask if self.umask
|
|
305
|
+
uid_num = Etc.getpwnam(self.uid).uid if self.uid
|
|
306
|
+
gid_num = Etc.getgrnam(self.gid).gid if self.gid
|
|
307
|
+
gid_num = Etc.getpwnam(self.uid).gid if self.gid.nil? && self.uid
|
|
308
|
+
|
|
309
|
+
::Dir.chroot(self.chroot) if self.chroot
|
|
310
|
+
::Process.setsid
|
|
311
|
+
::Process.groups = [gid_num] if gid_num
|
|
312
|
+
::Process.initgroups(self.uid, gid_num) if self.uid && gid_num
|
|
313
|
+
::Process::Sys.setgid(gid_num) if gid_num
|
|
314
|
+
::Process::Sys.setuid(uid_num) if self.uid
|
|
315
|
+
self.dir ||= '/'
|
|
316
|
+
Dir.chdir self.dir
|
|
317
|
+
$0 = command
|
|
318
|
+
STDIN.reopen "/dev/null"
|
|
319
|
+
if self.log_cmd
|
|
320
|
+
STDOUT.reopen IO.popen(self.log_cmd, "a")
|
|
321
|
+
else
|
|
322
|
+
STDOUT.reopen file_in_chroot(self.log), "a"
|
|
323
|
+
end
|
|
324
|
+
if err_log_cmd
|
|
325
|
+
STDERR.reopen IO.popen(err_log_cmd, "a")
|
|
326
|
+
elsif err_log && (log_cmd || err_log != log)
|
|
327
|
+
STDERR.reopen file_in_chroot(err_log), "a"
|
|
328
|
+
else
|
|
329
|
+
STDERR.reopen STDOUT
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# close any other file descriptors
|
|
333
|
+
3.upto(256){|fd| IO::new(fd).close rescue nil}
|
|
334
|
+
|
|
335
|
+
if self.env && self.env.is_a?(Hash)
|
|
336
|
+
self.env.each do |(key, value)|
|
|
337
|
+
ENV[key] = value.to_s
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
exec command unless command.empty?
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Ensure that a stop command actually stops the process. Force kill
|
|
346
|
+
# if necessary.
|
|
347
|
+
#
|
|
348
|
+
# Returns nothing
|
|
349
|
+
def ensure_stop
|
|
350
|
+
applog(self, :warn, "#{self.name} ensuring stop...")
|
|
351
|
+
|
|
352
|
+
unless self.pid
|
|
353
|
+
applog(self, :warn, "#{self.name} stop called but pid is uknown")
|
|
354
|
+
return
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Poll to see if it's dead
|
|
358
|
+
@stop_timeout.times do
|
|
359
|
+
begin
|
|
360
|
+
::Process.kill(0, self.pid)
|
|
361
|
+
rescue Errno::ESRCH
|
|
362
|
+
# It died. Good.
|
|
363
|
+
return
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
sleep 1
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# last resort
|
|
370
|
+
::Process.kill('KILL', self.pid) rescue nil
|
|
371
|
+
applog(self, :warn, "#{self.name} still alive after #{@stop_timeout}s; sent SIGKILL")
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
private
|
|
375
|
+
def file_in_chroot(file)
|
|
376
|
+
return file unless self.chroot
|
|
377
|
+
|
|
378
|
+
file.gsub(/^#{Regexp.escape(File.expand_path(self.chroot))}/, '')
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
data/lib/god/registry.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module God
|
|
2
|
+
def self.registry
|
|
3
|
+
@registry ||= Registry.new
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Registry
|
|
7
|
+
def initialize
|
|
8
|
+
@storage = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add(item)
|
|
12
|
+
# raise TypeError unless item.is_a? God::Process
|
|
13
|
+
@storage[item.name] = item
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def remove(item)
|
|
17
|
+
@storage.delete(item.name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def size
|
|
21
|
+
@storage.size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def [](name)
|
|
25
|
+
@storage[name]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reset
|
|
29
|
+
@storage.clear
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module God
|
|
2
|
+
|
|
3
|
+
class SimpleLogger
|
|
4
|
+
DEBUG = 2
|
|
5
|
+
INFO = 4
|
|
6
|
+
WARN = 8
|
|
7
|
+
ERROR = 16
|
|
8
|
+
FATAL = 32
|
|
9
|
+
|
|
10
|
+
SEV_LABEL = {DEBUG => 'DEBUG',
|
|
11
|
+
INFO => 'INFO',
|
|
12
|
+
WARN => 'WARN',
|
|
13
|
+
ERROR => 'ERROR',
|
|
14
|
+
FATAL => 'FATAL'}
|
|
15
|
+
|
|
16
|
+
CONSTANT_TO_SYMBOL = { DEBUG => :debug,
|
|
17
|
+
INFO => :info,
|
|
18
|
+
WARN => :warn,
|
|
19
|
+
ERROR => :error,
|
|
20
|
+
FATAL => :fatal }
|
|
21
|
+
|
|
22
|
+
attr_accessor :datetime_format, :level
|
|
23
|
+
|
|
24
|
+
def initialize(io)
|
|
25
|
+
@io = io
|
|
26
|
+
@level = INFO
|
|
27
|
+
@datetime_format = "%Y-%m-%d %H:%M:%S"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def output(level, msg)
|
|
31
|
+
return if level < self.level
|
|
32
|
+
|
|
33
|
+
time = Time.now.strftime(self.datetime_format)
|
|
34
|
+
label = SEV_LABEL[level]
|
|
35
|
+
@io.print("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fatal(msg)
|
|
39
|
+
self.output(FATAL, msg)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def error(msg)
|
|
43
|
+
self.output(ERROR, msg)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def warn(msg)
|
|
47
|
+
self.output(WARN, msg)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def info(msg)
|
|
51
|
+
self.output(INFO, msg)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def debug(msg)
|
|
55
|
+
self.output(DEBUG, msg)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
data/lib/god/socket.rb
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module God
|
|
4
|
+
|
|
5
|
+
# The God::Server oversees the DRb server which dishes out info on this God daemon.
|
|
6
|
+
class Socket
|
|
7
|
+
attr_reader :port
|
|
8
|
+
|
|
9
|
+
# The location of the socket for a given port
|
|
10
|
+
# +port+ is the port number
|
|
11
|
+
#
|
|
12
|
+
# Returns String (file location)
|
|
13
|
+
def self.socket_file(port)
|
|
14
|
+
"/tmp/god.#{port}.sock"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The address of the socket for a given port
|
|
18
|
+
# +port+ is the port number
|
|
19
|
+
#
|
|
20
|
+
# Returns String (drb address)
|
|
21
|
+
def self.socket(port)
|
|
22
|
+
"drbunix://#{self.socket_file(port)}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The location of the socket for this Server
|
|
26
|
+
#
|
|
27
|
+
# Returns String (file location)
|
|
28
|
+
def socket_file
|
|
29
|
+
self.class.socket_file(@port)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The address of the socket for this Server
|
|
33
|
+
#
|
|
34
|
+
# Returns String (drb address)
|
|
35
|
+
def socket
|
|
36
|
+
self.class.socket(@port)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Create a new Server and star the DRb server
|
|
40
|
+
# +port+ is the port on which to start the DRb service (default nil)
|
|
41
|
+
def initialize(port = nil, user = nil, group = nil, perm = nil)
|
|
42
|
+
@port = port
|
|
43
|
+
@user = user
|
|
44
|
+
@group = group
|
|
45
|
+
@perm = perm
|
|
46
|
+
start
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns true
|
|
50
|
+
def ping
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Forward API calls to God
|
|
55
|
+
#
|
|
56
|
+
# Returns whatever the forwarded call returns
|
|
57
|
+
def method_missing(*args, &block)
|
|
58
|
+
God.send(*args, &block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Stop the DRb server and delete the socket file
|
|
62
|
+
#
|
|
63
|
+
# Returns nothing
|
|
64
|
+
def stop
|
|
65
|
+
DRb.stop_service
|
|
66
|
+
FileUtils.rm_f(self.socket_file)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Start the DRb server. Abort if there is already a running god instance
|
|
72
|
+
# on the socket.
|
|
73
|
+
#
|
|
74
|
+
# Returns nothing
|
|
75
|
+
def start
|
|
76
|
+
begin
|
|
77
|
+
@drb ||= DRb.start_service(self.socket, self)
|
|
78
|
+
applog(nil, :info, "Started on #{DRb.uri}")
|
|
79
|
+
rescue Errno::EADDRINUSE
|
|
80
|
+
applog(nil, :info, "Socket already in use")
|
|
81
|
+
server = DRbObject.new(nil, self.socket)
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
Timeout.timeout(5) do
|
|
85
|
+
server.ping
|
|
86
|
+
end
|
|
87
|
+
abort "Socket #{self.socket} already in use by another instance of god"
|
|
88
|
+
rescue StandardError, Timeout::Error
|
|
89
|
+
applog(nil, :info, "Socket is stale, reopening")
|
|
90
|
+
File.delete(self.socket_file) rescue nil
|
|
91
|
+
@drb ||= DRb.start_service(self.socket, self)
|
|
92
|
+
applog(nil, :info, "Started on #{DRb.uri}")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if File.exists?(self.socket_file)
|
|
97
|
+
if @user
|
|
98
|
+
user_method = @user.is_a?(Integer) ? :getpwuid : :getpwnam
|
|
99
|
+
uid = Etc.send(user_method, @user).uid
|
|
100
|
+
gid = Etc.send(user_method, @user).gid
|
|
101
|
+
end
|
|
102
|
+
if @group
|
|
103
|
+
group_method = @group.is_a?(Integer) ? :getgrgid : :getgrnam
|
|
104
|
+
gid = Etc.send(group_method, @group).gid
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
File.chmod(Integer(@perm), socket_file) if @perm
|
|
108
|
+
File.chown(uid, gid, socket_file) if uid or gid
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
data/lib/god/sugar.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class Numeric
|
|
2
|
+
# Public: Units of seconds.
|
|
3
|
+
def seconds
|
|
4
|
+
self
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Public: Units of seconds.
|
|
8
|
+
alias :second :seconds
|
|
9
|
+
|
|
10
|
+
# Public: Units of minutes (60 seconds).
|
|
11
|
+
def minutes
|
|
12
|
+
self * 60
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Public: Units of minutes (60 seconds).
|
|
16
|
+
alias :minute :minutes
|
|
17
|
+
|
|
18
|
+
# Public: Units of hours (3600 seconds).
|
|
19
|
+
def hours
|
|
20
|
+
self * 3600
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Public: Units of hours (3600 seconds).
|
|
24
|
+
alias :hour :hours
|
|
25
|
+
|
|
26
|
+
# Public: Units of days (86400 seconds).
|
|
27
|
+
def days
|
|
28
|
+
self * 86400
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Public: Units of days (86400 seconds).
|
|
32
|
+
alias :day :days
|
|
33
|
+
|
|
34
|
+
# Units of kilobytes.
|
|
35
|
+
def kilobytes
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Units of kilobytes.
|
|
40
|
+
alias :kilobyte :kilobytes
|
|
41
|
+
|
|
42
|
+
# Units of megabytes (1024 kilobytes).
|
|
43
|
+
def megabytes
|
|
44
|
+
self * 1024
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Units of megabytes (1024 kilobytes).
|
|
48
|
+
alias :megabyte :megabytes
|
|
49
|
+
|
|
50
|
+
# Units of gigabytes (1,048,576 kilobytes).
|
|
51
|
+
def gigabytes
|
|
52
|
+
self * (1024 ** 2)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Units of gigabytes (1,048,576 kilobytes).
|
|
56
|
+
alias :gigabyte :gigabytes
|
|
57
|
+
|
|
58
|
+
# Units of percent. e.g. 50.percent.
|
|
59
|
+
def percent
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'syslog'
|
|
3
|
+
|
|
4
|
+
# Ensure that Syslog is open
|
|
5
|
+
begin
|
|
6
|
+
Syslog.open('god')
|
|
7
|
+
rescue RuntimeError
|
|
8
|
+
Syslog.reopen('god')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Syslog.info("Syslog enabled.")
|
|
12
|
+
|
|
13
|
+
module God
|
|
14
|
+
|
|
15
|
+
class SysLogger
|
|
16
|
+
SYMBOL_EQUIVALENTS = { :fatal => Syslog::LOG_CRIT,
|
|
17
|
+
:error => Syslog::LOG_ERR,
|
|
18
|
+
:warn => Syslog::LOG_WARNING,
|
|
19
|
+
:info => Syslog::LOG_INFO,
|
|
20
|
+
:debug => Syslog::LOG_DEBUG }
|
|
21
|
+
|
|
22
|
+
# Set the log level
|
|
23
|
+
# +level+ is the Symbol level to set as maximum. One of:
|
|
24
|
+
# [:fatal | :error | :warn | :info | :debug ]
|
|
25
|
+
#
|
|
26
|
+
# Returns Nothing
|
|
27
|
+
def self.level=(level)
|
|
28
|
+
Syslog.mask = Syslog::LOG_UPTO(SYMBOL_EQUIVALENTS[level])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Log a message to syslog.
|
|
32
|
+
# +level+ is the Symbol level of the message. One of:
|
|
33
|
+
# [:fatal | :error | :warn | :info | :debug ]
|
|
34
|
+
# +text+ is the String text of the message
|
|
35
|
+
#
|
|
36
|
+
# Returns Nothing
|
|
37
|
+
def self.log(level, text)
|
|
38
|
+
Syslog.log(SYMBOL_EQUIVALENTS[level], '%s', text)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
rescue Object => e
|
|
44
|
+
puts "Syslog could not be enabled: #{e.message}"
|
|
45
|
+
end
|