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