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