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