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