up version to 0.5.1
[god.git] / lib / god.rb
blobe7e5e592c711fe520838cd13e6c732b69ba83cf3
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'
36 require 'god/contact'
37 require 'god/contacts/email'
39 require 'god/socket'
40 require 'god/timer'
41 require 'god/hub'
43 require 'god/metric'
44 require 'god/watch'
46 require 'god/trigger'
47 require 'god/event_handler'
48 require 'god/registry'
49 require 'god/process'
51 require 'god/sugar'
53 $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
55 LOG = God::Logger.new
56 LOG.datetime_format = "%Y-%m-%d %H:%M:%S "
58 # The $run global determines whether god should be started when the
59 # program would normally end. This should be set to true if when god
60 # should be started (e.g. `god -c <config file>`) and false otherwise
61 # (e.g. `god status`)
62 $run ||= nil
64 GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
66 begin
67   Syslog.open('god')
68 rescue RuntimeError
69   Syslog.reopen('god')
70 end
72 # Return the binding of god's root level
73 def root_binding
74   binding
75 end
77 God::EventHandler.load
79 module Kernel
80   alias_method :abort_orig, :abort
81   
82   def abort(text = nil)
83     $run = false
84     LOG.log(nil, :error, text) if text
85     text ? abort_orig(text) : exit(1)
86   end
87   
88   alias_method :exit_orig, :exit
89   
90   def exit(code = 0)
91     $run = false
92     exit_orig(code)
93   end
94 end
96 class Module
97   def safe_attr_accessor(*args)
98     args.each do |arg|
99       define_method((arg.to_s + "=").intern) do |other|
100         if !self.running && self.inited
101           abort "God.#{arg} must be set before any Tasks are defined"
102         end
103         
104         if self.running && self.inited
105           LOG.log(nil, :warn, "God.#{arg} can't be set while god is running")
106           return
107         end
108         
109         instance_variable_set(('@' + arg.to_s).intern, other)
110       end
111       
112       define_method(arg) do
113         instance_variable_get(('@' + arg.to_s).intern)
114       end
115     end
116   end
119 module God
120   VERSION = '0.5.1'
121   
122   LOG_BUFFER_SIZE_DEFAULT = 1000
123   PID_FILE_DIRECTORY_DEFAULT = '/var/run/god'
124   DRB_PORT_DEFAULT = 17165
125   DRB_ALLOW_DEFAULT = ['127.0.0.1']
126   
127   class << self
128     # user configurable
129     safe_attr_accessor :host,
130                        :port,
131                        :allow,
132                        :log_buffer_size,
133                        :pid_file_directory
134     
135     # internal
136     attr_accessor :inited,
137                   :running,
138                   :pending_watches,
139                   :pending_watch_states,
140                   :server,
141                   :watches,
142                   :groups,
143                   :contacts,
144                   :contact_groups
145   end
146   
147   # initialize class instance variables
148   self.host = nil
149   self.port = nil
150   self.allow = nil
151   self.log_buffer_size = nil
152   self.pid_file_directory = nil
153   
154   def self.internal_init
155     # only do this once
156     return if self.inited
157     
158     # variable init
159     self.watches = {}
160     self.groups = {}
161     self.pending_watches = []
162     self.pending_watch_states = {}
163     self.contacts = {}
164     self.contact_groups = {}
165     
166     # set defaults
167     self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
168     self.pid_file_directory ||= PID_FILE_DIRECTORY_DEFAULT
169     self.port ||= DRB_PORT_DEFAULT
170     self.allow ||= DRB_ALLOW_DEFAULT
171     LOG.level = Logger::INFO
172     
173     # init has been executed
174     self.inited = true
175     
176     # not yet running
177     self.running = false
178   end
179   
180   # Instantiate a new, empty Watch object and pass it to the mandatory
181   # block. The attributes of the watch will be set by the configuration
182   # file.
183   def self.watch(&block)
184     self.task(Watch, &block)
185   end
186   
187   # Instantiate a new, empty Task object and pass it to the mandatory
188   # block. The attributes of the task will be set by the configuration
189   # file.
190   def self.task(klass = Task)
191     self.internal_init
192     
193     t = klass.new
194     yield(t)
195     
196     # do the post-configuration
197     t.prepare
198     
199     # if running, completely remove the watch (if necessary) to
200     # prepare for the reload
201     existing_watch = self.watches[t.name]
202     if self.running && existing_watch
203       self.pending_watch_states[existing_watch.name] = existing_watch.state
204       self.unwatch(existing_watch)
205     end
206     
207     # ensure the new watch has a unique name
208     if self.watches[t.name] || self.groups[t.name]
209       abort "Task name '#{t.name}' already used for a Task or Group"
210     end
211     
212     # ensure watch is internally valid
213     t.valid? || abort("Task '#{t.name}' is not valid (see above)")
214     
215     # add to list of watches
216     self.watches[t.name] = t
217     
218     # add to pending watches
219     self.pending_watches << t
220     
221     # add to group if specified
222     if t.group
223       # ensure group name hasn't been used for a watch already
224       if self.watches[t.group]
225         abort "Group name '#{t.group}' already used for a Task"
226       end
227     
228       self.groups[t.group] ||= []
229       self.groups[t.group] << t
230     end
232     # register watch
233     t.register!
234     
235     # log
236     if self.running && existing_watch
237       LOG.log(t, :info, "#{t.name} Reloaded config")
238     elsif self.running
239       LOG.log(t, :info, "#{t.name} Loaded config")
240     end
241   end
242   
243   def self.unwatch(watch)
244     # unmonitor
245     watch.unmonitor unless watch.state == :unmonitored
246     
247     # unregister
248     watch.unregister!
249     
250     # remove from watches
251     self.watches.delete(watch.name)
252     
253     # remove from groups
254     if watch.group
255       self.groups[watch.group].delete(watch)
256     end
257   end
258   
259   def self.contact(kind)
260     self.internal_init
261     
262     # create the condition
263     begin
264       c = Contact.generate(kind)
265     rescue NoSuchContactError => e
266       abort e.message
267     end
268     
269     # send to block so config can set attributes
270     yield(c) if block_given?
271     
272     # call prepare on the contact
273     c.prepare
274     
275     # remove existing contacts of same name
276     existing_contact = self.contacts[c.name]
277     if self.running && existing_contact
278       self.uncontact(existing_contact)
279     end
280     
281     # ensure the new contact has a unique name
282     if self.contacts[c.name] || self.contact_groups[c.name]
283       abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
284     end
285     
286     # abort if the Contact is invalid, the Contact will have printed
287     # out its own error messages by now
288     unless Contact.valid?(c) && c.valid?
289       abort "Exiting on invalid contact"
290     end
291     
292     # add to list of contacts
293     self.contacts[c.name] = c
294     
295     # add to contact group if specified
296     if c.group
297       # ensure group name hasn't been used for a contact already
298       if self.contacts[c.group]
299         abort "Contact Group name '#{c.group}' already used for a Contact"
300       end
301     
302       self.contact_groups[c.group] ||= []
303       self.contact_groups[c.group] << c
304     end
305   end
306   
307   def self.uncontact(contact)
308     self.contacts.delete(contact.name)
309     if contact.group
310       self.contact_groups[contact.group].delete(contact)
311     end
312   end
313     
314   def self.control(name, command)
315     # get the list of watches
316     watches = Array(self.watches[name] || self.groups[name])
317   
318     jobs = []
319     
320     # do the command
321     case command
322       when "start", "monitor"
323         watches.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
324       when "restart"
325         watches.each { |w| jobs << Thread.new { w.move(:restart) } }
326       when "stop"
327         watches.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
328       when "unmonitor"
329         watches.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
330       else
331         raise InvalidCommandError.new
332     end
333     
334     jobs.each { |j| j.join }
335     
336     watches.map { |x| x.name }
337   end
338   
339   def self.stop_all
340     self.watches.sort.each do |name, w|
341       Thread.new do
342         w.unmonitor if w.state != :unmonitored
343         w.action(:stop) if w.alive?
344       end
345     end
346     
347     10.times do
348       return true unless self.watches.map { |name, w| w.alive? }.any?
349       sleep 1
350     end
351     
352     return false
353   end
354   
355   def self.terminate
356     exit!(0)
357   end
358   
359   def self.status
360     info = {}
361     self.watches.map do |name, w|
362       info[name] = {:state => w.state}
363     end
364     info
365   end
366   
367   def self.running_log(watch_name, since)
368     unless self.watches[watch_name]
369       raise NoSuchWatchError.new
370     end
371     
372     LOG.watch_log_since(watch_name, since)
373   end
374   
375   def self.running_load(code, filename)
376     errors = ""
377     watches = []
378     
379     begin
380       LOG.start_capture
381       
382       eval(code, root_binding, filename)
383       self.pending_watches.each do |w|
384         if previous_state = self.pending_watch_states[w.name]
385           w.monitor unless previous_state == :unmonitored
386         else
387           w.monitor if w.autostart?
388         end
389       end
390       watches = self.pending_watches.dup
391       self.pending_watches.clear
392       self.pending_watch_states.clear
393     rescue Exception => e
394       # don't ever let running_load take down god
395       errors << LOG.finish_capture
396       
397       unless e.instance_of?(SystemExit)
398         errors << e.message << "\n"
399         errors << e.backtrace.join("\n")
400       end
401     end
402     
403     names = watches.map { |x| x.name }
404     [names, errors]
405   end
406   
407   def self.load(glob)
408     Dir[glob].each do |f|
409       Kernel.load f
410     end
411   end
412   
413   def self.setup
414     # Make pid directory
415     unless test(?d, self.pid_file_directory)
416       begin
417         FileUtils.mkdir_p(self.pid_file_directory)
418       rescue Errno::EACCES => e
419         abort "Failed to create pid file directory: #{e.message}"
420       end
421     end
422   end
423     
424   def self.validater
425     unless test(?w, self.pid_file_directory)
426       abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
427     end
428   end
429   
430   def self.start
431     self.internal_init
432     self.setup
433     self.validater
434     
435     # instantiate server
436     self.server = Socket.new(self.port)
437     
438     # start event handler system
439     EventHandler.start if EventHandler.loaded?
440     
441     # start the timer system
442     Timer.get
443     
444     # start monitoring any watches set to autostart
445     self.watches.values.each { |w| w.monitor if w.autostart? }
446     
447     # clear pending watches
448     self.pending_watches.clear
449     
450     # mark as running
451     self.running = true
452     
453     # join the timer thread so we don't exit
454     Timer.get.join
455   end
456   
457   def self.at_exit
458     self.start
459   end
462 at_exit do
463   God.at_exit if $run