resurrected_god 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/Announce.txt +135 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +22 -0
  5. data/README.md +33 -0
  6. data/Rakefile +129 -0
  7. data/bin/god +134 -0
  8. data/doc/god.asciidoc +1592 -0
  9. data/doc/intro.asciidoc +20 -0
  10. data/ext/god/.gitignore +5 -0
  11. data/ext/god/extconf.rb +56 -0
  12. data/ext/god/kqueue_handler.c +133 -0
  13. data/ext/god/netlink_handler.c +182 -0
  14. data/lib/god/behavior.rb +52 -0
  15. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  16. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  17. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  18. data/lib/god/cli/command.rb +268 -0
  19. data/lib/god/cli/run.rb +170 -0
  20. data/lib/god/cli/version.rb +23 -0
  21. data/lib/god/compat19.rb +33 -0
  22. data/lib/god/condition.rb +96 -0
  23. data/lib/god/conditions/always.rb +36 -0
  24. data/lib/god/conditions/complex.rb +86 -0
  25. data/lib/god/conditions/cpu_usage.rb +80 -0
  26. data/lib/god/conditions/degrading_lambda.rb +52 -0
  27. data/lib/god/conditions/disk_usage.rb +32 -0
  28. data/lib/god/conditions/file_mtime.rb +28 -0
  29. data/lib/god/conditions/file_touched.rb +44 -0
  30. data/lib/god/conditions/flapping.rb +128 -0
  31. data/lib/god/conditions/http_response_code.rb +184 -0
  32. data/lib/god/conditions/lambda.rb +25 -0
  33. data/lib/god/conditions/memory_usage.rb +82 -0
  34. data/lib/god/conditions/process_exits.rb +66 -0
  35. data/lib/god/conditions/process_running.rb +63 -0
  36. data/lib/god/conditions/socket_responding.rb +142 -0
  37. data/lib/god/conditions/tries.rb +44 -0
  38. data/lib/god/configurable.rb +57 -0
  39. data/lib/god/contact.rb +114 -0
  40. data/lib/god/contacts/airbrake.rb +44 -0
  41. data/lib/god/contacts/campfire.rb +121 -0
  42. data/lib/god/contacts/email.rb +130 -0
  43. data/lib/god/contacts/hipchat.rb +117 -0
  44. data/lib/god/contacts/jabber.rb +75 -0
  45. data/lib/god/contacts/prowl.rb +57 -0
  46. data/lib/god/contacts/scout.rb +55 -0
  47. data/lib/god/contacts/sensu.rb +59 -0
  48. data/lib/god/contacts/slack.rb +98 -0
  49. data/lib/god/contacts/statsd.rb +46 -0
  50. data/lib/god/contacts/twitter.rb +51 -0
  51. data/lib/god/contacts/webhook.rb +74 -0
  52. data/lib/god/driver.rb +238 -0
  53. data/lib/god/errors.rb +24 -0
  54. data/lib/god/event_handler.rb +112 -0
  55. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  56. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  57. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  58. data/lib/god/logger.rb +109 -0
  59. data/lib/god/metric.rb +87 -0
  60. data/lib/god/process.rb +381 -0
  61. data/lib/god/registry.rb +32 -0
  62. data/lib/god/simple_logger.rb +59 -0
  63. data/lib/god/socket.rb +113 -0
  64. data/lib/god/sugar.rb +62 -0
  65. data/lib/god/sys_logger.rb +45 -0
  66. data/lib/god/system/portable_poller.rb +42 -0
  67. data/lib/god/system/process.rb +50 -0
  68. data/lib/god/system/slash_proc_poller.rb +92 -0
  69. data/lib/god/task.rb +552 -0
  70. data/lib/god/timeline.rb +25 -0
  71. data/lib/god/trigger.rb +43 -0
  72. data/lib/god/version.rb +4 -0
  73. data/lib/god/watch.rb +340 -0
  74. data/lib/god.rb +777 -0
  75. data/test/configs/child_events/child_events.god +44 -0
  76. data/test/configs/child_events/simple_server.rb +3 -0
  77. data/test/configs/child_polls/child_polls.god +37 -0
  78. data/test/configs/child_polls/simple_server.rb +12 -0
  79. data/test/configs/complex/complex.god +59 -0
  80. data/test/configs/complex/simple_server.rb +3 -0
  81. data/test/configs/contact/contact.god +118 -0
  82. data/test/configs/contact/simple_server.rb +3 -0
  83. data/test/configs/daemon_events/daemon_events.god +37 -0
  84. data/test/configs/daemon_events/simple_server.rb +8 -0
  85. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  86. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  87. data/test/configs/daemon_polls/simple_server.rb +6 -0
  88. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  89. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  90. data/test/configs/keepalive/keepalive.god +9 -0
  91. data/test/configs/keepalive/keepalive.rb +12 -0
  92. data/test/configs/lifecycle/lifecycle.god +25 -0
  93. data/test/configs/matias/matias.god +50 -0
  94. data/test/configs/real.rb +59 -0
  95. data/test/configs/running_load/running_load.god +16 -0
  96. data/test/configs/stop_options/simple_server.rb +12 -0
  97. data/test/configs/stop_options/stop_options.god +39 -0
  98. data/test/configs/stress/simple_server.rb +3 -0
  99. data/test/configs/stress/stress.god +15 -0
  100. data/test/configs/task/logs/.placeholder +0 -0
  101. data/test/configs/task/task.god +26 -0
  102. data/test/configs/test.rb +61 -0
  103. data/test/configs/usr1_trapper.rb +10 -0
  104. data/test/helper.rb +172 -0
  105. data/test/suite.rb +6 -0
  106. data/test/test_airbrake.rb +14 -0
  107. data/test/test_behavior.rb +18 -0
  108. data/test/test_campfire.rb +22 -0
  109. data/test/test_condition.rb +52 -0
  110. data/test/test_conditions_disk_usage.rb +50 -0
  111. data/test/test_conditions_http_response_code.rb +109 -0
  112. data/test/test_conditions_process_running.rb +40 -0
  113. data/test/test_conditions_socket_responding.rb +176 -0
  114. data/test/test_conditions_tries.rb +67 -0
  115. data/test/test_contact.rb +109 -0
  116. data/test/test_driver.rb +26 -0
  117. data/test/test_email.rb +34 -0
  118. data/test/test_event_handler.rb +82 -0
  119. data/test/test_god.rb +710 -0
  120. data/test/test_god_system.rb +201 -0
  121. data/test/test_handlers_kqueue_handler.rb +16 -0
  122. data/test/test_hipchat.rb +23 -0
  123. data/test/test_jabber.rb +29 -0
  124. data/test/test_logger.rb +55 -0
  125. data/test/test_metric.rb +74 -0
  126. data/test/test_process.rb +263 -0
  127. data/test/test_prowl.rb +15 -0
  128. data/test/test_registry.rb +15 -0
  129. data/test/test_sensu.rb +11 -0
  130. data/test/test_slack.rb +57 -0
  131. data/test/test_socket.rb +34 -0
  132. data/test/test_statsd.rb +22 -0
  133. data/test/test_sugar.rb +42 -0
  134. data/test/test_system_portable_poller.rb +17 -0
  135. data/test/test_system_process.rb +30 -0
  136. data/test/test_task.rb +246 -0
  137. data/test/test_timeline.rb +37 -0
  138. data/test/test_trigger.rb +63 -0
  139. data/test/test_watch.rb +286 -0
  140. data/test/test_webhook.rb +22 -0
  141. metadata +476 -0
