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