dosire-god 0.7.9

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 (108) hide show
  1. data/History.txt +261 -0
  2. data/Manifest.txt +107 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +127 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god/behavior.rb +52 -0
  14. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  15. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  16. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  17. data/lib/god/cli/command.rb +206 -0
  18. data/lib/god/cli/run.rb +177 -0
  19. data/lib/god/cli/version.rb +23 -0
  20. data/lib/god/condition.rb +96 -0
  21. data/lib/god/conditions/always.rb +23 -0
  22. data/lib/god/conditions/complex.rb +86 -0
  23. data/lib/god/conditions/cpu_usage.rb +80 -0
  24. data/lib/god/conditions/degrading_lambda.rb +52 -0
  25. data/lib/god/conditions/disk_usage.rb +27 -0
  26. data/lib/god/conditions/flapping.rb +128 -0
  27. data/lib/god/conditions/http_response_code.rb +168 -0
  28. data/lib/god/conditions/lambda.rb +25 -0
  29. data/lib/god/conditions/memory_usage.rb +82 -0
  30. data/lib/god/conditions/process_exits.rb +72 -0
  31. data/lib/god/conditions/process_running.rb +74 -0
  32. data/lib/god/conditions/tries.rb +44 -0
  33. data/lib/god/configurable.rb +57 -0
  34. data/lib/god/contact.rb +106 -0
  35. data/lib/god/contacts/email.rb +95 -0
  36. data/lib/god/dependency_graph.rb +41 -0
  37. data/lib/god/diagnostics.rb +37 -0
  38. data/lib/god/driver.rb +206 -0
  39. data/lib/god/errors.rb +24 -0
  40. data/lib/god/event_handler.rb +111 -0
  41. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  42. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  43. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  44. data/lib/god/logger.rb +120 -0
  45. data/lib/god/metric.rb +59 -0
  46. data/lib/god/process.rb +327 -0
  47. data/lib/god/registry.rb +32 -0
  48. data/lib/god/simple_logger.rb +53 -0
  49. data/lib/god/socket.rb +96 -0
  50. data/lib/god/sugar.rb +47 -0
  51. data/lib/god/system/portable_poller.rb +42 -0
  52. data/lib/god/system/process.rb +42 -0
  53. data/lib/god/system/slash_proc_poller.rb +82 -0
  54. data/lib/god/task.rb +487 -0
  55. data/lib/god/timeline.rb +25 -0
  56. data/lib/god/trigger.rb +43 -0
  57. data/lib/god/watch.rb +183 -0
  58. data/lib/god.rb +644 -0
  59. data/test/configs/child_events/child_events.god +44 -0
  60. data/test/configs/child_events/simple_server.rb +3 -0
  61. data/test/configs/child_polls/child_polls.god +37 -0
  62. data/test/configs/child_polls/simple_server.rb +12 -0
  63. data/test/configs/complex/complex.god +59 -0
  64. data/test/configs/complex/simple_server.rb +3 -0
  65. data/test/configs/contact/contact.god +74 -0
  66. data/test/configs/contact/simple_server.rb +3 -0
  67. data/test/configs/daemon_events/daemon_events.god +37 -0
  68. data/test/configs/daemon_events/simple_server.rb +8 -0
  69. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  70. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  71. data/test/configs/daemon_polls/simple_server.rb +6 -0
  72. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  73. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  74. data/test/configs/matias/matias.god +50 -0
  75. data/test/configs/real.rb +59 -0
  76. data/test/configs/running_load/running_load.god +16 -0
  77. data/test/configs/stress/simple_server.rb +3 -0
  78. data/test/configs/stress/stress.god +15 -0
  79. data/test/configs/task/logs/.placeholder +0 -0
  80. data/test/configs/task/task.god +26 -0
  81. data/test/configs/test.rb +61 -0
  82. data/test/helper.rb +151 -0
  83. data/test/suite.rb +6 -0
  84. data/test/test_behavior.rb +21 -0
  85. data/test/test_condition.rb +50 -0
  86. data/test/test_conditions_disk_usage.rb +56 -0
  87. data/test/test_conditions_http_response_code.rb +109 -0
  88. data/test/test_conditions_process_running.rb +44 -0
  89. data/test/test_conditions_tries.rb +67 -0
  90. data/test/test_contact.rb +109 -0
  91. data/test/test_dependency_graph.rb +62 -0
  92. data/test/test_driver.rb +11 -0
  93. data/test/test_event_handler.rb +80 -0
  94. data/test/test_god.rb +598 -0
  95. data/test/test_handlers_kqueue_handler.rb +16 -0
  96. data/test/test_logger.rb +63 -0
  97. data/test/test_metric.rb +72 -0
  98. data/test/test_process.rb +246 -0
  99. data/test/test_registry.rb +15 -0
  100. data/test/test_socket.rb +42 -0
  101. data/test/test_sugar.rb +42 -0
  102. data/test/test_system_portable_poller.rb +17 -0
  103. data/test/test_system_process.rb +30 -0
  104. data/test/test_task.rb +262 -0
  105. data/test/test_timeline.rb +37 -0
  106. data/test/test_trigger.rb +59 -0
  107. data/test/test_watch.rb +279 -0
  108. metadata +186 -0