data/lib/god.rb ADDED
@@ -0,0 +1,777 @@
1
+ # Bail out before loading anything unless this flag is set.
2
+ #
3
+ # We are doing this to guard against bundler autoloading because there is
4
+ # no value in loading god in most processes.
5
+ if $load_god
6
+
7
+ # core
8
+ require 'stringio'
9
+ require 'fileutils'
10
+
11
+ begin
12
+ require 'fastthread'
13
+ rescue LoadError
14
+ ensure
15
+ require 'thread'
16
+ end
17
+
18
+ # stdlib
19
+
20
+ # internal requires
21
+ require 'god/errors'
22
+ require 'god/simple_logger'
23
+ require 'god/logger'
24
+ require 'god/sugar'
25
+
26
+ require 'god/system/process'
27
+ require 'god/system/portable_poller'
28
+ require 'god/system/slash_proc_poller'
29
+
30
+ require 'god/timeline'
31
+ require 'god/configurable'
32
+
33
+ require 'god/task'
34
+
35
+ require 'god/behavior'
36
+ require 'god/behaviors/clean_pid_file'
37
+ require 'god/behaviors/clean_unix_socket'
38
+ require 'god/behaviors/notify_when_flapping'
39
+
40
+ require 'god/condition'
41
+ require 'god/conditions/process_running'
42
+ require 'god/conditions/process_exits'
43
+ require 'god/conditions/tries'
44
+ require 'god/conditions/memory_usage'
45
+ require 'god/conditions/cpu_usage'
46
+ require 'god/conditions/always'
47
+ require 'god/conditions/lambda'
48
+ require 'god/conditions/degrading_lambda'
49
+ require 'god/conditions/flapping'
50
+ require 'god/conditions/http_response_code'
51
+ require 'god/conditions/disk_usage'
52
+ require 'god/conditions/complex'
53
+ require 'god/conditions/file_mtime'
54
+ require 'god/conditions/file_touched'
55
+ require 'god/conditions/socket_responding'
56
+
57
+ require 'god/socket'
58
+ require 'god/driver'
59
+
60
+ require 'god/metric'
61
+ require 'god/watch'
62
+
63
+ require 'god/trigger'
64
+ require 'god/event_handler'
65
+ require 'god/registry'
66
+ require 'god/process'
67
+
68
+ require 'god/cli/version'
69
+ require 'god/cli/command'
70
+
71
+ # ruby 1.8 specific configuration
72
+ if RUBY_VERSION < '1.9'
73
+ $KCODE = 'u'
74
+ end
75
+
76
+ CONTACT_DEPS = {}
77
+ CONTACT_LOAD_SUCCESS = {}
78
+
79
+ def load_contact(name)
80
+ require "god/contacts/#{name}"
81
+ CONTACT_LOAD_SUCCESS[name] = true
82
+ rescue LoadError
83
+ CONTACT_LOAD_SUCCESS[name] = false
84
+ end
85
+
86
+ require 'god/contact'
87
+ load_contact(:campfire)
88
+ load_contact(:hipchat)
89
+ load_contact(:email)
90
+ load_contact(:jabber)
91
+ load_contact(:prowl)
92
+ load_contact(:scout)
93
+ load_contact(:statsd)
94
+ load_contact(:twitter)
95
+ load_contact(:webhook)
96
+ load_contact(:airbrake)
97
+ load_contact(:slack)
98
+ load_contact(:sensu)
99
+
100
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
101
+
102
+ # App wide logging system
103
+ LOG = God::Logger.new
104
+
105
+ def applog(watch, level, text)
106
+ LOG.log(watch, level, text)
107
+ end
108
+
109
+ # The $run global determines whether god should be started when the
110
+ # program would normally end. This should be set to true if when god
111
+ # should be started (e.g. `god -c <config file>`) and false otherwise
112
+ # (e.g. `god status`)
113
+ $run ||= nil
114
+
115
+ GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
116
+
117
+ # Return the binding of god's root level
118
+ def root_binding
119
+ binding
120
+ end
121
+
122
+ module Kernel
123
+ alias_method :abort_orig, :abort
124
+
125
+ def abort(text = nil)
126
+ $run = false
127
+ applog(nil, :error, text) if text
128
+ exit(1)
129
+ end
130
+
131
+ alias_method :exit_orig, :exit
132
+
133
+ def exit(code = 0)
134
+ $run = false
135
+ exit_orig(code)
136
+ end
137
+ end
138
+
139
+ class Module
140
+ def safe_attr_accessor(*args)
141
+ args.each do |arg|
142
+ define_method((arg.to_s + "=").intern) do |other|
143
+ if !self.running && self.inited
144
+ abort "God.#{arg} must be set before any Tasks are defined"
145
+ end
146
+
147
+ if self.running && self.inited
148
+ applog(nil, :warn, "God.#{arg} can't be set while god is running")
149
+ return
150
+ end
151
+
152
+ instance_variable_set(('@' + arg.to_s).intern, other)
153
+ end
154
+
155
+ define_method(arg) do
156
+ instance_variable_get(('@' + arg.to_s).intern)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ module God
163
+ # The String version number for this package.
164
+ VERSION = '0.14.0'
165
+
166
+ # The Integer number of lines of backlog to keep for the logger.
167
+ LOG_BUFFER_SIZE_DEFAULT = 100
168
+
169
+ # An Array of directory paths to be used as the default PID file directory.
170
+ # This list will be searched in order and the first one that has write
171
+ # permissions will be used.
172
+ PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
173
+
174
+ # The default Integer port number for the DRb communcations channel.
175
+ DRB_PORT_DEFAULT = 17165
176
+
177
+ # The default Array of String IPs that will allow DRb communication access.
178
+ DRB_ALLOW_DEFAULT = ['127.0.0.1']
179
+
180
+ # The default Symbol log level.
181
+ LOG_LEVEL_DEFAULT = :info
182
+
183
+ # The default Integer number of seconds to wait for god to terminate when
184
+ # issued the quit command.
185
+ TERMINATE_TIMEOUT_DEFAULT = 10
186
+
187
+ # The default Integer number of seconds to wait for a process to terminate.
188
+ STOP_TIMEOUT_DEFAULT = 10
189
+
190
+ # The default String signal to send for the stop command.
191
+ STOP_SIGNAL_DEFAULT = 'TERM'
192
+
193
+ class << self
194
+ # user configurable
195
+ safe_attr_accessor :pid,
196
+ :host,
197
+ :port,
198
+ :allow,
199
+ :log_buffer_size,
200
+ :pid_file_directory,
201
+ :log_file,
202
+ :log_level,
203
+ :use_events,
204
+ :terminate_timeout,
205
+ :socket_user,
206
+ :socket_group,
207
+ :socket_perms
208
+
209
+ # internal
210
+ attr_accessor :inited,
211
+ :running,
212
+ :pending_watches,
213
+ :pending_watch_states,
214
+ :server,
215
+ :watches,
216
+ :groups,
217
+ :contacts,
218
+ :contact_groups,
219
+ :main
220
+ end
221
+
222
+ # Initialize class instance variables.
223
+ self.pid = nil
224
+ self.host = nil
225
+ self.port = nil
226
+ self.allow = nil
227
+ self.log_buffer_size = nil
228
+ self.pid_file_directory = nil
229
+ self.log_level = nil
230
+ self.terminate_timeout = nil
231
+ self.socket_user = nil
232
+ self.socket_group = nil
233
+ self.socket_perms = 0755
234
+
235
+ # Initialize internal data.
236
+ #
237
+ # Returns nothing.
238
+ def self.internal_init
239
+ # Only do this once.
240
+ return if self.inited
241
+
242
+ # Variable init.
243
+ self.watches = {}
244
+ self.groups = {}
245
+ self.pending_watches = []
246
+ self.pending_watch_states = {}
247
+ self.contacts = {}
248
+ self.contact_groups = {}
249
+
250
+ # Set defaults.
251
+ self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
252
+ self.port ||= DRB_PORT_DEFAULT
253
+ self.allow ||= DRB_ALLOW_DEFAULT
254
+ self.log_level ||= LOG_LEVEL_DEFAULT
255
+ self.terminate_timeout ||= TERMINATE_TIMEOUT_DEFAULT
256
+
257
+ # Additional setup.
258
+ self.setup
259
+
260
+ # Log level.
261
+ log_level_map = { :debug => Logger::DEBUG,
262
+ :info => Logger::INFO,
263
+ :warn => Logger::WARN,
264
+ :error => Logger::ERROR,
265
+ :fatal => Logger::FATAL }
266
+ LOG.level = log_level_map[self.log_level]
267
+
268
+ # Init has been executed.
269
+ self.inited = true
270
+
271
+ # Not yet running.
272
+ self.running = false
273
+ end
274
+
275
+ # Instantiate a new, empty Watch object and pass it to the mandatory block.
276
+ # The attributes of the watch will be set by the configuration file. Aborts
277
+ # on duplicate watch name, invalid watch, or conflicting group name.
278
+ #
279
+ # Returns nothing.
280
+ def self.watch(&block)
281
+ self.task(Watch, &block)
282
+ end
283
+
284
+ # Instantiate a new, empty Task object and yield it to the mandatory block.
285
+ # The attributes of the task will be set by the configuration file. Aborts
286
+ # on duplicate task name, invalid task, or conflicting group name.
287
+ #
288
+ # Returns nothing.
289
+ def self.task(klass = Task)
290
+ # Ensure internal init has run.
291
+ self.internal_init
292
+
293
+ t = klass.new
294
+ yield(t)
295
+
296
+ # Do the post-configuration.
297
+ t.prepare
298
+
299
+ # If running, completely remove the watch (if necessary) to prepare for
300
+ # the reload
301
+ existing_watch = self.watches[t.name]
302
+ if self.running && existing_watch
303
+ self.pending_watch_states[existing_watch.name] = existing_watch.state
304
+ self.unwatch(existing_watch)
305
+ end
306
+
307
+ # Ensure the new watch has a unique name.
308
+ if self.watches[t.name] || self.groups[t.name]
309
+ abort "Task name '#{t.name}' already used for a Task or Group"
310
+ end
311
+
312
+ # Ensure watch is internally valid.
313
+ t.valid? || abort("Task '#{t.name}' is not valid (see above)")
314
+
315
+ # Add to list of watches.
316
+ self.watches[t.name] = t
317
+
318
+ # Add to pending watches.
319
+ self.pending_watches << t
320
+
321
+ # Add to group if specified.
322
+ if t.group
323
+ # Ensure group name hasn't been used for a watch already.
324
+ if self.watches[t.group]
325
+ abort "Group name '#{t.group}' already used for a Task"
326
+ end
327
+
328
+ self.groups[t.group] ||= []
329
+ self.groups[t.group] << t
330
+ end
331
+
332
+ # Register watch.
333
+ t.register!
334
+
335
+ # Log.
336
+ if self.running && existing_watch
337
+ applog(t, :info, "#{t.name} Reloaded config")
338
+ elsif self.running
339
+ applog(t, :info, "#{t.name} Loaded config")
340
+ end
341
+ end
342
+
343
+ # Unmonitor and remove the given watch from god.
344
+ #
345
+ # watch - The Watch to remove.
346
+ #
347
+ # Returns nothing.
348
+ def self.unwatch(watch)
349
+ # Unmonitor.
350
+ watch.unmonitor unless watch.state == :unmonitored
351
+
352
+ # Unregister.
353
+ watch.unregister!
354
+
355
+ # Remove from watches.
356
+ self.watches.delete(watch.name)
357
+
358
+ # Remove from groups.
359
+ if watch.group
360
+ self.groups[watch.group].delete(watch)
361
+ end
362
+
363
+ applog(watch, :info, "#{watch.name} unwatched")
364
+ end
365
+
366
+ # Instantiate a new Contact of the given kind and send it to the block.
367
+ # Then prepare, validate, and record the Contact. Aborts on invalid kind,
368
+ # duplicate contact name, invalid contact, or conflicting group name.
369
+ #
370
+ # kind - The Symbol contact class specifier.
371
+ #
372
+ # Returns nothing.
373
+ def self.contact(kind)
374
+ # Ensure internal init has run.
375
+ self.internal_init
376
+
377
+ # Verify contact has been loaded.
378
+ if CONTACT_LOAD_SUCCESS[kind] == false
379
+ applog(nil, :error, "A required dependency for the #{kind} contact is unavailable.")
380
+ applog(nil, :error, "Run the following commands to install the dependencies:")
381
+ CONTACT_DEPS[kind].each do |d|
382
+ applog(nil, :error, " [sudo] gem install #{d}")
383
+ end
384
+ abort
385
+ end
386
+
387
+ # Create the contact.
388
+ begin
389
+ c = Contact.generate(kind)
390
+ rescue NoSuchContactError => e
391
+ abort e.message
392
+ end
393
+
394
+ # Send to block so config can set attributes.
395
+ yield(c) if block_given?
396
+
397
+ # Call prepare on the contact.
398
+ c.prepare
399
+
400
+ # Remove existing contacts of same name.
401
+ existing_contact = self.contacts[c.name]
402
+ if self.running && existing_contact
403
+ self.uncontact(existing_contact)
404
+ end
405
+
406
+ # Warn and noop if the contact has been defined before.
407
+ if self.contacts[c.name] || self.contact_groups[c.name]
408
+ applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
409
+ return
410
+ end
411
+
412
+ # Abort if the Contact is invalid, the Contact will have printed out its
413
+ # own error messages by now.
414
+ unless Contact.valid?(c) && c.valid?
415
+ abort "Exiting on invalid contact"
416
+ end
417
+
418
+ # Add to list of contacts.
419
+ self.contacts[c.name] = c
420
+
421
+ # Add to contact group if specified.
422
+ if c.group
423
+ # Ensure group name hasn't been used for a contact already.
424
+ if self.contacts[c.group]
425
+ abort "Contact Group name '#{c.group}' already used for a Contact"
426
+ end
427
+
428
+ self.contact_groups[c.group] ||= []
429
+ self.contact_groups[c.group] << c
430
+ end
431
+ end
432
+
433
+ # Remove the given contact from god.
434
+ #
435
+ # contact - The Contact to remove.
436
+ #
437
+ # Returns nothing.
438
+ def self.uncontact(contact)
439
+ self.contacts.delete(contact.name)
440
+ if contact.group
441
+ self.contact_groups[contact.group].delete(contact)
442
+ end
443
+ end
444
+
445
+ def self.watches_by_name(name)
446
+ case name
447
+ when "", nil then self.watches.values.dup
448
+ else Array(self.watches[name] || self.groups[name]).dup
449
+ end
450
+ end
451
+
452
+ # Control the lifecycle of the given task(s).
453
+ #
454
+ # name - The String name of a task/group. If empty, invokes command for all tasks.
455
+ # command - The String command to run. Valid commands are:
456
+ # "start", "monitor", "restart", "stop", "unmonitor", "remove".
457
+ #
458
+ # Returns an Array of String task names affected by the command.
459
+ def self.control(name, command)
460
+ # Get the list of items.
461
+ items = self.watches_by_name(name)
462
+
463
+ jobs = []
464
+
465
+ # Do the command.
466
+ case command
467
+ when "start", "monitor"
468
+ items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
469
+ when "restart"
470
+ items.each { |w| jobs << Thread.new { w.move(:restart) } }
471
+ when "stop"
472
+ items.each { |w| jobs << Thread.new { w.action(:stop); w.unmonitor if w.state != :unmonitored } }
473
+ when "unmonitor"
474
+ items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
475
+ when "remove"
476
+ items.each { |w| self.unwatch(w) }
477
+ else
478
+ raise InvalidCommandError.new
479
+ end
480
+
481
+ jobs.each { |j| j.join }
482
+
483
+ items.map { |x| x.name }
484
+ end
485
+
486
+ # Unmonitor and stop all tasks.
487
+ #
488
+ # Returns true on success, false if all tasks could not be stopped within 10
489
+ # seconds
490
+ def self.stop_all
491
+ self.watches.sort.each do |name, w|
492
+ Thread.new do
493
+ w.action(:stop)
494
+ w.unmonitor if w.state != :unmonitored
495
+ end
496
+ end
497
+
498
+ terminate_timeout.times do
499
+ return true unless self.watches.map { |name, w| w.alive? }.any?
500
+ sleep 1
501
+ end
502
+
503
+ return false
504
+ end
505
+
506
+ # Force the termination of god.
507
+ # * Clean up pid file if one exists
508
+ # * Stop DRb service
509
+ # * Hard exit using exit!
510
+ #
511
+ # Never returns because the process will no longer exist!
512
+ def self.terminate
513
+ FileUtils.rm_f(self.pid) if self.pid
514
+ self.server.stop if self.server
515
+ exit!(0)
516
+ end
517
+
518
+ # Gather the status of each task.
519
+ #
520
+ # Examples
521
+ #
522
+ # God.status
523
+ # # => { 'mongrel' => :up, 'nginx' => :up }
524
+ #
525
+ # Returns a Hash where the key is the String task name and the value is the
526
+ # Symbol status.
527
+ def self.status
528
+ info = {}
529
+ self.watches.map do |name, w|
530
+ info[name] = { :state => w.state, :group => w.group }
531
+ end
532
+ info
533
+ end
534
+
535
+ # Send a signal to each task.
536
+ #
537
+ # name - The String name of the task or group.
538
+ # signal - The String or integer signal to send. e.g. 'HUP', 9.
539
+ #
540
+ # Returns an Array of String names of the tasks affected.
541
+ def self.signal(name, signal)
542
+ items = watches_by_name(name)
543
+ jobs = []
544
+ items.each { |w| jobs << Thread.new { w.signal(signal) } }
545
+ jobs.each { |j| j.join }
546
+ items.map { |x| x.name }
547
+ end
548
+
549
+ # Log lines for the given task since the specified time.
550
+ #
551
+ # watch_name - The String name of the task (may be abbreviated).
552
+ # since - The Time since which to report log lines.
553
+ #
554
+ # Raises God::NoSuchWatchError if no tasks matched.
555
+ # Returns the String of newline separated log lines.
556
+ def self.running_log(watch_name, since)
557
+ matches = pattern_match(watch_name, self.watches.keys)
558
+
559
+ unless matches.first
560
+ raise NoSuchWatchError.new
561
+ end
562
+
563
+ LOG.watch_log_since(matches.first, since)
564
+ end
565
+
566
+ # Load a config file into a running god instance. Rescues any exceptions
567
+ # that the config may raise and reports these back to the caller.
568
+ #
569
+ # code - The String config file contents.
570
+ # filename - The filename of the config file.
571
+ # action - The optional String command specifying how to deal with
572
+ # existing watches. Valid options are: 'stop', 'remove' or
573
+ # 'leave' (default).
574
+ #
575
+ # Returns a three-tuple Array [loaded_names, errors, unloaded_names] where:
576
+ # loaded_names - The Array of String task names that were loaded.
577
+ # errors - The String of error messages produced during the
578
+ # load phase. Will be a blank String if no errors
579
+ # were encountered.
580
+ # unloaded_names - The Array of String task names that were unloaded
581
+ # from the system (if 'remove' or 'stop' was
582
+ # specified as the action).
583
+ def self.running_load(code, filename, action = nil)
584
+ errors = ""
585
+ loaded_watches = []
586
+ unloaded_watches = []
587
+ jobs = []
588
+
589
+ begin
590
+ LOG.start_capture
591
+
592
+ Gem.clear_paths
593
+ eval(code, root_binding, filename)
594
+ self.pending_watches.each do |w|
595
+ if previous_state = self.pending_watch_states[w.name]
596
+ w.monitor unless previous_state == :unmonitored
597
+ else
598
+ w.monitor if w.autostart?
599
+ end
600
+ end
601
+ loaded_watches = self.pending_watches.map { |w| w.name }
602
+ self.pending_watches.clear
603
+ self.pending_watch_states.clear
604
+
605
+ self.watches.each do |name, watch|
606
+ next if loaded_watches.include?(name)
607
+
608
+ case action
609
+ when 'stop'
610
+ jobs << Thread.new(watch) { |w| w.action(:stop); self.unwatch(w) }
611
+ unloaded_watches << name
612
+ when 'remove'
613
+ jobs << Thread.new(watch) { |w| self.unwatch(w) }
614
+ unloaded_watches << name
615
+ when 'leave', '', nil
616
+ # Do nothing
617
+ else
618
+ raise InvalidCommandError, "Unknown action: #{action}"
619
+ end
620
+ end
621
+
622
+ # Make sure we quit capturing when we're done.
623
+ LOG.finish_capture
624
+ rescue Exception => e
625
+ # Don't ever let running_load take down god.
626
+ errors << LOG.finish_capture
627
+
628
+ unless e.instance_of?(SystemExit)
629
+ errors << e.message << "\n"
630
+ errors << e.backtrace.join("\n")
631
+ end
632
+ end
633
+
634
+ jobs.each { |t| t.join }
635
+
636
+ [loaded_watches, errors, unloaded_watches]
637
+ end
638
+
639
+ # Load the given file(s) according to the given glob.
640
+ #
641
+ # glob - The glob-enabled String path to load.
642
+ #
643
+ # Returns nothing.
644
+ def self.load(glob)
645
+ Dir[glob].each do |f|
646
+ Kernel.load f
647
+ end
648
+ end
649
+
650
+ # Setup pid file directory and log system.
651
+ #
652
+ # Returns nothing.
653
+ def self.setup
654
+ if self.pid_file_directory
655
+ # Pid file dir was specified, ensure it is created and writable.
656
+ unless File.exist?(self.pid_file_directory)
657
+ begin
658
+ FileUtils.mkdir_p(self.pid_file_directory)
659
+ rescue Errno::EACCES => e
660
+ abort "Failed to create pid file directory: #{e.message}"
661
+ end
662
+ end
663
+
664
+ unless File.writable?(self.pid_file_directory)
665
+ abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
666
+ end
667
+ else
668
+ # No pid file dir specified, try defaults.
669
+ PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
670
+ dir = File.expand_path(idir)
671
+ begin
672
+ FileUtils.mkdir_p(dir)
673
+ if File.writable?(dir)
674
+ self.pid_file_directory = dir
675
+ break
676
+ end
677
+ rescue Errno::EACCES => e
678
+ end
679
+ end
680
+
681
+ unless self.pid_file_directory
682
+ dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
683
+ abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
684
+ end
685
+ end
686
+
687
+ if God::Logger.syslog
688
+ LOG.info("Syslog enabled.")
689
+ else
690
+ LOG.info("Syslog disabled.")
691
+ end
692
+
693
+ applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
694
+ end
695
+
696
+ # Initialize and startup the machinery that makes god work.
697
+ #
698
+ # Returns nothing.
699
+ def self.start
700
+ self.internal_init
701
+
702
+ # Instantiate server.
703
+ self.server = Socket.new(self.port, self.socket_user, self.socket_group, self.socket_perms)
704
+
705
+ # Start monitoring any watches set to autostart.
706
+ self.watches.values.each { |w| w.monitor if w.autostart? }
707
+
708
+ # Clear pending watches.
709
+ self.pending_watches.clear
710
+
711
+ # Mark as running.
712
+ self.running = true
713
+
714
+ # Don't exit.
715
+ self.main =
716
+ Thread.new do
717
+ loop do
718
+ sleep 60
719
+ end
720
+ end
721
+ end
722
+
723
+ # Prevent god from exiting.
724
+ #
725
+ # Returns nothing.
726
+ def self.join
727
+ self.main.join if self.main
728
+ end
729
+
730
+ # Returns the version String.
731
+ def self.version
732
+ God::VERSION
733
+ end
734
+
735
+ # To be called on program exit to start god.
736
+ #
737
+ # Returns nothing.
738
+ def self.at_exit
739
+ self.start
740
+ self.join
741
+ end
742
+
743
+ # private
744
+
745
+ # Match a shortened pattern against a list of String candidates.
746
+ # The pattern is expanded into a regular expression by
747
+ # inserting .* between each character.
748
+ #
749
+ # pattern - The String containing the abbreviation.
750
+ # list - The Array of Strings to match against.
751
+ #
752
+ # Examples
753
+ #
754
+ # list = %w{ foo bar bars }
755
+ # pattern = 'br'
756
+ # God.pattern_match(list, pattern)
757
+ # # => ['bar', 'bars']
758
+ #
759
+ # Returns the Array of matching name Strings.
760
+ def self.pattern_match(pattern, list)
761
+ regex = pattern.split('').join('.*')
762
+
763
+ list.select do |item|
764
+ item =~ Regexp.new(regex)
765
+ end.sort_by { |x| x.size }
766
+ end
767
+ end
768
+
769
+ # Runs immediately before the program exits. If $run is true,
770
+ # start god, if $run is false, exit normally.
771
+ #
772
+ # Returns nothing.
773
+ at_exit do
774
+ God.at_exit if $run
775
+ end
776
+
777
+ end