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