dguettler-god 0.7.13.2

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