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.
- data/History.txt +261 -0
- data/Manifest.txt +107 -0
- data/README.txt +59 -0
- data/Rakefile +35 -0
- data/bin/god +127 -0
- data/examples/events.god +84 -0
- data/examples/gravatar.god +54 -0
- data/examples/single.god +66 -0
- data/ext/god/extconf.rb +55 -0
- data/ext/god/kqueue_handler.c +123 -0
- data/ext/god/netlink_handler.c +167 -0
- data/init/god +42 -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 +206 -0
- data/lib/god/cli/run.rb +177 -0
- data/lib/god/cli/version.rb +23 -0
- data/lib/god/condition.rb +96 -0
- data/lib/god/conditions/always.rb +23 -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 +27 -0
- data/lib/god/conditions/flapping.rb +128 -0
- data/lib/god/conditions/http_response_code.rb +168 -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 +72 -0
- data/lib/god/conditions/process_running.rb +74 -0
- data/lib/god/conditions/tries.rb +44 -0
- data/lib/god/configurable.rb +57 -0
- data/lib/god/contact.rb +106 -0
- data/lib/god/contacts/email.rb +95 -0
- data/lib/god/dependency_graph.rb +41 -0
- data/lib/god/diagnostics.rb +37 -0
- data/lib/god/driver.rb +206 -0
- data/lib/god/errors.rb +24 -0
- data/lib/god/event_handler.rb +111 -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 +120 -0
- data/lib/god/metric.rb +59 -0
- data/lib/god/process.rb +327 -0
- data/lib/god/registry.rb +32 -0
- data/lib/god/simple_logger.rb +53 -0
- data/lib/god/socket.rb +96 -0
- data/lib/god/sugar.rb +47 -0
- data/lib/god/system/portable_poller.rb +42 -0
- data/lib/god/system/process.rb +42 -0
- data/lib/god/system/slash_proc_poller.rb +82 -0
- data/lib/god/task.rb +487 -0
- data/lib/god/timeline.rb +25 -0
- data/lib/god/trigger.rb +43 -0
- data/lib/god/watch.rb +183 -0
- data/lib/god.rb +644 -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 +74 -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/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/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/helper.rb +151 -0
- data/test/suite.rb +6 -0
- data/test/test_behavior.rb +21 -0
- data/test/test_condition.rb +50 -0
- data/test/test_conditions_disk_usage.rb +56 -0
- data/test/test_conditions_http_response_code.rb +109 -0
- data/test/test_conditions_process_running.rb +44 -0
- data/test/test_conditions_tries.rb +67 -0
- data/test/test_contact.rb +109 -0
- data/test/test_dependency_graph.rb +62 -0
- data/test/test_driver.rb +11 -0
- data/test/test_event_handler.rb +80 -0
- data/test/test_god.rb +598 -0
- data/test/test_handlers_kqueue_handler.rb +16 -0
- data/test/test_logger.rb +63 -0
- data/test/test_metric.rb +72 -0
- data/test/test_process.rb +246 -0
- data/test/test_registry.rb +15 -0
- data/test/test_socket.rb +42 -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 +262 -0
- data/test/test_timeline.rb +37 -0
- data/test/test_trigger.rb +59 -0
- data/test/test_watch.rb +279 -0
- metadata +186 -0
data/lib/god.rb
ADDED
@@ -0,0 +1,644 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
2
|
+
|
3
|
+
# rubygems
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
# core
|
7
|
+
require 'stringio'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'fastthread'
|
12
|
+
rescue LoadError
|
13
|
+
ensure
|
14
|
+
require 'thread'
|
15
|
+
end
|
16
|
+
|
17
|
+
# stdlib
|
18
|
+
|
19
|
+
# internal requires
|
20
|
+
require 'god/errors'
|
21
|
+
require 'god/simple_logger'
|
22
|
+
require 'god/logger'
|
23
|
+
|
24
|
+
require 'god/system/process'
|
25
|
+
require 'god/system/portable_poller'
|
26
|
+
require 'god/system/slash_proc_poller'
|
27
|
+
|
28
|
+
require 'god/dependency_graph'
|
29
|
+
require 'god/timeline'
|
30
|
+
require 'god/configurable'
|
31
|
+
|
32
|
+
require 'god/task'
|
33
|
+
|
34
|
+
require 'god/behavior'
|
35
|
+
require 'god/behaviors/clean_pid_file'
|
36
|
+
require 'god/behaviors/clean_unix_socket'
|
37
|
+
require 'god/behaviors/notify_when_flapping'
|
38
|
+
|
39
|
+
require 'god/condition'
|
40
|
+
require 'god/conditions/process_running'
|
41
|
+
require 'god/conditions/process_exits'
|
42
|
+
require 'god/conditions/tries'
|
43
|
+
require 'god/conditions/memory_usage'
|
44
|
+
require 'god/conditions/cpu_usage'
|
45
|
+
require 'god/conditions/always'
|
46
|
+
require 'god/conditions/lambda'
|
47
|
+
require 'god/conditions/degrading_lambda'
|
48
|
+
require 'god/conditions/flapping'
|
49
|
+
require 'god/conditions/http_response_code'
|
50
|
+
require 'god/conditions/disk_usage'
|
51
|
+
require 'god/conditions/complex'
|
52
|
+
|
53
|
+
require 'god/contact'
|
54
|
+
require 'god/contacts/email'
|
55
|
+
begin
|
56
|
+
require 'god/contacts/jabber'
|
57
|
+
rescue LoadError
|
58
|
+
end
|
59
|
+
|
60
|
+
require 'god/socket'
|
61
|
+
require 'god/driver'
|
62
|
+
|
63
|
+
require 'god/metric'
|
64
|
+
require 'god/watch'
|
65
|
+
|
66
|
+
require 'god/trigger'
|
67
|
+
require 'god/event_handler'
|
68
|
+
require 'god/registry'
|
69
|
+
require 'god/process'
|
70
|
+
|
71
|
+
require 'god/sugar'
|
72
|
+
|
73
|
+
require 'god/cli/version'
|
74
|
+
require 'god/cli/command'
|
75
|
+
|
76
|
+
require 'god/diagnostics'
|
77
|
+
|
78
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
|
79
|
+
|
80
|
+
# App wide logging system
|
81
|
+
LOG = God::Logger.new
|
82
|
+
|
83
|
+
def applog(watch, level, text)
|
84
|
+
LOG.log(watch, level, text)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The $run global determines whether god should be started when the
|
88
|
+
# program would normally end. This should be set to true if when god
|
89
|
+
# should be started (e.g. `god -c <config file>`) and false otherwise
|
90
|
+
# (e.g. `god status`)
|
91
|
+
$run ||= nil
|
92
|
+
|
93
|
+
GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
94
|
+
|
95
|
+
# Return the binding of god's root level
|
96
|
+
def root_binding
|
97
|
+
binding
|
98
|
+
end
|
99
|
+
|
100
|
+
module Kernel
|
101
|
+
alias_method :abort_orig, :abort
|
102
|
+
|
103
|
+
def abort(text = nil)
|
104
|
+
$run = false
|
105
|
+
applog(nil, :error, text) if text
|
106
|
+
exit(1)
|
107
|
+
end
|
108
|
+
|
109
|
+
alias_method :exit_orig, :exit
|
110
|
+
|
111
|
+
def exit(code = 0)
|
112
|
+
$run = false
|
113
|
+
exit_orig(code)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Module
|
118
|
+
def safe_attr_accessor(*args)
|
119
|
+
args.each do |arg|
|
120
|
+
define_method((arg.to_s + "=").intern) do |other|
|
121
|
+
if !self.running && self.inited
|
122
|
+
abort "God.#{arg} must be set before any Tasks are defined"
|
123
|
+
end
|
124
|
+
|
125
|
+
if self.running && self.inited
|
126
|
+
applog(nil, :warn, "God.#{arg} can't be set while god is running")
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
instance_variable_set(('@' + arg.to_s).intern, other)
|
131
|
+
end
|
132
|
+
|
133
|
+
define_method(arg) do
|
134
|
+
instance_variable_get(('@' + arg.to_s).intern)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module God
|
141
|
+
VERSION = '0.7.9'
|
142
|
+
|
143
|
+
LOG_BUFFER_SIZE_DEFAULT = 100
|
144
|
+
PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
|
145
|
+
DRB_PORT_DEFAULT = 17165
|
146
|
+
DRB_ALLOW_DEFAULT = ['127.0.0.1']
|
147
|
+
LOG_LEVEL_DEFAULT = :info
|
148
|
+
|
149
|
+
class << self
|
150
|
+
# user configurable
|
151
|
+
safe_attr_accessor :pid,
|
152
|
+
:host,
|
153
|
+
:port,
|
154
|
+
:allow,
|
155
|
+
:log_buffer_size,
|
156
|
+
:pid_file_directory,
|
157
|
+
:log_file,
|
158
|
+
:log_level,
|
159
|
+
:use_events
|
160
|
+
|
161
|
+
# internal
|
162
|
+
attr_accessor :inited,
|
163
|
+
:running,
|
164
|
+
:pending_watches,
|
165
|
+
:pending_watch_states,
|
166
|
+
:server,
|
167
|
+
:watches,
|
168
|
+
:groups,
|
169
|
+
:contacts,
|
170
|
+
:contact_groups,
|
171
|
+
:main
|
172
|
+
end
|
173
|
+
|
174
|
+
# initialize class instance variables
|
175
|
+
self.pid = nil
|
176
|
+
self.host = nil
|
177
|
+
self.port = nil
|
178
|
+
self.allow = nil
|
179
|
+
self.log_buffer_size = nil
|
180
|
+
self.pid_file_directory = nil
|
181
|
+
self.log_level = nil
|
182
|
+
|
183
|
+
# Initialize internal data.
|
184
|
+
#
|
185
|
+
# Returns nothing
|
186
|
+
def self.internal_init
|
187
|
+
# only do this once
|
188
|
+
return if self.inited
|
189
|
+
|
190
|
+
# variable init
|
191
|
+
self.watches = {}
|
192
|
+
self.groups = {}
|
193
|
+
self.pending_watches = []
|
194
|
+
self.pending_watch_states = {}
|
195
|
+
self.contacts = {}
|
196
|
+
self.contact_groups = {}
|
197
|
+
|
198
|
+
# set defaults
|
199
|
+
self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
|
200
|
+
self.port ||= DRB_PORT_DEFAULT
|
201
|
+
self.allow ||= DRB_ALLOW_DEFAULT
|
202
|
+
self.log_level ||= LOG_LEVEL_DEFAULT
|
203
|
+
|
204
|
+
# additional setup
|
205
|
+
self.setup
|
206
|
+
|
207
|
+
# log level
|
208
|
+
log_level_map = {:debug => Logger::DEBUG,
|
209
|
+
:info => Logger::INFO,
|
210
|
+
:warn => Logger::WARN,
|
211
|
+
:error => Logger::ERROR,
|
212
|
+
:fatal => Logger::FATAL}
|
213
|
+
LOG.level = log_level_map[self.log_level]
|
214
|
+
|
215
|
+
# init has been executed
|
216
|
+
self.inited = true
|
217
|
+
|
218
|
+
# not yet running
|
219
|
+
self.running = false
|
220
|
+
end
|
221
|
+
|
222
|
+
# Instantiate a new, empty Watch object and pass it to the mandatory
|
223
|
+
# block. The attributes of the watch will be set by the configuration
|
224
|
+
# file.
|
225
|
+
#
|
226
|
+
# Aborts on duplicate watch name
|
227
|
+
# invalid watch
|
228
|
+
# conflicting group name
|
229
|
+
#
|
230
|
+
# Returns nothing
|
231
|
+
def self.watch(&block)
|
232
|
+
self.task(Watch, &block)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Instantiate a new, empty Task object and yield it to the mandatory
|
236
|
+
# block. The attributes of the task will be set by the configuration
|
237
|
+
# file.
|
238
|
+
#
|
239
|
+
# Aborts on duplicate task name
|
240
|
+
# invalid task
|
241
|
+
# conflicting group name
|
242
|
+
#
|
243
|
+
# Returns nothing
|
244
|
+
def self.task(klass = Task)
|
245
|
+
self.internal_init
|
246
|
+
|
247
|
+
t = klass.new
|
248
|
+
yield(t)
|
249
|
+
|
250
|
+
# do the post-configuration
|
251
|
+
t.prepare
|
252
|
+
|
253
|
+
# if running, completely remove the watch (if necessary) to
|
254
|
+
# prepare for the reload
|
255
|
+
existing_watch = self.watches[t.name]
|
256
|
+
if self.running && existing_watch
|
257
|
+
self.pending_watch_states[existing_watch.name] = existing_watch.state
|
258
|
+
self.unwatch(existing_watch)
|
259
|
+
end
|
260
|
+
|
261
|
+
# ensure the new watch has a unique name
|
262
|
+
if self.watches[t.name] || self.groups[t.name]
|
263
|
+
abort "Task name '#{t.name}' already used for a Task or Group"
|
264
|
+
end
|
265
|
+
|
266
|
+
# ensure watch is internally valid
|
267
|
+
t.valid? || abort("Task '#{t.name}' is not valid (see above)")
|
268
|
+
|
269
|
+
# add to list of watches
|
270
|
+
self.watches[t.name] = t
|
271
|
+
|
272
|
+
# add to pending watches
|
273
|
+
self.pending_watches << t
|
274
|
+
|
275
|
+
# add to group if specified
|
276
|
+
if t.group
|
277
|
+
# ensure group name hasn't been used for a watch already
|
278
|
+
if self.watches[t.group]
|
279
|
+
abort "Group name '#{t.group}' already used for a Task"
|
280
|
+
end
|
281
|
+
|
282
|
+
self.groups[t.group] ||= []
|
283
|
+
self.groups[t.group] << t
|
284
|
+
end
|
285
|
+
|
286
|
+
# register watch
|
287
|
+
t.register!
|
288
|
+
|
289
|
+
# log
|
290
|
+
if self.running && existing_watch
|
291
|
+
applog(t, :info, "#{t.name} Reloaded config")
|
292
|
+
elsif self.running
|
293
|
+
applog(t, :info, "#{t.name} Loaded config")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Unmonitor and remove the given watch from god.
|
298
|
+
# +watch+ is the Watch to remove
|
299
|
+
#
|
300
|
+
# Returns nothing
|
301
|
+
def self.unwatch(watch)
|
302
|
+
# unmonitor
|
303
|
+
watch.unmonitor unless watch.state == :unmonitored
|
304
|
+
|
305
|
+
# unregister
|
306
|
+
watch.unregister!
|
307
|
+
|
308
|
+
# remove from watches
|
309
|
+
self.watches.delete(watch.name)
|
310
|
+
|
311
|
+
# remove from groups
|
312
|
+
if watch.group
|
313
|
+
self.groups[watch.group].delete(watch)
|
314
|
+
end
|
315
|
+
|
316
|
+
applog(watch, :info, "#{watch.name} unwatched")
|
317
|
+
end
|
318
|
+
|
319
|
+
# Instantiate a new Contact of the given kind and send it to the block.
|
320
|
+
# Then prepare, validate, and record the Contact.
|
321
|
+
# +kind+ is the contact class specifier
|
322
|
+
#
|
323
|
+
# Aborts on invalid kind
|
324
|
+
# duplicate contact name
|
325
|
+
# invalid contact
|
326
|
+
# conflicting group name
|
327
|
+
#
|
328
|
+
# Returns nothing
|
329
|
+
def self.contact(kind)
|
330
|
+
self.internal_init
|
331
|
+
|
332
|
+
# create the contact
|
333
|
+
begin
|
334
|
+
c = Contact.generate(kind)
|
335
|
+
rescue NoSuchContactError => e
|
336
|
+
abort e.message
|
337
|
+
end
|
338
|
+
|
339
|
+
# send to block so config can set attributes
|
340
|
+
yield(c) if block_given?
|
341
|
+
|
342
|
+
# call prepare on the contact
|
343
|
+
c.prepare
|
344
|
+
|
345
|
+
# remove existing contacts of same name
|
346
|
+
existing_contact = self.contacts[c.name]
|
347
|
+
if self.running && existing_contact
|
348
|
+
self.uncontact(existing_contact)
|
349
|
+
end
|
350
|
+
|
351
|
+
# warn and noop if the contact has been defined before
|
352
|
+
if self.contacts[c.name] || self.contact_groups[c.name]
|
353
|
+
applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
|
354
|
+
return
|
355
|
+
end
|
356
|
+
|
357
|
+
# abort if the Contact is invalid, the Contact will have printed
|
358
|
+
# out its own error messages by now
|
359
|
+
unless Contact.valid?(c) && c.valid?
|
360
|
+
abort "Exiting on invalid contact"
|
361
|
+
end
|
362
|
+
|
363
|
+
# add to list of contacts
|
364
|
+
self.contacts[c.name] = c
|
365
|
+
|
366
|
+
# add to contact group if specified
|
367
|
+
if c.group
|
368
|
+
# ensure group name hasn't been used for a contact already
|
369
|
+
if self.contacts[c.group]
|
370
|
+
abort "Contact Group name '#{c.group}' already used for a Contact"
|
371
|
+
end
|
372
|
+
|
373
|
+
self.contact_groups[c.group] ||= []
|
374
|
+
self.contact_groups[c.group] << c
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Remove the given contact from god.
|
379
|
+
# +contact+ is the Contact to remove
|
380
|
+
#
|
381
|
+
# Returns nothing
|
382
|
+
def self.uncontact(contact)
|
383
|
+
self.contacts.delete(contact.name)
|
384
|
+
if contact.group
|
385
|
+
self.contact_groups[contact.group].delete(contact)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Control the lifecycle of the given task(s).
|
390
|
+
# +name+ is the name of a task/group (String)
|
391
|
+
# +command+ is the command to run (String)
|
392
|
+
# one of: "start"
|
393
|
+
# "monitor"
|
394
|
+
# "restart"
|
395
|
+
# "stop"
|
396
|
+
# "unmonitor"
|
397
|
+
# "remove"
|
398
|
+
#
|
399
|
+
# Returns String[]:task_names
|
400
|
+
def self.control(name, command)
|
401
|
+
# get the list of items
|
402
|
+
items = Array(self.watches[name] || self.groups[name]).dup
|
403
|
+
|
404
|
+
jobs = []
|
405
|
+
|
406
|
+
# do the command
|
407
|
+
case command
|
408
|
+
when "start", "monitor"
|
409
|
+
items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
|
410
|
+
when "restart"
|
411
|
+
items.each { |w| jobs << Thread.new { w.move(:restart) } }
|
412
|
+
when "stop"
|
413
|
+
items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
|
414
|
+
when "unmonitor"
|
415
|
+
items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
|
416
|
+
when "remove"
|
417
|
+
items.each { |w| self.unwatch(w) }
|
418
|
+
else
|
419
|
+
raise InvalidCommandError.new
|
420
|
+
end
|
421
|
+
|
422
|
+
jobs.each { |j| j.join }
|
423
|
+
|
424
|
+
items.map { |x| x.name }
|
425
|
+
end
|
426
|
+
|
427
|
+
# Unmonitor and stop all tasks.
|
428
|
+
#
|
429
|
+
# Returns true on success
|
430
|
+
# false if all tasks could not be stopped within 10 seconds
|
431
|
+
def self.stop_all
|
432
|
+
self.watches.sort.each do |name, w|
|
433
|
+
Thread.new do
|
434
|
+
w.unmonitor if w.state != :unmonitored
|
435
|
+
w.action(:stop) if w.alive?
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
10.times do
|
440
|
+
return true unless self.watches.map { |name, w| w.alive? }.any?
|
441
|
+
sleep 1
|
442
|
+
end
|
443
|
+
|
444
|
+
return false
|
445
|
+
end
|
446
|
+
|
447
|
+
# Force the termination of god.
|
448
|
+
# * Clean up pid file if one exists
|
449
|
+
# * Stop DRb service
|
450
|
+
# * Hard exit using exit!
|
451
|
+
#
|
452
|
+
# Never returns because the process will no longer exist!
|
453
|
+
def self.terminate
|
454
|
+
FileUtils.rm_f(self.pid) if self.pid
|
455
|
+
self.server.stop if self.server
|
456
|
+
exit!(0)
|
457
|
+
end
|
458
|
+
|
459
|
+
# Gather the status of each task.
|
460
|
+
#
|
461
|
+
# Examples
|
462
|
+
# God.status
|
463
|
+
# # => { 'mongrel' => :up, 'nginx' => :up }
|
464
|
+
#
|
465
|
+
# Returns { String:task_name => Symbol:status, ... }
|
466
|
+
def self.status
|
467
|
+
info = {}
|
468
|
+
self.watches.map do |name, w|
|
469
|
+
info[name] = {:state => w.state, :group => w.group}
|
470
|
+
end
|
471
|
+
info
|
472
|
+
end
|
473
|
+
|
474
|
+
# Log lines for the given task since the specified time.
|
475
|
+
# +watch_name+ is the name of the task (may be abbreviated)
|
476
|
+
# +since+ is the Time since which to report log lines
|
477
|
+
#
|
478
|
+
# Raises God::NoSuchWatchError if no tasks matched
|
479
|
+
#
|
480
|
+
# Returns String:joined_log_lines
|
481
|
+
def self.running_log(watch_name, since)
|
482
|
+
matches = pattern_match(watch_name, self.watches.keys)
|
483
|
+
|
484
|
+
unless matches.first
|
485
|
+
raise NoSuchWatchError.new
|
486
|
+
end
|
487
|
+
|
488
|
+
LOG.watch_log_since(matches.first, since)
|
489
|
+
end
|
490
|
+
|
491
|
+
# Load a config file into a running god instance. Rescues any exceptions
|
492
|
+
# that the config may raise and reports these back to the caller.
|
493
|
+
# +code+ is a String containing the config file
|
494
|
+
# +filename+ is the filename of the config file
|
495
|
+
#
|
496
|
+
# Returns [String[]:task_names, String:errors]
|
497
|
+
def self.running_load(code, filename)
|
498
|
+
errors = ""
|
499
|
+
watches = []
|
500
|
+
|
501
|
+
begin
|
502
|
+
LOG.start_capture
|
503
|
+
|
504
|
+
Gem.clear_paths
|
505
|
+
eval(code, root_binding, filename)
|
506
|
+
self.pending_watches.each do |w|
|
507
|
+
if previous_state = self.pending_watch_states[w.name]
|
508
|
+
w.monitor unless previous_state == :unmonitored
|
509
|
+
else
|
510
|
+
w.monitor if w.autostart?
|
511
|
+
end
|
512
|
+
end
|
513
|
+
watches = self.pending_watches.dup
|
514
|
+
self.pending_watches.clear
|
515
|
+
self.pending_watch_states.clear
|
516
|
+
rescue Exception => e
|
517
|
+
# don't ever let running_load take down god
|
518
|
+
errors << LOG.finish_capture
|
519
|
+
|
520
|
+
unless e.instance_of?(SystemExit)
|
521
|
+
errors << e.message << "\n"
|
522
|
+
errors << e.backtrace.join("\n")
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
names = watches.map { |x| x.name }
|
527
|
+
[names, errors]
|
528
|
+
end
|
529
|
+
|
530
|
+
# Load the given file(s) according to the given glob.
|
531
|
+
# +glob+ is the glob-enabled path to load
|
532
|
+
#
|
533
|
+
# Returns nothing
|
534
|
+
def self.load(glob)
|
535
|
+
Dir[glob].each do |f|
|
536
|
+
Kernel.load f
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def self.setup
|
541
|
+
if self.pid_file_directory
|
542
|
+
# pid file dir was specified, ensure it is created and writable
|
543
|
+
unless File.exist?(self.pid_file_directory)
|
544
|
+
begin
|
545
|
+
FileUtils.mkdir_p(self.pid_file_directory)
|
546
|
+
rescue Errno::EACCES => e
|
547
|
+
abort "Failed to create pid file directory: #{e.message}"
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
unless File.writable?(self.pid_file_directory)
|
552
|
+
abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
|
553
|
+
end
|
554
|
+
else
|
555
|
+
# no pid file dir specified, try defaults
|
556
|
+
PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
|
557
|
+
dir = File.expand_path(idir)
|
558
|
+
begin
|
559
|
+
FileUtils.mkdir_p(dir)
|
560
|
+
if File.writable?(dir)
|
561
|
+
self.pid_file_directory = dir
|
562
|
+
break
|
563
|
+
end
|
564
|
+
rescue Errno::EACCES => e
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
unless self.pid_file_directory
|
569
|
+
dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
|
570
|
+
abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
|
575
|
+
end
|
576
|
+
|
577
|
+
# Initialize and startup the machinery that makes god work.
|
578
|
+
#
|
579
|
+
# Returns nothing
|
580
|
+
def self.start
|
581
|
+
self.internal_init
|
582
|
+
|
583
|
+
# instantiate server
|
584
|
+
self.server = Socket.new(self.port)
|
585
|
+
|
586
|
+
# start monitoring any watches set to autostart
|
587
|
+
self.watches.values.each { |w| w.monitor if w.autostart? }
|
588
|
+
|
589
|
+
# clear pending watches
|
590
|
+
self.pending_watches.clear
|
591
|
+
|
592
|
+
# mark as running
|
593
|
+
self.running = true
|
594
|
+
|
595
|
+
# don't exit
|
596
|
+
self.main =
|
597
|
+
Thread.new do
|
598
|
+
loop do
|
599
|
+
sleep 60
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
self.main.join
|
604
|
+
end
|
605
|
+
|
606
|
+
# To be called on program exit to start god
|
607
|
+
#
|
608
|
+
# Returns nothing
|
609
|
+
def self.at_exit
|
610
|
+
self.start
|
611
|
+
end
|
612
|
+
|
613
|
+
# private
|
614
|
+
|
615
|
+
# Match a shortened pattern against a list of String candidates.
|
616
|
+
# The pattern is expanded into a regular expression by
|
617
|
+
# inserting .* between each character.
|
618
|
+
# +pattern+ is the String containing the abbreviation
|
619
|
+
# +list+ is the Array of Strings to match against
|
620
|
+
#
|
621
|
+
# Examples
|
622
|
+
#
|
623
|
+
# list = %w{ foo bar bars }
|
624
|
+
# pattern = 'br'
|
625
|
+
# God.pattern_match(list, pattern)
|
626
|
+
# # => ['bar', 'bars']
|
627
|
+
#
|
628
|
+
# Returns String[]:matched_elements
|
629
|
+
def self.pattern_match(pattern, list)
|
630
|
+
regex = pattern.split('').join('.*')
|
631
|
+
|
632
|
+
list.select do |item|
|
633
|
+
item =~ Regexp.new(regex)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# Runs immediately before the program exits. If $run is true,
|
639
|
+
# start god, if $run is false, exit normally.
|
640
|
+
#
|
641
|
+
# Returns nothing
|
642
|
+
at_exit do
|
643
|
+
God.at_exit if $run
|
644
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
God.watch do |w|
|
2
|
+
w.name = "child-events"
|
3
|
+
w.interval = 5.seconds
|
4
|
+
w.start = File.join(GOD_ROOT, *%w[test configs child_events simple_server.rb])
|
5
|
+
# w.log = File.join(GOD_ROOT, *%w[test configs child_events god.log])
|
6
|
+
|
7
|
+
# determine the state on startup
|
8
|
+
w.transition(:init, { true => :up, false => :start }) do |on|
|
9
|
+
on.condition(:process_running) do |c|
|
10
|
+
c.running = true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# determine when process has finished starting
|
15
|
+
w.transition([:start, :restart], :up) do |on|
|
16
|
+
on.condition(:process_running) do |c|
|
17
|
+
c.running = true
|
18
|
+
end
|
19
|
+
|
20
|
+
# failsafe
|
21
|
+
on.condition(:tries) do |c|
|
22
|
+
c.times = 2
|
23
|
+
c.transition = :start
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# start if process is not running
|
28
|
+
w.transition(:up, :start) do |on|
|
29
|
+
on.condition(:process_exits)
|
30
|
+
end
|
31
|
+
|
32
|
+
# lifecycle
|
33
|
+
w.lifecycle do |on|
|
34
|
+
on.condition(:flapping) do |c|
|
35
|
+
c.to_state = [:start, :restart]
|
36
|
+
c.times = 5
|
37
|
+
c.within = 20.seconds
|
38
|
+
c.transition = :unmonitored
|
39
|
+
c.retry_in = 10.seconds
|
40
|
+
c.retry_times = 2
|
41
|
+
c.retry_within = 5.minutes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|