lib/god.rb in god-0.3.0 vs lib/god.rb in god-0.4.0

- old
+ new

@@ -1,24 +1,33 @@ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed +# core +require 'logger' + +# stdlib require 'syslog' # internal requires require 'god/errors' - +require 'god/logger' require 'god/system/process' +require 'god/dependency_graph' +require 'god/timeline' require 'god/behavior' require 'god/behaviors/clean_pid_file' +require 'god/behaviors/notify_when_flapping' require 'god/condition' -require 'god/conditions/timeline' require 'god/conditions/process_running' require 'god/conditions/process_exits' +require 'god/conditions/tries' require 'god/conditions/memory_usage' require 'god/conditions/cpu_usage' require 'god/conditions/always' +require 'god/conditions/lambda' +require 'god/conditions/degrading_lambda' require 'god/reporter' require 'god/server' require 'god/timer' require 'god/hub' @@ -41,128 +50,251 @@ end God::EventHandler.load module God - VERSION = '0.3.0' + VERSION = '0.4.0' + LOG = Logger.new + + LOG_BUFFER_SIZE_DEFAULT = 100 + PID_FILE_DIRECTORY_DEFAULT = '/var/run/god' + DRB_PORT_DEFAULT = 17165 + DRB_ALLOW_DEFAULT = ['127.0.0.1'] + class << self - attr_accessor :inited, :host, :port + # user configurable + attr_accessor :host, + :port, + :allow, + :log_buffer_size, + :pid_file_directory - # drb - attr_accessor :server - - # api - attr_accessor :watches, :groups + # internal + attr_accessor :inited, + :running, + :pending_watches, + :server, + :watches, + :groups end def self.init + if self.inited + abort "God.init must be called before any Watches" + end + + self.internal_init + end + + def self.internal_init # only do this once return if self.inited # variable init self.watches = {} self.groups = {} + self.pending_watches = [] + # set defaults + self.log_buffer_size = LOG_BUFFER_SIZE_DEFAULT + self.pid_file_directory = PID_FILE_DIRECTORY_DEFAULT + self.port = DRB_PORT_DEFAULT + self.allow = DRB_ALLOW_DEFAULT + # yield to the config file yield self if block_given? - # instantiate server - self.server = Server.new(self.host, self.port) - # init has been executed self.inited = true - end - # Where pid files created by god will go by default - def self.pid_file_directory - @pid_file_directory ||= '/var/run/god' + # not yet running + self.running = false end - - def self.pid_file_directory=(value) - @pid_file_directory = value - end - + # Instantiate a new, empty Watch object and pass it to the mandatory # block. The attributes of the watch will be set by the configuration # file. def self.watch - self.init + self.internal_init w = Watch.new yield(w) + # if running, completely remove the watch (if necessary) to + # prepare for the reload + existing_watch = self.watches[w.name] + if self.running && existing_watch + self.unwatch(existing_watch) + end + # ensure the new watch has a unique name if self.watches[w.name] || self.groups[w.name] abort "Watch name '#{w.name}' already used for a Watch or Group" end + # ensure watch is internally valid + w.valid? || abort("Watch '#{w.name}' is not valid (see above)") + # add to list of watches self.watches[w.name] = w + # add to pending watches + self.pending_watches << w + # add to group if specified if w.group # ensure group name hasn't been used for a watch already if self.watches[w.group] abort "Group name '#{w.group}' already used for a Watch" end self.groups[w.group] ||= [] - self.groups[w.group] << w.name + self.groups[w.group] << w end # register watch w.register! end + def self.unwatch(watch) + # unmonitor + watch.unmonitor + + # unregister + watch.unregister! + + # remove from watches + self.watches.delete(watch.name) + + # remove from groups + if watch.group + self.groups[watch.group].delete(watch) + end + end + def self.control(name, command) # get the list of watches watches = Array(self.watches[name] || self.groups[name]) + jobs = [] + # do the command case command when "start", "monitor" - watches.each { |w| w.monitor } + watches.each { |w| jobs << Thread.new { w.monitor } } when "restart" - watches.each { |w| w.move(:restart) } + watches.each { |w| jobs << Thread.new { w.move(:restart) } } when "stop" - watches.each { |w| w.unmonitor.action(:stop) } + watches.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) } } when "unmonitor" - watches.each { |w| w.unmonitor } + watches.each { |w| jobs << Thread.new { w.unmonitor } } else raise InvalidCommandError.new end + jobs.each { |j| j.join } + watches end + + def self.stop_all + self.watches.sort.each do |name, w| + Thread.new do + w.unmonitor if w.state + w.action(:stop) if w.alive? + end + end - def self.start - # make sure there's something to do - if self.watches.nil? || self.watches.empty? - abort "You must specify at least one watch!" + 10.times do + return true unless self.watches.map { |name, w| w.alive? }.any? + sleep 1 end + return false + end + + def self.terminate + exit!(0) + end + + def self.status + info = {} + self.watches.map do |name, w| + status = w.state || :unmonitored + info[name] = {:state => status} + end + info + end + + def self.running_log(watch_name, since) + unless self.watches[watch_name] + raise NoSuchWatchError.new + end + + LOG.watch_log_since(watch_name, since) + end + + def self.running_load(code) + eval(code) + self.pending_watches.each { |w| w.monitor if w.autostart? } + watches = self.pending_watches.dup + self.pending_watches.clear + watches + end + + def self.load(glob) + Dir[glob].each do |f| + Kernel.load f + end + end + + def self.setup + # Make pid directory + unless test(?d, self.pid_file_directory) + begin + FileUtils.mkdir_p(self.pid_file_directory) + rescue Errno::EACCES => e + abort "Failed to create pid file directory: #{e.message}" + end + end + end + + def self.validater + unless test(?w, self.pid_file_directory) + abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}" + end + end + + def self.start + self.internal_init + self.setup + self.validater + + # instantiate server + self.server = Server.new(self.host, self.port, self.allow) + # start event handler system EventHandler.start if EventHandler.loaded? # start the timer system Timer.get # start monitoring any watches set to autostart self.watches.values.each { |w| w.monitor if w.autostart? } + # clear pending watches + self.pending_watches.clear + + # mark as running + self.running = true + # join the timer thread so we don't exit Timer.get.join end def self.at_exit self.start - end - - def self.load(glob) - Dir[glob].each do |f| - Kernel.load f - end end end at_exit do God.at_exit \ No newline at end of file