b4cdd69e9e115c423f8b64cedd62f472e712b237
[god.git] / lib / god.rb
blobb4cdd69e9e115c423f8b64cedd62f472e712b237
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.4'
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   
142   class << self
143     # user configurable
144     safe_attr_accessor :pid,
145                        :host,
146                        :port,
147                        :allow,
148                        :log_buffer_size,
149                        :pid_file_directory
150     
151     # internal
152     attr_accessor :inited,
153                   :running,
154                   :pending_watches,
155                   :pending_watch_states,
156                   :server,
157                   :watches,
158                   :groups,
159                   :contacts,
160                   :contact_groups
161   end
162   
163   # initialize class instance variables
164   self.pid = nil
165   self.host = nil
166   self.port = nil
167   self.allow = nil
168   self.log_buffer_size = nil
169   self.pid_file_directory = nil
170   
171   # Initialize internal data.
172   #
173   # Returns nothing
174   def self.internal_init
175     # only do this once
176     return if self.inited
177     
178     # variable init
179     self.watches = {}
180     self.groups = {}
181     self.pending_watches = []
182     self.pending_watch_states = {}
183     self.contacts = {}
184     self.contact_groups = {}
185     
186     # set defaults
187     self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
188     self.pid_file_directory ||= PID_FILE_DIRECTORY_DEFAULT
189     self.port ||= DRB_PORT_DEFAULT
190     self.allow ||= DRB_ALLOW_DEFAULT
191     LOG.level = Logger::INFO
192     
193     # init has been executed
194     self.inited = true
195     
196     # not yet running
197     self.running = false
198   end
199   
200   # Instantiate a new, empty Watch object and pass it to the mandatory
201   # block. The attributes of the watch will be set by the configuration
202   # file.
203   #
204   # Aborts on duplicate watch name
205   #           invalid watch
206   #           conflicting group name
207   #
208   # Returns nothing
209   def self.watch(&block)
210     self.task(Watch, &block)
211   end
212   
213   # Instantiate a new, empty Task object and yield it to the mandatory
214   # block. The attributes of the task will be set by the configuration
215   # file.
216   #
217   # Aborts on duplicate task name
218   #           invalid task
219   #           conflicting group name
220   #
221   # Returns nothing
222   def self.task(klass = Task)
223     self.internal_init
224     
225     t = klass.new
226     yield(t)
227     
228     # do the post-configuration
229     t.prepare
230     
231     # if running, completely remove the watch (if necessary) to
232     # prepare for the reload
233     existing_watch = self.watches[t.name]
234     if self.running && existing_watch
235       self.pending_watch_states[existing_watch.name] = existing_watch.state
236       self.unwatch(existing_watch)
237     end
238     
239     # ensure the new watch has a unique name
240     if self.watches[t.name] || self.groups[t.name]
241       abort "Task name '#{t.name}' already used for a Task or Group"
242     end
243     
244     # ensure watch is internally valid
245     t.valid? || abort("Task '#{t.name}' is not valid (see above)")
246     
247     # add to list of watches
248     self.watches[t.name] = t
249     
250     # add to pending watches
251     self.pending_watches << t
252     
253     # add to group if specified
254     if t.group
255       # ensure group name hasn't been used for a watch already
256       if self.watches[t.group]
257         abort "Group name '#{t.group}' already used for a Task"
258       end
259       
260       self.groups[t.group] ||= []
261       self.groups[t.group] << t
262     end
263     
264     # register watch
265     t.register!
266     
267     # log
268     if self.running && existing_watch
269       applog(t, :info, "#{t.name} Reloaded config")
270     elsif self.running
271       applog(t, :info, "#{t.name} Loaded config")
272     end
273   end
274   
275   # Unmonitor and remove the given watch from god.
276   #   +watch+ is the Watch to remove
277   #
278   # Returns nothing
279   def self.unwatch(watch)
280     # unmonitor
281     watch.unmonitor unless watch.state == :unmonitored
282     
283     # unregister
284     watch.unregister!
285     
286     # remove from watches
287     self.watches.delete(watch.name)
288     
289     # remove from groups
290     if watch.group
291       self.groups[watch.group].delete(watch)
292     end
293     
294     applog(watch, :info, "#{watch.name} unwatched")
295   end
296   
297   # Instantiate a new Contact of the given kind and send it to the block.
298   # Then prepare, validate, and record the Contact.
299   #   +kind+ is the contact class specifier
300   #
301   # Aborts on invalid kind
302   #           duplicate contact name
303   #           invalid contact
304   #           conflicting group name
305   #
306   # Returns nothing
307   def self.contact(kind)
308     self.internal_init
309     
310     # create the contact
311     begin
312       c = Contact.generate(kind)
313     rescue NoSuchContactError => e
314       abort e.message
315     end
316     
317     # send to block so config can set attributes
318     yield(c) if block_given?
319     
320     # call prepare on the contact
321     c.prepare
322     
323     # remove existing contacts of same name
324     existing_contact = self.contacts[c.name]
325     if self.running && existing_contact
326       self.uncontact(existing_contact)
327     end
328     
329     # ensure the new contact has a unique name
330     if self.contacts[c.name] || self.contact_groups[c.name]
331       abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
332     end
333     
334     # abort if the Contact is invalid, the Contact will have printed
335     # out its own error messages by now
336     unless Contact.valid?(c) && c.valid?
337       abort "Exiting on invalid contact"
338     end
339     
340     # add to list of contacts
341     self.contacts[c.name] = c
342     
343     # add to contact group if specified
344     if c.group
345       # ensure group name hasn't been used for a contact already
346       if self.contacts[c.group]
347         abort "Contact Group name '#{c.group}' already used for a Contact"
348       end
349       
350       self.contact_groups[c.group] ||= []
351       self.contact_groups[c.group] << c
352     end
353   end
354   
355   # Remove the given contact from god.
356   #   +contact+ is the Contact to remove
357   #
358   # Returns nothing
359   def self.uncontact(contact)
360     self.contacts.delete(contact.name)
361     if contact.group
362       self.contact_groups[contact.group].delete(contact)
363     end
364   end
365   
366   # Control the lifecycle of the given task(s).
367   #   +name+ is the name of a task/group (String)
368   #   +command+ is the command to run (String)
369   #             one of: "start"
370   #                     "monitor"
371   #                     "restart"
372   #                     "stop"
373   #                     "unmonitor"
374   #                     "remove"
375   #
376   # Returns String[]:task_names
377   def self.control(name, command)
378     # get the list of items
379     items = Array(self.watches[name] || self.groups[name]).dup
380     
381     jobs = []
382     
383     # do the command
384     case command
385       when "start", "monitor"
386         items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
387       when "restart"
388         items.each { |w| jobs << Thread.new { w.move(:restart) } }
389       when "stop"
390         items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
391       when "unmonitor"
392         items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
393       when "remove"
394         items.each { |w| self.unwatch(w) }
395       else
396         raise InvalidCommandError.new
397     end
398     
399     jobs.each { |j| j.join }
400     
401     items.map { |x| x.name }
402   end
403   
404   # Unmonitor and stop all tasks.
405   #
406   # Returns true on success
407   #         false if all tasks could not be stopped within 10 seconds
408   def self.stop_all
409     self.watches.sort.each do |name, w|
410       Thread.new do
411         w.unmonitor if w.state != :unmonitored
412         w.action(:stop) if w.alive?
413       end
414     end
415     
416     10.times do
417       return true unless self.watches.map { |name, w| w.alive? }.any?
418       sleep 1
419     end
420     
421     return false
422   end
423   
424   # Force the termination of god.
425   #   * Clean up pid file if one exists
426   #   * Stop DRb service
427   #   * Hard exit using exit!
428   #
429   # Never returns because the process will no longer exist!
430   def self.terminate
431     FileUtils.rm_f(self.pid) if self.pid
432     self.server.stop if self.server
433     exit!(0)
434   end
435   
436   # Gather the status of each task.
437   #
438   # Examples
439   #   God.status
440   #   # => { 'mongrel' => :up, 'nginx' => :up }
441   #
442   # Returns { String:task_name => Symbol:status, ... }
443   def self.status
444     info = {}
445     self.watches.map do |name, w|
446       info[name] = {:state => w.state}
447     end
448     info
449   end
450   
451   # Log lines for the given task since the specified time.
452   #   +watch_name+ is the name of the task (may be abbreviated)
453   #   +since+ is the Time since which to report log lines
454   #
455   # Raises God::NoSuchWatchError if no tasks matched
456   #
457   # Returns String:joined_log_lines
458   def self.running_log(watch_name, since)
459     matches = pattern_match(watch_name, self.watches.keys)
460     
461     unless matches.first
462       raise NoSuchWatchError.new
463     end
464     
465     LOG.watch_log_since(matches.first, since)
466   end
467   
468   # Load a config file into a running god instance. Rescues any exceptions
469   # that the config may raise and reports these back to the caller.
470   #   +code+ is a String containing the config file
471   #   +filename+ is the filename of the config file
472   #
473   # Returns [String[]:task_names, String:errors]
474   def self.running_load(code, filename)
475     errors = ""
476     watches = []
477     
478     begin
479       LOG.start_capture
480       
481       Gem.clear_paths
482       eval(code, root_binding, filename)
483       self.pending_watches.each do |w|
484         if previous_state = self.pending_watch_states[w.name]
485           w.monitor unless previous_state == :unmonitored
486         else
487           w.monitor if w.autostart?
488         end
489       end
490       watches = self.pending_watches.dup
491       self.pending_watches.clear
492       self.pending_watch_states.clear
493     rescue Exception => e
494       # don't ever let running_load take down god
495       errors << LOG.finish_capture
496       
497       unless e.instance_of?(SystemExit)
498         errors << e.message << "\n"
499         errors << e.backtrace.join("\n")
500       end
501     end
502     
503     names = watches.map { |x| x.name }
504     [names, errors]
505   end
506   
507   # Load the given file(s) according to the given glob.
508   #   +glob+ is the glob-enabled path to load
509   #
510   # Returns nothing
511   def self.load(glob)
512     Dir[glob].each do |f|
513       Kernel.load f
514     end
515   end
516   
517   def self.setup
518     # Make pid directory
519     unless test(?d, self.pid_file_directory)
520       begin
521         FileUtils.mkdir_p(self.pid_file_directory)
522       rescue Errno::EACCES => e
523         abort "Failed to create pid file directory: #{e.message}"
524       end
525     end
526   end
527   
528   def self.validater
529     unless test(?w, self.pid_file_directory)
530       abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
531     end
532   end
533   
534   # Initialize and startup the machinery that makes god work.
535   #
536   # Returns nothing
537   def self.start
538     self.internal_init
539     self.setup
540     self.validater
541     
542     # instantiate server
543     self.server = Socket.new(self.port)
544     
545     # start event handler system
546     EventHandler.start if EventHandler.loaded?
547     
548     # start the timer system
549     Timer.get
550     
551     # start monitoring any watches set to autostart
552     self.watches.values.each { |w| w.monitor if w.autostart? }
553     
554     # clear pending watches
555     self.pending_watches.clear
556     
557     # mark as running
558     self.running = true
559     
560     # join the timer thread so we don't exit
561     Timer.get.join
562   end
563   
564   # To be called on program exit to start god
565   #
566   # Returns nothing
567   def self.at_exit
568     self.start
569   end
570   
571   # private
572   
573   # Match a shortened pattern against a list of String candidates.
574   # The pattern is expanded into a regular expression by
575   # inserting .* between each character.
576   #   +pattern+ is the String containing the abbreviation
577   #   +list+ is the Array of Strings to match against
578   #
579   # Examples
580   #
581   #   list = %w{ foo bar bars }
582   #   pattern = 'br'
583   #   God.pattern_match(list, pattern)
584   #   # => ['bar', 'bars']
585   #
586   # Returns String[]:matched_elements
587   def self.pattern_match(pattern, list)
588     regex = pattern.split('').join('.*')
589     
590     list.select do |item|
591       item =~ Regexp.new(regex)
592     end
593   end
596 # Runs immediately before the program exits. If $run is true,
597 # start god, if $run is false, exit normally.
599 # Returns nothing
600 at_exit do
601   God.at_exit if $run