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