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