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