eric-god 0.7.7

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 +255 -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 +108 -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 +325 -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
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,325 @@
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::Sys.setgid(gid_num) if self.gid
32
+ ::Process::Sys.setuid(uid_num) if self.uid
33
+
34
+ File.writable?(file) ? exit(0) : exit(1)
35
+ end
36
+
37
+ wpid, status = ::Process.waitpid2(pid)
38
+ status.exitstatus == 0 ? true : false
39
+ end
40
+
41
+ def valid?
42
+ # determine if we're tracking pid or not
43
+ self.pid_file
44
+
45
+ valid = true
46
+
47
+ # a start command must be specified
48
+ if self.start.nil?
49
+ valid = false
50
+ applog(self, :error, "No start command was specified")
51
+ end
52
+
53
+ # self-daemonizing processes must specify a stop command
54
+ if !@tracking_pid && self.stop.nil?
55
+ valid = false
56
+ applog(self, :error, "No stop command was specified")
57
+ end
58
+
59
+ # uid must exist if specified
60
+ if self.uid
61
+ begin
62
+ Etc.getpwnam(self.uid)
63
+ rescue ArgumentError
64
+ valid = false
65
+ applog(self, :error, "UID for '#{self.uid}' does not exist")
66
+ end
67
+ end
68
+
69
+ # gid must exist if specified
70
+ if self.gid
71
+ begin
72
+ Etc.getgrnam(self.gid)
73
+ rescue ArgumentError
74
+ valid = false
75
+ applog(self, :error, "GID for '#{self.gid}' does not exist")
76
+ end
77
+ end
78
+
79
+ # pid dir must exist if specified
80
+ if !@tracking_pid && !File.exist?(File.dirname(self.pid_file))
81
+ valid = false
82
+ applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist")
83
+ end
84
+
85
+ # pid dir must be writable if specified
86
+ if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file))
87
+ valid = false
88
+ applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}")
89
+ end
90
+
91
+ # log dir must exist
92
+ if !File.exist?(File.dirname(self.log))
93
+ valid = false
94
+ applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist")
95
+ end
96
+
97
+ # log file or dir must be writable
98
+ if File.exist?(self.log)
99
+ unless file_writable?(self.log)
100
+ valid = false
101
+ applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}")
102
+ end
103
+ else
104
+ unless file_writable?(File.dirname(self.log))
105
+ valid = false
106
+ applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}")
107
+ end
108
+ end
109
+
110
+ # chroot directory must exist and have /dev/null in it
111
+ if self.chroot
112
+ if !File.directory?(self.chroot)
113
+ valid = false
114
+ LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not exist")
115
+ end
116
+
117
+ if !File.exist?(File.join(self.chroot, '/dev/null'))
118
+ valid = false
119
+ LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'")
120
+ end
121
+ end
122
+
123
+ valid
124
+ end
125
+
126
+ # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
127
+ # No really, trust me. Use the instance variable.
128
+ def pid_file=(value)
129
+ # if value is nil, do the right thing
130
+ if value
131
+ @tracking_pid = false
132
+ else
133
+ @tracking_pid = true
134
+ end
135
+
136
+ @pid_file = value
137
+ end
138
+
139
+ def pid_file
140
+ @pid_file ||= default_pid_file
141
+ end
142
+
143
+ # Fetch the PID from pid_file. If the pid_file does not
144
+ # exist, then use the PID from the last time it was read.
145
+ # If it has never been read, then return nil.
146
+ #
147
+ # Returns Integer(pid) or nil
148
+ def pid
149
+ contents = File.read(self.pid_file).strip rescue ''
150
+ real_pid = contents =~ /^\d+$/ ? contents.to_i : nil
151
+
152
+ if real_pid
153
+ @pid = real_pid
154
+ real_pid
155
+ else
156
+ @pid
157
+ end
158
+ end
159
+
160
+ def start!
161
+ call_action(:start)
162
+ end
163
+
164
+ def stop!
165
+ call_action(:stop)
166
+ end
167
+
168
+ def restart!
169
+ call_action(:restart)
170
+ end
171
+
172
+ def default_pid_file
173
+ File.join(God.pid_file_directory, "#{self.name}.pid")
174
+ end
175
+
176
+ def call_action(action)
177
+ command = send(action)
178
+
179
+ if action == :stop && command.nil?
180
+ pid = self.pid
181
+ name = self.name
182
+ command = lambda do
183
+ applog(self, :info, "#{self.name} stop: default lambda killer")
184
+
185
+ ::Process.kill('TERM', pid) rescue nil
186
+ applog(self, :info, "#{self.name} sent SIGTERM")
187
+
188
+ # Poll to see if it's dead
189
+ 5.times do
190
+ begin
191
+ ::Process.kill(0, pid)
192
+ rescue Errno::ESRCH
193
+ # It died. Good.
194
+ applog(self, :info, "#{self.name} process stopped")
195
+ return
196
+ end
197
+
198
+ sleep 1
199
+ end
200
+
201
+ ::Process.kill('KILL', pid) rescue nil
202
+ applog(self, :info, "#{self.name} still alive; sent SIGKILL")
203
+ end
204
+ end
205
+
206
+ if command.kind_of?(String)
207
+ pid = nil
208
+
209
+ if @tracking_pid
210
+ # double fork god-daemonized processes
211
+ # we don't want to wait for them to finish
212
+ r, w = IO.pipe
213
+ begin
214
+ opid = fork do
215
+ STDOUT.reopen(w)
216
+ r.close
217
+ pid = self.spawn(command)
218
+ puts pid.to_s # send pid back to forker
219
+ end
220
+
221
+ ::Process.waitpid(opid, 0)
222
+ w.close
223
+ pid = r.gets.chomp
224
+ ensure
225
+ # make sure the file descriptors get closed no matter what
226
+ r.close rescue nil
227
+ w.close rescue nil
228
+ end
229
+ else
230
+ # single fork self-daemonizing processes
231
+ # we want to wait for them to finish
232
+ pid = self.spawn(command)
233
+ status = ::Process.waitpid2(pid, 0)
234
+ exit_code = status[1] >> 8
235
+
236
+ if exit_code != 0
237
+ applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}")
238
+ end
239
+
240
+ ensure_stop if action == :stop
241
+ end
242
+
243
+ if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
244
+ File.open(default_pid_file, 'w') do |f|
245
+ f.write pid
246
+ end
247
+
248
+ @tracking_pid = true
249
+ @pid_file = default_pid_file
250
+ end
251
+ elsif command.kind_of?(Proc)
252
+ # lambda command
253
+ command.call
254
+ else
255
+ raise NotImplementedError
256
+ end
257
+ end
258
+
259
+ # Fork/exec the given command, returns immediately
260
+ # +command+ is the String containing the shell command
261
+ #
262
+ # Returns nothing
263
+ def spawn(command)
264
+ fork do
265
+ uid_num = Etc.getpwnam(self.uid).uid if self.uid
266
+ gid_num = Etc.getgrnam(self.gid).gid if self.gid
267
+
268
+ ::Dir.chroot(self.chroot) if self.chroot
269
+ ::Process.setsid
270
+ ::Process::Sys.setgid(gid_num) if self.gid
271
+ ::Process::Sys.setuid(uid_num) if self.uid
272
+ Dir.chdir "/"
273
+ $0 = command
274
+ STDIN.reopen "/dev/null"
275
+ STDOUT.reopen file_in_chroot(self.log), "a"
276
+ STDERR.reopen STDOUT
277
+
278
+ # close any other file descriptors
279
+ 3.upto(256){|fd| IO::new(fd).close rescue nil}
280
+
281
+ if self.env && self.env.is_a?(Hash)
282
+ self.env.each do |(key, value)|
283
+ ENV[key] = value
284
+ end
285
+ end
286
+
287
+ exec command unless command.empty?
288
+ end
289
+ end
290
+
291
+ # Ensure that a stop command actually stops the process. Force kill
292
+ # if necessary.
293
+ #
294
+ # Returns nothing
295
+ def ensure_stop
296
+ unless self.pid
297
+ applog(self, :warn, "#{self.name} stop called but pid is uknown")
298
+ return
299
+ end
300
+
301
+ # Poll to see if it's dead
302
+ 10.times do
303
+ begin
304
+ ::Process.kill(0, self.pid)
305
+ rescue Errno::ESRCH
306
+ # It died. Good.
307
+ return
308
+ end
309
+
310
+ sleep 1
311
+ end
312
+
313
+ # last resort
314
+ ::Process.kill('KILL', self.pid) rescue nil
315
+ applog(self, :warn, "#{self.name} process still running 10 seconds after stop command returned. Force killing.")
316
+ end
317
+
318
+ private
319
+ def file_in_chroot(file)
320
+ return file unless self.chroot
321
+
322
+ file.gsub(/^#{Regexp.escape(File.expand_path(self.chroot))}/, '')
323
+ end
324
+ end
325
+ 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