@@ -0,0 +1,13 @@
1
+ require 'netlink_handler_ext'
2
+
3
+ module God
4
+ class NetlinkHandler
5
+ EVENT_SYSTEM = "netlink"
6
+
7
+ def self.register_process(pid, events)
8
+ # netlink doesn't need to do this
9
+ # it just reads from the eventhandler actions to see if the pid
10
+ # matches the list we're looking for -- Kev
11
+ end
12
+ end
13
+ end
data/lib/god/logger.rb ADDED
@@ -0,0 +1,120 @@
1
+ module God
2
+
3
+ class Logger < SimpleLogger
4
+ SYSLOG_EQUIVALENTS = {:fatal => :crit,
5
+ :error => :err,
6
+ :warn => :debug,
7
+ :info => :debug,
8
+ :debug => :debug}
9
+
10
+ attr_accessor :logs
11
+
12
+ class << self
13
+ attr_accessor :syslog
14
+ end
15
+
16
+ self.syslog ||= true
17
+
18
+ # Instantiate a new Logger object
19
+ def initialize
20
+ super($stdout)
21
+ self.logs = {}
22
+ @mutex = Mutex.new
23
+ @capture = nil
24
+ @templogio = StringIO.new
25
+ @templog = SimpleLogger.new(@templogio)
26
+ @templog.level = Logger::INFO
27
+ load_syslog
28
+ end
29
+
30
+ # If Logger.syslog is true then attempt to load the syslog bindings. If syslog
31
+ # cannot be loaded, then set Logger.syslog to false and continue.
32
+ #
33
+ # Returns nothing
34
+ def load_syslog
35
+ return unless Logger.syslog
36
+
37
+ begin
38
+ require 'syslog'
39
+
40
+ # Ensure that Syslog is open
41
+ begin
42
+ Syslog.open('god')
43
+ rescue RuntimeError
44
+ Syslog.reopen('god')
45
+ end
46
+ rescue Exception
47
+ Logger.syslog = false
48
+ end
49
+ end
50
+
51
+ # Log a message
52
+ # +watch+ is the String name of the Watch (may be nil if not Watch is applicable)
53
+ # +level+ is the log level [:debug|:info|:warn|:error|:fatal]
54
+ # +text+ is the String message
55
+ #
56
+ # Returns nothing
57
+ def log(watch, level, text)
58
+ # initialize watch log if necessary
59
+ self.logs[watch.name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) if watch
60
+
61
+ # push onto capture and timeline for the given watch
62
+ @templogio.truncate(0)
63
+ @templogio.rewind
64
+ @templog.send(level, text % [])
65
+ @mutex.synchronize do
66
+ @capture.puts(@templogio.string.dup) if @capture
67
+ self.logs[watch.name] << [Time.now, @templogio.string.dup] if watch
68
+ end
69
+
70
+ # send to regular logger
71
+ self.send(level, text % [])
72
+
73
+ # send to syslog
74
+ Syslog.send(SYSLOG_EQUIVALENTS[level], text) if Logger.syslog
75
+ end
76
+
77
+ # Get all log output for a given Watch since a certain Time.
78
+ # +watch_name+ is the String name of the Watch
79
+ # +since+ is the Time since which to fetch log lines
80
+ #
81
+ # Returns String
82
+ def watch_log_since(watch_name, since)
83
+ # initialize watch log if necessary
84
+ self.logs[watch_name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT)
85
+
86
+ # get and join lines since given time
87
+ @mutex.synchronize do
88
+ self.logs[watch_name].select do |x|
89
+ x.first > since
90
+ end.map do |x|
91
+ x[1]
92
+ end.join
93
+ end
94
+ end
95
+
96
+ # private
97
+
98
+ # Enable capturing of log
99
+ #
100
+ # Returns nothing
101
+ def start_capture
102
+ @mutex.synchronize do
103
+ @capture = StringIO.new
104
+ end
105
+ end
106
+
107
+ # Disable capturing of log and return what was captured since
108
+ # capturing was enabled with Logger#start_capture
109
+ #
110
+ # Returns String
111
+ def finish_capture
112
+ @mutex.synchronize do
113
+ cap = @capture.string
114
+ @capture = nil
115
+ cap
116
+ end
117
+ end
118
+ end
119
+
120
+ end
data/lib/god/metric.rb ADDED
@@ -0,0 +1,59 @@
1
+ module God
2
+
3
+ class Metric
4
+ attr_accessor :watch, :destination, :conditions
5
+
6
+ def initialize(watch, destination = nil)
7
+ self.watch = watch
8
+ self.destination = destination
9
+ self.conditions = []
10
+ end
11
+
12
+ # Instantiate a Condition of type +kind+ and pass it into the optional
13
+ # block. Attributes of the condition must be set in the config file
14
+ def condition(kind)
15
+ # create the condition
16
+ begin
17
+ c = Condition.generate(kind, self.watch)
18
+ rescue NoSuchConditionError => e
19
+ abort e.message
20
+ end
21
+
22
+ # send to block so config can set attributes
23
+ yield(c) if block_given?
24
+
25
+ # call prepare on the condition
26
+ c.prepare
27
+
28
+ # test generic and specific validity
29
+ unless Condition.valid?(c) && c.valid?
30
+ abort "Exiting on invalid condition"
31
+ end
32
+
33
+ # inherit interval from watch if no poll condition specific interval was set
34
+ if c.kind_of?(PollCondition) && !c.interval
35
+ if self.watch.interval
36
+ c.interval = self.watch.interval
37
+ else
38
+ abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.watch.name}', and no default Watch interval from which to inherit"
39
+ end
40
+ end
41
+
42
+ # remember
43
+ self.conditions << c
44
+ end
45
+
46
+ def enable
47
+ self.conditions.each do |c|
48
+ self.watch.attach(c)
49
+ end
50
+ end
51
+
52
+ def disable
53
+ self.conditions.each do |c|
54
+ self.watch.detach(c)
55
+ end
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,327 @@
1
+ module God
2
+ class Process
3
+ WRITES_PID = [:start, :restart]
4
+
5
+ attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart, :unix_socket, :chroot, :env
6
+
7
+ def initialize
8
+ self.log = '/dev/null'
9
+
10
+ @pid_file = nil
11
+ @tracking_pid = true
12
+ @user_log = false
13
+ @pid = nil
14
+ @unix_socket = nil
15
+ end
16
+
17
+ def alive?
18
+ if self.pid
19
+ System::Process.new(self.pid).exists?
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def file_writable?(file)
26
+ pid = fork do
27
+ uid_num = Etc.getpwnam(self.uid).uid if self.uid
28
+ gid_num = Etc.getgrnam(self.gid).gid if self.gid
29
+
30
+ ::Dir.chroot(self.chroot) if self.chroot
31
+ ::Process.groups = [gid_num] if self.gid
32
+ ::Process::Sys.setgid(gid_num) if self.gid
33
+ ::Process::Sys.setuid(uid_num) if self.uid
34
+
35
+ File.writable?(file_in_chroot(file)) ? exit(0) : exit(1)
36
+ end
37
+
38
+ wpid, status = ::Process.waitpid2(pid)
39
+ status.exitstatus == 0 ? true : false
40
+ end
41
+
42
+ def valid?
43
+ # determine if we're tracking pid or not
44
+ self.pid_file
45
+
46
+ valid = true
47
+
48
+ # a start command must be specified
49
+ if self.start.nil?
50
+ valid = false
51
+ applog(self, :error, "No start command was specified")
52
+ end
53
+
54
+ # self-daemonizing processes must specify a stop command
55
+ if !@tracking_pid && self.stop.nil?
56
+ valid = false
57
+ applog(self, :error, "No stop command was specified")
58
+ end
59
+
60
+ # uid must exist if specified
61
+ if self.uid
62
+ begin
63
+ Etc.getpwnam(self.uid)
64
+ rescue ArgumentError
65
+ valid = false
66
+ applog(self, :error, "UID for '#{self.uid}' does not exist")
67
+ end
68
+ end
69
+
70
+ # gid must exist if specified
71
+ if self.gid
72
+ begin
73
+ Etc.getgrnam(self.gid)
74
+ rescue ArgumentError
75
+ valid = false
76
+ applog(self, :error, "GID for '#{self.gid}' does not exist")
77
+ end
78
+ end
79
+
80
+ # pid dir must exist if specified
81
+ if !@tracking_pid && !File.exist?(File.dirname(self.pid_file))
82
+ valid = false
83
+ applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist")
84
+ end
85
+
86
+ # pid dir must be writable if specified
87
+ if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file))
88
+ valid = false
89
+ applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}")
90
+ end
91
+
92
+ # log dir must exist
93
+ if !File.exist?(File.dirname(self.log))
94
+ valid = false
95
+ applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist")
96
+ end
97
+
98
+ # log file or dir must be writable
99
+ if File.exist?(self.log)
100
+ unless file_writable?(self.log)
101
+ valid = false
102
+ applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}")
103
+ end
104
+ else
105
+ unless file_writable?(File.dirname(self.log))
106
+ valid = false
107
+ applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}")
108
+ end
109
+ end
110
+
111
+ # chroot directory must exist and have /dev/null in it
112
+ if self.chroot
113
+ if !File.directory?(self.chroot)
114
+ valid = false
115
+ LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not exist")
116
+ end
117
+
118
+ if !File.exist?(File.join(self.chroot, '/dev/null'))
119
+ valid = false
120
+ LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'")
121
+ end
122
+ end
123
+
124
+ valid
125
+ end
126
+
127
+ # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
128
+ # No really, trust me. Use the instance variable.
129
+ def pid_file=(value)
130
+ # if value is nil, do the right thing
131
+ if value
132
+ @tracking_pid = false
133
+ else
134
+ @tracking_pid = true
135
+ end
136
+
137
+ @pid_file = value
138
+ end
139
+
140
+ def pid_file
141
+ @pid_file ||= default_pid_file
142
+ end
143
+
144
+ # Fetch the PID from pid_file. If the pid_file does not
145
+ # exist, then use the PID from the last time it was read.
146
+ # If it has never been read, then return nil.
147
+ #
148
+ # Returns Integer(pid) or nil
149
+ def pid
150
+ contents = File.read(self.pid_file).strip rescue ''
151
+ real_pid = contents =~ /^\d+$/ ? contents.to_i : nil
152
+
153
+ if real_pid
154
+ @pid = real_pid
155
+ real_pid
156
+ else
157
+ @pid
158
+ end
159
+ end
160
+
161
+ def start!
162
+ call_action(:start)
163
+ end
164
+
165
+ def stop!
166
+ call_action(:stop)
167
+ end
168
+
169
+ def restart!
170
+ call_action(:restart)
171
+ end
172
+
173
+ def default_pid_file
174
+ File.join(God.pid_file_directory, "#{self.name}.pid")
175
+ end
176
+
177
+ def call_action(action)
178
+ command = send(action)
179
+
180
+ if action == :stop && command.nil?
181
+ pid = self.pid
182
+ name = self.name
183
+ command = lambda do
184
+ applog(self, :info, "#{self.name} stop: default lambda killer")
185
+
186
+ ::Process.kill('TERM', pid) rescue nil
187
+ applog(self, :info, "#{self.name} sent SIGTERM")
188
+
189
+ # Poll to see if it's dead
190
+ 5.times do
191
+ begin
192
+ ::Process.kill(0, pid)
193
+ rescue Errno::ESRCH
194
+ # It died. Good.
195
+ applog(self, :info, "#{self.name} process stopped")
196
+ return
197
+ end
198
+
199
+ sleep 1
200
+ end
201
+
202
+ ::Process.kill('KILL', pid) rescue nil
203
+ applog(self, :info, "#{self.name} still alive; sent SIGKILL")
204
+ end
205
+ end
206
+
207
+ if command.kind_of?(String)
208
+ pid = nil
209
+
210
+ if @tracking_pid
211
+ # double fork god-daemonized processes
212
+ # we don't want to wait for them to finish
213
+ r, w = IO.pipe
214
+ begin
215
+ opid = fork do
216
+ STDOUT.reopen(w)
217
+ r.close
218
+ pid = self.spawn(command)
219
+ puts pid.to_s # send pid back to forker
220
+ end
221
+
222
+ ::Process.waitpid(opid, 0)
223
+ w.close
224
+ pid = r.gets.chomp
225
+ ensure
226
+ # make sure the file descriptors get closed no matter what
227
+ r.close rescue nil
228
+ w.close rescue nil
229
+ end
230
+ else
231
+ # single fork self-daemonizing processes
232
+ # we want to wait for them to finish
233
+ pid = self.spawn(command)
234
+ status = ::Process.waitpid2(pid, 0)
235
+ exit_code = status[1] >> 8
236
+
237
+ if exit_code != 0
238
+ applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}")
239
+ end
240
+
241
+ ensure_stop if action == :stop
242
+ end
243
+
244
+ if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
245
+ File.open(default_pid_file, 'w') do |f|
246
+ f.write pid
247
+ end
248
+
249
+ @tracking_pid = true
250
+ @pid_file = default_pid_file
251
+ end
252
+ elsif command.kind_of?(Proc)
253
+ # lambda command
254
+ command.call
255
+ else
256
+ raise NotImplementedError
257
+ end
258
+ end
259
+
260
+ # Fork/exec the given command, returns immediately
261
+ # +command+ is the String containing the shell command
262
+ #
263
+ # Returns nothing
264
+ def spawn(command)
265
+ fork do
266
+ uid_num = Etc.getpwnam(self.uid).uid if self.uid
267
+ gid_num = Etc.getgrnam(self.gid).gid if self.gid
268
+
269
+ ::Dir.chroot(self.chroot) if self.chroot
270
+ ::Process.setsid
271
+ ::Process.groups = [gid_num] if self.gid
272
+ ::Process::Sys.setgid(gid_num) if self.gid
273
+ ::Process::Sys.setuid(uid_num) if self.uid
274
+ Dir.chdir "/"
275
+ $0 = command
276
+ STDIN.reopen "/dev/null"
277
+ STDOUT.reopen file_in_chroot(self.log), "a"
278
+ STDERR.reopen STDOUT
279
+
280
+ # close any other file descriptors
281
+ 3.upto(256){|fd| IO::new(fd).close rescue nil}
282
+
283
+ if self.env && self.env.is_a?(Hash)
284
+ self.env.each do |(key, value)|
285
+ ENV[key] = value
286
+ end
287
+ end
288
+
289
+ exec command unless command.empty?
290
+ end
291
+ end
292
+
293
+ # Ensure that a stop command actually stops the process. Force kill
294
+ # if necessary.
295
+ #
296
+ # Returns nothing
297
+ def ensure_stop
298
+ unless self.pid
299
+ applog(self, :warn, "#{self.name} stop called but pid is uknown")
300
+ return
301
+ end
302
+
303
+ # Poll to see if it's dead
304
+ 10.times do
305
+ begin
306
+ ::Process.kill(0, self.pid)
307
+ rescue Errno::ESRCH
308
+ # It died. Good.
309
+ return
310
+ end
311
+
312
+ sleep 1
313
+ end
314
+
315
+ # last resort
316
+ ::Process.kill('KILL', self.pid) rescue nil
317
+ applog(self, :warn, "#{self.name} process still running 10 seconds after stop command returned. Force killing.")
318
+ end
319
+
320
+ private
321
+ def file_in_chroot(file)
322
+ return file unless self.chroot
323
+
324
+ file.gsub(/^#{Regexp.escape(File.expand_path(self.chroot))}/, '')
325
+ end
326
+ end
327
+ 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,53 @@
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
+ attr_accessor :datetime_format, :level
17
+
18
+ def initialize(io)
19
+ @io = io
20
+ @level = INFO
21
+ @datetime_format = "%Y-%m-%d %H:%M:%S"
22
+ end
23
+
24
+ def output(level, msg)
25
+ return if level < self.level
26
+
27
+ time = Time.now.strftime(self.datetime_format)
28
+ label = SEV_LABEL[level]
29
+ @io.print("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}\n")
30
+ end
31
+
32
+ def fatal(msg)
33
+ self.output(FATAL, msg)
34
+ end
35
+
36
+ def error(msg)
37
+ self.output(ERROR, msg)
38
+ end
39
+
40
+ def warn(msg)
41
+ self.output(WARN, msg)
42
+ end
43
+
44
+ def info(msg)
45
+ self.output(INFO, msg)
46
+ end
47
+
48
+ def debug(msg)
49
+ self.output(DEBUG, msg)
50
+ end
51
+ end
52
+
53
+ end
data/lib/god/socket.rb ADDED
@@ -0,0 +1,96 @@
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)
42
+ @port = port
43
+ start
44
+ end
45
+
46
+ # Returns true
47
+ def ping
48
+ true
49
+ end
50
+
51
+ # Forward API calls to God
52
+ #
53
+ # Returns whatever the forwarded call returns
54
+ def method_missing(*args, &block)
55
+ God.send(*args, &block)
56
+ end
57
+
58
+ # Stop the DRb server and delete the socket file
59
+ #
60
+ # Returns nothing
61
+ def stop
62
+ DRb.stop_service
63
+ FileUtils.rm_f(self.socket_file)
64
+ end
65
+
66
+ private
67
+
68
+ # Start the DRb server. Abort if there is already a running god instance
69
+ # on the socket.
70
+ #
71
+ # Returns nothing
72
+ def start
73
+ begin
74
+ @drb ||= DRb.start_service(self.socket, self)
75
+ applog(nil, :info, "Started on #{DRb.uri}")
76
+ rescue Errno::EADDRINUSE
77
+ applog(nil, :info, "Socket already in use")
78
+ DRb.start_service
79
+ server = DRbObject.new(nil, self.socket)
80
+
81
+ begin
82
+ Timeout.timeout(5) do
83
+ server.ping
84
+ end
85
+ abort "Socket #{self.socket} already in use by another instance of god"
86
+ rescue StandardError, Timeout::Error
87
+ applog(nil, :info, "Socket is stale, reopening")
88
+ File.delete(self.socket_file) rescue nil
89
+ @drb ||= DRb.start_service(self.socket, self)
90
+ applog(nil, :info, "Started on #{DRb.uri}")
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ end