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.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/Announce.txt +135 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +22 -0
  5. data/README.md +33 -0
  6. data/Rakefile +129 -0
  7. data/bin/god +134 -0
  8. data/doc/god.asciidoc +1592 -0
  9. data/doc/intro.asciidoc +20 -0
  10. data/ext/god/.gitignore +5 -0
  11. data/ext/god/extconf.rb +56 -0
  12. data/ext/god/kqueue_handler.c +133 -0
  13. data/ext/god/netlink_handler.c +182 -0
  14. data/lib/god/behavior.rb +52 -0
  15. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  16. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  17. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  18. data/lib/god/cli/command.rb +268 -0
  19. data/lib/god/cli/run.rb +170 -0
  20. data/lib/god/cli/version.rb +23 -0
  21. data/lib/god/compat19.rb +33 -0
  22. data/lib/god/condition.rb +96 -0
  23. data/lib/god/conditions/always.rb +36 -0
  24. data/lib/god/conditions/complex.rb +86 -0
  25. data/lib/god/conditions/cpu_usage.rb +80 -0
  26. data/lib/god/conditions/degrading_lambda.rb +52 -0
  27. data/lib/god/conditions/disk_usage.rb +32 -0
  28. data/lib/god/conditions/file_mtime.rb +28 -0
  29. data/lib/god/conditions/file_touched.rb +44 -0
  30. data/lib/god/conditions/flapping.rb +128 -0
  31. data/lib/god/conditions/http_response_code.rb +184 -0
  32. data/lib/god/conditions/lambda.rb +25 -0
  33. data/lib/god/conditions/memory_usage.rb +82 -0
  34. data/lib/god/conditions/process_exits.rb +66 -0
  35. data/lib/god/conditions/process_running.rb +63 -0
  36. data/lib/god/conditions/socket_responding.rb +142 -0
  37. data/lib/god/conditions/tries.rb +44 -0
  38. data/lib/god/configurable.rb +57 -0
  39. data/lib/god/contact.rb +114 -0
  40. data/lib/god/contacts/airbrake.rb +44 -0
  41. data/lib/god/contacts/campfire.rb +121 -0
  42. data/lib/god/contacts/email.rb +130 -0
  43. data/lib/god/contacts/hipchat.rb +117 -0
  44. data/lib/god/contacts/jabber.rb +75 -0
  45. data/lib/god/contacts/prowl.rb +57 -0
  46. data/lib/god/contacts/scout.rb +55 -0
  47. data/lib/god/contacts/sensu.rb +59 -0
  48. data/lib/god/contacts/slack.rb +98 -0
  49. data/lib/god/contacts/statsd.rb +46 -0
  50. data/lib/god/contacts/twitter.rb +51 -0
  51. data/lib/god/contacts/webhook.rb +74 -0
  52. data/lib/god/driver.rb +238 -0
  53. data/lib/god/errors.rb +24 -0
  54. data/lib/god/event_handler.rb +112 -0
  55. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  56. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  57. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  58. data/lib/god/logger.rb +109 -0
  59. data/lib/god/metric.rb +87 -0
  60. data/lib/god/process.rb +381 -0
  61. data/lib/god/registry.rb +32 -0
  62. data/lib/god/simple_logger.rb +59 -0
  63. data/lib/god/socket.rb +113 -0
  64. data/lib/god/sugar.rb +62 -0
  65. data/lib/god/sys_logger.rb +45 -0
  66. data/lib/god/system/portable_poller.rb +42 -0
  67. data/lib/god/system/process.rb +50 -0
  68. data/lib/god/system/slash_proc_poller.rb +92 -0
  69. data/lib/god/task.rb +552 -0
  70. data/lib/god/timeline.rb +25 -0
  71. data/lib/god/trigger.rb +43 -0
  72. data/lib/god/version.rb +4 -0
  73. data/lib/god/watch.rb +340 -0
  74. data/lib/god.rb +777 -0
  75. data/test/configs/child_events/child_events.god +44 -0
  76. data/test/configs/child_events/simple_server.rb +3 -0
  77. data/test/configs/child_polls/child_polls.god +37 -0
  78. data/test/configs/child_polls/simple_server.rb +12 -0
  79. data/test/configs/complex/complex.god +59 -0
  80. data/test/configs/complex/simple_server.rb +3 -0
  81. data/test/configs/contact/contact.god +118 -0
  82. data/test/configs/contact/simple_server.rb +3 -0
  83. data/test/configs/daemon_events/daemon_events.god +37 -0
  84. data/test/configs/daemon_events/simple_server.rb +8 -0
  85. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  86. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  87. data/test/configs/daemon_polls/simple_server.rb +6 -0
  88. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  89. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  90. data/test/configs/keepalive/keepalive.god +9 -0
  91. data/test/configs/keepalive/keepalive.rb +12 -0
  92. data/test/configs/lifecycle/lifecycle.god +25 -0
  93. data/test/configs/matias/matias.god +50 -0
  94. data/test/configs/real.rb +59 -0
  95. data/test/configs/running_load/running_load.god +16 -0
  96. data/test/configs/stop_options/simple_server.rb +12 -0
  97. data/test/configs/stop_options/stop_options.god +39 -0
  98. data/test/configs/stress/simple_server.rb +3 -0
  99. data/test/configs/stress/stress.god +15 -0
  100. data/test/configs/task/logs/.placeholder +0 -0
  101. data/test/configs/task/task.god +26 -0
  102. data/test/configs/test.rb +61 -0
  103. data/test/configs/usr1_trapper.rb +10 -0
  104. data/test/helper.rb +172 -0
  105. data/test/suite.rb +6 -0
  106. data/test/test_airbrake.rb +14 -0
  107. data/test/test_behavior.rb +18 -0
  108. data/test/test_campfire.rb +22 -0
  109. data/test/test_condition.rb +52 -0
  110. data/test/test_conditions_disk_usage.rb +50 -0
  111. data/test/test_conditions_http_response_code.rb +109 -0
  112. data/test/test_conditions_process_running.rb +40 -0
  113. data/test/test_conditions_socket_responding.rb +176 -0
  114. data/test/test_conditions_tries.rb +67 -0
  115. data/test/test_contact.rb +109 -0
  116. data/test/test_driver.rb +26 -0
  117. data/test/test_email.rb +34 -0
  118. data/test/test_event_handler.rb +82 -0
  119. data/test/test_god.rb +710 -0
  120. data/test/test_god_system.rb +201 -0
  121. data/test/test_handlers_kqueue_handler.rb +16 -0
  122. data/test/test_hipchat.rb +23 -0
  123. data/test/test_jabber.rb +29 -0
  124. data/test/test_logger.rb +55 -0
  125. data/test/test_metric.rb +74 -0
  126. data/test/test_process.rb +263 -0
  127. data/test/test_prowl.rb +15 -0
  128. data/test/test_registry.rb +15 -0
  129. data/test/test_sensu.rb +11 -0
  130. data/test/test_slack.rb +57 -0
  131. data/test/test_socket.rb +34 -0
  132. data/test/test_statsd.rb +22 -0
  133. data/test/test_sugar.rb +42 -0
  134. data/test/test_system_portable_poller.rb +17 -0
  135. data/test/test_system_process.rb +30 -0
  136. data/test/test_task.rb +246 -0
  137. data/test/test_timeline.rb +37 -0
  138. data/test/test_trigger.rb +63 -0
  139. data/test/test_watch.rb +286 -0
  140. data/test/test_webhook.rb +22 -0
  141. metadata +476 -0
@@ -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
@@ -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