update history and manifest
[god.git] / lib / god.rb
blob498439c5db3f93282020185f49d28f4dc1aec8f3
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'
35 require 'god/contact'
36 require 'god/contacts/email'
38 require 'god/reporter'
39 require 'god/server'
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 begin
56   Syslog.open('god')
57 rescue RuntimeError
58   Syslog.reopen('god')
59 end
61 God::EventHandler.load
63 module Kernel
64   # Override abort to exit without executing the at_exit hook
65   def abort(text)
66     puts text
67     exit!
68   end
69 end
71 module God
72   VERSION = '0.5.0'
73   
74   LOG = Logger.new
75     
76   LOG_BUFFER_SIZE_DEFAULT = 100
77   PID_FILE_DIRECTORY_DEFAULT = '/var/run/god'
78   DRB_PORT_DEFAULT = 17165
79   DRB_ALLOW_DEFAULT = ['127.0.0.1']
80   
81   class << self
82     # user configurable
83     attr_accessor :host,
84                   :port,
85                   :allow,
86                   :log_buffer_size,
87                   :pid_file_directory
88     
89     # internal
90     attr_accessor :inited,
91                   :running,
92                   :pending_watches,
93                   :server,
94                   :watches,
95                   :groups,
96                   :contacts,
97                   :contact_groups
98   end
99   
100   def self.init
101     if self.inited
102       abort "God.init must be called before any Watches"
103     end
104     
105     self.internal_init
106     
107     # yield to the config file
108     yield self if block_given?
109   end
110   
111   def self.internal_init
112     # only do this once
113     return if self.inited
114     
115     # variable init
116     self.watches = {}
117     self.groups = {}
118     self.pending_watches = []
119     self.contacts = {}
120     self.contact_groups = {}
121     
122     # set defaults
123     self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
124     self.pid_file_directory ||= PID_FILE_DIRECTORY_DEFAULT
125     self.port ||= DRB_PORT_DEFAULT
126     self.allow ||= DRB_ALLOW_DEFAULT
127     
128     # init has been executed
129     self.inited = true
130     
131     # not yet running
132     self.running = false
133   end
134   
135   # Instantiate a new, empty Watch object and pass it to the mandatory
136   # block. The attributes of the watch will be set by the configuration
137   # file.
138   def self.watch(&block)
139     self.task(Watch, &block)
140   end
141   
142   # Instantiate a new, empty Task object and pass it to the mandatory
143   # block. The attributes of the task will be set by the configuration
144   # file.
145   def self.task(klass = Task)
146     self.internal_init
147     
148     t = klass.new
149     yield(t)
150     
151     # do the post-configuration
152     t.prepare
153     
154     # if running, completely remove the watch (if necessary) to
155     # prepare for the reload
156     existing_watch = self.watches[t.name]
157     if self.running && existing_watch
158       self.unwatch(existing_watch)
159     end
160     
161     # ensure the new watch has a unique name
162     if self.watches[t.name] || self.groups[t.name]
163       abort "Task name '#{t.name}' already used for a Task or Group"
164     end
165     
166     # ensure watch is internally valid
167     t.valid? || abort("Task '#{t.name}' is not valid (see above)")
168     
169     # add to list of watches
170     self.watches[t.name] = t
171     
172     # add to pending watches
173     self.pending_watches << t
174     
175     # add to group if specified
176     if t.group
177       # ensure group name hasn't been used for a watch already
178       if self.watches[t.group]
179         abort "Group name '#{t.group}' already used for a Task"
180       end
181     
182       self.groups[t.group] ||= []
183       self.groups[t.group] << t
184     end
186     # register watch
187     t.register!
188   end
189   
190   def self.unwatch(watch)
191     # unmonitor
192     watch.unmonitor
193     
194     # unregister
195     watch.unregister!
196     
197     # remove from watches
198     self.watches.delete(watch.name)
199     
200     # remove from groups
201     if watch.group
202       self.groups[watch.group].delete(watch)
203     end
204   end
205   
206   def self.contact(kind)
207     self.internal_init
208     
209     # create the condition
210     begin
211       c = Contact.generate(kind)
212     rescue NoSuchContactError => e
213       abort e.message
214     end
215     
216     # send to block so config can set attributes
217     yield(c) if block_given?
218     
219     # call prepare on the contact
220     c.prepare
221     
222     # ensure the new contact has a unique name
223     if self.contacts[c.name] || self.contact_groups[c.name]
224       abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
225     end
226     
227     # abort if the Contact is invalid, the Contact will have printed
228     # out its own error messages by now
229     unless Contact.valid?(c) && c.valid?
230       abort "Exiting on invalid contact"
231     end
232     
233     # add to list of contacts
234     self.contacts[c.name] = c
235     
236     # add to contact group if specified
237     if c.group
238       # ensure group name hasn't been used for a contact already
239       if self.contacts[c.group]
240         abort "Contact Group name '#{c.group}' already used for a Contact"
241       end
242     
243       self.contact_groups[c.group] ||= []
244       self.contact_groups[c.group] << c
245     end
246   end
247     
248   def self.control(name, command)
249     # get the list of watches
250     watches = Array(self.watches[name] || self.groups[name])
251   
252     jobs = []
253     
254     # do the command
255     case command
256       when "start", "monitor"
257         watches.each { |w| jobs << Thread.new { w.monitor } }
258       when "restart"
259         watches.each { |w| jobs << Thread.new { w.move(:restart) } }
260       when "stop"
261         watches.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) } }
262       when "unmonitor"
263         watches.each { |w| jobs << Thread.new { w.unmonitor } }
264       else
265         raise InvalidCommandError.new
266     end
267     
268     jobs.each { |j| j.join }
269     
270     watches
271   end
272   
273   def self.stop_all
274     self.watches.sort.each do |name, w|
275       Thread.new do
276         w.unmonitor if w.state
277         w.action(:stop) if w.alive?
278       end
279     end
280     
281     10.times do
282       return true unless self.watches.map { |name, w| w.alive? }.any?
283       sleep 1
284     end
285     
286     return false
287   end
288   
289   def self.terminate
290     exit!(0)
291   end
292   
293   def self.status
294     info = {}
295     self.watches.map do |name, w|
296       info[name] = {:state => w.state}
297     end
298     info
299   end
300   
301   def self.running_log(watch_name, since)
302     unless self.watches[watch_name]
303       raise NoSuchWatchError.new
304     end
305     
306     LOG.watch_log_since(watch_name, since)
307   end
308   
309   def self.running_load(code)
310     eval(code)
311     self.pending_watches.each { |w| w.monitor if w.autostart? }
312     watches = self.pending_watches.dup
313     self.pending_watches.clear
314     watches
315   end
316   
317   def self.load(glob)
318     Dir[glob].each do |f|
319       Kernel.load f
320     end
321   end
322   
323   def self.setup
324     # Make pid directory
325     unless test(?d, self.pid_file_directory)
326       begin
327         FileUtils.mkdir_p(self.pid_file_directory)
328       rescue Errno::EACCES => e
329         abort "Failed to create pid file directory: #{e.message}"
330       end
331     end
332   end
333     
334   def self.validater
335     unless test(?w, self.pid_file_directory)
336       abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
337     end
338   end
339   
340   def self.start
341     self.internal_init
342     self.setup
343     self.validater
344     
345     # instantiate server
346     self.server = Server.new(self.host, self.port, self.allow)
347     
348     # start event handler system
349     EventHandler.start if EventHandler.loaded?
350     
351     # start the timer system
352     Timer.get
354     # start monitoring any watches set to autostart
355     self.watches.values.each { |w| w.monitor if w.autostart? }
356     
357     # clear pending watches
358     self.pending_watches.clear
359     
360     # mark as running
361     self.running = true
362     
363     # join the timer thread so we don't exit
364     Timer.get.join
365   end
366   
367   def self.at_exit
368     self.start
369   end
372 at_exit do
373   God.at_exit