diff --git a/Announce.txt b/Announce.txt index 3a9a2d11..f72f62da 100644 --- a/Announce.txt +++ b/Announce.txt @@ -52,7 +52,7 @@ EXAMPLE The easiest way to understand how god will make your life better is by looking at a sample config file. The following configuration file is what I use at gravatar.com to keep the mongrels running: # run with: god -c /path/to/gravatar.god -# +# # This is the actual config file used to keep the mongrels of # gravatar.com running. @@ -61,7 +61,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" %w{8200 8201 8202}.each do |port| God.watch do |w| w.name = "gravatar2-mongrel-#{port}" - w.interval = 30.seconds # default + w.interval = 30.seconds # default w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \ -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d" w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" @@ -69,7 +69,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" w.start_grace = 10.seconds w.restart_grace = 10.seconds w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") - + w.behavior(:clean_pid_file) w.start_if do |start| @@ -78,19 +78,19 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" c.running = false end end - + w.restart_if do |restart| restart.condition(:memory_usage) do |c| c.above = 150.megabytes c.times = [3, 5] # 3 out of 5 intervals end - + restart.condition(:cpu_usage) do |c| c.above = 50.percent c.times = 5 end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/History.txt b/History.txt index f69d1c98..c8525c0d 100644 --- a/History.txt +++ b/History.txt @@ -142,7 +142,7 @@ * Minor Enhancement * Allow extra args to pass through to config file - + == 0.5.1 / 2007-10-08 * Bug Fixes diff --git a/README.txt b/README.txt index 7aabb9e4..b63912f0 100644 --- a/README.txt +++ b/README.txt @@ -1,16 +1,16 @@ god by Tom Preston-Werner Kevin Clark (kqueue/netlink support) - + http://god.rubyforge.org == DESCRIPTION: - -God is an easy to configure, easy to extend monitoring framework written + +God is an easy to configure, easy to extend monitoring framework written in Ruby. -Keeping your server processes and tasks running should be a simple part of -your deployment process. God aims to be the simplest, most powerful +Keeping your server processes and tasks running should be a simple part of +your deployment process. God aims to be the simplest, most powerful monitoring application available. == DOCUMENTATION: @@ -24,7 +24,7 @@ Sign up for the god mailing list at http://groups.google.com/group/god-rb == INSTALL: $ sudo gem install god - + == CONTRIBUTE: Latest code is available at http://github.com/mojombo/god diff --git a/examples/events.god b/examples/events.god index 34a1aa22..d38d82c5 100644 --- a/examples/events.god +++ b/examples/events.god @@ -14,23 +14,23 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -c #{RAILS_ROOT}" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") w.log = File.join(RAILS_ROOT, "log/commands.#{port}.log") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 8 @@ -43,7 +43,7 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -51,13 +51,13 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = 5 end - + on.condition(:http_response_code) do |c| c.host = 'localhost' c.port = port @@ -67,7 +67,7 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/examples/gravatar.god b/examples/gravatar.god index a676ddc9..a7b61040 100644 --- a/examples/gravatar.god +++ b/examples/gravatar.god @@ -1,5 +1,5 @@ # run with: god -c /path/to/gravatar.god -# +# # This is the actual config file used to keep the mongrels of # gravatar.com running. @@ -8,7 +8,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" %w{8200 8201 8202}.each do |port| God.watch do |w| w.name = "gravatar2-mongrel-#{port}" - w.interval = 30.seconds # default + w.interval = 30.seconds # default w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \ -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d" w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" @@ -16,7 +16,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" w.start_grace = 10.seconds w.restart_grace = 10.seconds w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") - + w.behavior(:clean_pid_file) w.start_if do |start| @@ -25,19 +25,19 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" c.running = false end end - + w.restart_if do |restart| restart.condition(:memory_usage) do |c| c.above = 150.megabytes c.times = [3, 5] # 3 out of 5 intervals end - + restart.condition(:cpu_usage) do |c| c.above = 50.percent c.times = 5 end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/examples/single.god b/examples/single.god index c55372fc..229d331c 100644 --- a/examples/single.god +++ b/examples/single.god @@ -7,23 +7,23 @@ God.watch do |w| w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.pid" w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.pid" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 5 @@ -35,7 +35,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -43,14 +43,14 @@ God.watch do |w| c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/ext/god/extconf.rb b/ext/god/extconf.rb index 46835750..41ee9467 100644 --- a/ext/god/extconf.rb +++ b/ext/god/extconf.rb @@ -16,7 +16,7 @@ def create_dummy_makefile puts "Missing 'sys/event.h' header" fail = true end - + if fail puts puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions." @@ -31,7 +31,7 @@ def create_dummy_makefile puts "You may need to install a header package for your system" fail = true end - + unless have_header('linux/connector.h') && have_header('linux/cn_proc.h') puts puts "Missing 'linux/connector.h', or 'linux/cn_proc.h' header(s)" @@ -39,7 +39,7 @@ def create_dummy_makefile puts "You may need to install a header package for your system" fail = true end - + if fail puts puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions." diff --git a/ext/god/kqueue_handler.c b/ext/god/kqueue_handler.c index 7936993d..da7b03cf 100644 --- a/ext/god/kqueue_handler.c +++ b/ext/god/kqueue_handler.c @@ -31,28 +31,28 @@ kqh_event_mask(VALUE klass, VALUE sym) } else { rb_raise(rb_eNotImpError, "Event `%s` not implemented", rb_id2name(id)); } - + return Qnil; } - + VALUE kqh_monitor_process(VALUE klass, VALUE pid, VALUE mask) { struct kevent new_event; ID event; - + u_int fflags = NUM2UINT(mask); - + EV_SET(&new_event, FIX2UINT(pid), EVFILT_PROC, EV_ADD | EV_ENABLE, fflags, 0, 0); - + if (-1 == kevent(kq, &new_event, 1, NULL, 0, NULL)) { rb_raise(rb_eStandardError, strerror(errno)); } - + num_events = NUM_EVENTS; - + return Qnil; } @@ -62,23 +62,23 @@ kqh_handle_events() int nevents, i, num_to_fetch; struct kevent *events; fd_set read_set; - + FD_ZERO(&read_set); FD_SET(kq, &read_set); - + // Don't actually run this method until we've got an event - rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL); - + rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL); + // Grabbing num_events once for thread safety num_to_fetch = num_events; events = (struct kevent*)malloc(num_to_fetch * sizeof(struct kevent)); - + if (NULL == events) { rb_raise(rb_eStandardError, strerror(errno)); } - + nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL); - + if (-1 == nevents) { free(events); rb_raise(rb_eStandardError, strerror(errno)); @@ -91,7 +91,7 @@ kqh_handle_events() } } } - + free(events); return INT2FIX(nevents); @@ -101,17 +101,17 @@ void Init_kqueue_handler_ext() { kq = kqueue(); - + if (kq == -1) { rb_raise(rb_eStandardError, "kqueue initilization failed"); } - + proc_exit = rb_intern("proc_exit"); proc_fork = rb_intern("proc_fork"); m_call = rb_intern("call"); m_size = rb_intern("size"); m_deregister = rb_intern("deregister"); - + mGod = rb_const_get(rb_cObject, rb_intern("God")); cEventHandler = rb_const_get(mGod, rb_intern("EventHandler")); cKQueueHandler = rb_define_class_under(mGod, "KQueueHandler", rb_cObject); diff --git a/ext/god/netlink_handler.c b/ext/god/netlink_handler.c index 32c53d7a..b467c441 100644 --- a/ext/god/netlink_handler.c +++ b/ext/god/netlink_handler.c @@ -27,42 +27,42 @@ nlh_handle_events() char buff[CONNECTOR_MAX_MSG_SIZE]; struct nlmsghdr *hdr; struct proc_event *event; - + VALUE extra_data; - + fd_set fds; - + FD_ZERO(&fds); FD_SET(nl_sock, &fds); - + if (0 > rb_thread_select(nl_sock + 1, &fds, NULL, NULL, NULL)) { rb_raise(rb_eStandardError, strerror(errno)); } - + /* If there were no events detected, return */ if (! FD_ISSET(nl_sock, &fds)) { return INT2FIX(0); } - - /* if there are events, make calls */ + + /* if there are events, make calls */ if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) { rb_raise(rb_eStandardError, strerror(errno)); } - + hdr = (struct nlmsghdr *)buff; - + if (NLMSG_ERROR == hdr->nlmsg_type) { rb_raise(rb_eStandardError, strerror(errno)); - } else if (NLMSG_DONE == hdr->nlmsg_type) { - + } else if (NLMSG_DONE == hdr->nlmsg_type) { + event = (struct proc_event *)((struct cn_msg *)NLMSG_DATA(hdr))->data; - + switch(event->what) { case PROC_EVENT_EXIT: if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.exit.process_pid))) { return INT2FIX(0); } - + extra_data = rb_hash_new(); rb_hash_aset(extra_data, ID2SYM(rb_intern("pid")), INT2FIX(event->event_data.exit.process_pid)); rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_code")), INT2FIX(event->event_data.exit.exit_code)); @@ -71,18 +71,18 @@ nlh_handle_events() rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.exit.process_pid), ID2SYM(proc_exit), extra_data); return INT2FIX(1); - + case PROC_EVENT_FORK: if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.fork.parent_pid))) { return INT2FIX(0); } - + extra_data = rb_hash_new(); rb_hash_aset(extra_data, rb_intern("parent_pid"), INT2FIX(event->event_data.fork.parent_pid)); rb_hash_aset(extra_data, rb_intern("parent_thread_group_id"), INT2FIX(event->event_data.fork.parent_tgid)); rb_hash_aset(extra_data, rb_intern("child_pid"), INT2FIX(event->event_data.fork.child_pid)); rb_hash_aset(extra_data, rb_intern("child_thread_group_id"), INT2FIX(event->event_data.fork.child_tgid)); - + rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork), extra_data); return INT2FIX(1); @@ -93,7 +93,7 @@ nlh_handle_events() break; } } - + return Qnil; } @@ -108,23 +108,23 @@ connect_to_netlink() char buff[NL_MESSAGE_SIZE]; struct nlmsghdr *hdr; /* for telling netlink what we want */ struct cn_msg *msg; /* the actual connector message */ - + /* connect to netlink socket */ nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); - + if (-1 == nl_sock) { rb_raise(rb_eStandardError, strerror(errno)); } - + bzero(&sa_nl, sizeof(sa_nl)); sa_nl.nl_family = AF_NETLINK; sa_nl.nl_groups = CN_IDX_PROC; sa_nl.nl_pid = getpid(); - + if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) { rb_raise(rb_eStandardError, strerror(errno)); } - + /* Fill header */ hdr = (struct nlmsghdr *)buff; hdr->nlmsg_len = NL_MESSAGE_SIZE; @@ -132,7 +132,7 @@ connect_to_netlink() hdr->nlmsg_flags = 0; hdr->nlmsg_seq = 0; hdr->nlmsg_pid = getpid(); - + /* Fill message */ msg = (struct cn_msg *)NLMSG_DATA(hdr); msg->id.idx = CN_IDX_PROC; /* Connecting to process information */ @@ -142,7 +142,7 @@ connect_to_netlink() msg->flags = 0; msg->len = sizeof(int); *(int*)msg->data = PROC_CN_MCAST_LISTEN; - + if (-1 == send(nl_sock, hdr, hdr->nlmsg_len, 0)) { rb_raise(rb_eStandardError, strerror(errno)); } @@ -150,17 +150,17 @@ connect_to_netlink() void Init_netlink_handler_ext() -{ +{ proc_exit = rb_intern("proc_exit"); proc_fork = rb_intern("proc_fork"); m_call = rb_intern("call"); m_watching_pid = rb_intern("watching_pid?"); - + mGod = rb_const_get(rb_cObject, rb_intern("God")); cEventHandler = rb_const_get(mGod, rb_intern("EventHandler")); cNetlinkHandler = rb_define_class_under(mGod, "NetlinkHandler", rb_cObject); rb_define_singleton_method(cNetlinkHandler, "handle_events", nlh_handle_events, 0); - + connect_to_netlink(); } diff --git a/ideas/execve/execve.c b/ideas/execve/execve.c index 4439807f..37d07b2e 100644 --- a/ideas/execve/execve.c +++ b/ideas/execve/execve.c @@ -12,18 +12,18 @@ void Init_execve() { VALUE method_execve(VALUE self, VALUE r_cmd, VALUE r_env) { char *shell = (char *)dln_find_exe("sh", 0); char *arg[] = { "sh", "-c", StringValuePtr(r_cmd), (char *)0 }; - + struct RArray *env_array; env_array = RARRAY(r_env); char *env[env_array->len + 1]; - + int i; for(i = 0; i < env_array->len; i++) { env[i] = StringValuePtr(env_array->ptr[i]); } - + env[env_array->len] = (char *)0; - + execve(shell, arg, env); return Qnil; } \ No newline at end of file diff --git a/ideas/future.god b/ideas/future.god index 6723030e..9a28eb5f 100644 --- a/ideas/future.god +++ b/ideas/future.god @@ -22,17 +22,17 @@ God.watch do |w| w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -d" w.stop = "mongrel_rails stop -P ./log/mongrel.pid -c #{RAILS_ROOT}" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| @@ -44,7 +44,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -52,14 +52,14 @@ God.watch do |w| c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| @@ -71,7 +71,7 @@ God.watch do |w| c.giveup_tries = 5 c.notify = 'tom' end - + on.condition(:memory_usage) do |c| c.interval = 20 c.above = 40.megabytes diff --git a/init/god b/init/god index 2c7d7336..bda4cfe3 100755 --- a/init/god +++ b/init/god @@ -5,7 +5,7 @@ # chkconfig: - 85 15 # description: God is an easy to configure, easy to extend monitoring \ # framework written in Ruby. -# +# CONF_DIR=/etc/god @@ -37,6 +37,6 @@ case "$1" in echo "Usage: god {start|stop|restart|status}" exit 1 ;; -esac +esac exit $RETVAL diff --git a/lib/god.rb b/lib/god.rb index fa56ce7b..0804c436 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -99,15 +99,15 @@ def root_binding module Kernel alias_method :abort_orig, :abort - + def abort(text = nil) $run = false applog(nil, :error, text) if text exit(1) end - + alias_method :exit_orig, :exit - + def exit(code = 0) $run = false exit_orig(code) @@ -121,15 +121,15 @@ def safe_attr_accessor(*args) if !self.running && self.inited abort "God.#{arg} must be set before any Tasks are defined" end - + if self.running && self.inited applog(nil, :warn, "God.#{arg} can't be set while god is running") return end - + instance_variable_set(('@' + arg.to_s).intern, other) end - + define_method(arg) do instance_variable_get(('@' + arg.to_s).intern) end @@ -139,13 +139,13 @@ def safe_attr_accessor(*args) module God VERSION = '0.7.9' - + LOG_BUFFER_SIZE_DEFAULT = 100 PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids'] DRB_PORT_DEFAULT = 17165 DRB_ALLOW_DEFAULT = ['127.0.0.1'] LOG_LEVEL_DEFAULT = :info - + class << self # user configurable safe_attr_accessor :pid, @@ -157,7 +157,7 @@ class << self :log_file, :log_level, :use_events - + # internal attr_accessor :inited, :running, @@ -170,7 +170,7 @@ class << self :contact_groups, :main end - + # initialize class instance variables self.pid = nil self.host = nil @@ -179,14 +179,14 @@ class << self self.log_buffer_size = nil self.pid_file_directory = nil self.log_level = nil - + # Initialize internal data. # # Returns nothing def self.internal_init # only do this once return if self.inited - + # variable init self.watches = {} self.groups = {} @@ -194,16 +194,16 @@ def self.internal_init self.pending_watch_states = {} self.contacts = {} self.contact_groups = {} - + # set defaults self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT self.port ||= DRB_PORT_DEFAULT self.allow ||= DRB_ALLOW_DEFAULT self.log_level ||= LOG_LEVEL_DEFAULT - + # additional setup self.setup - + # log level log_level_map = {:debug => Logger::DEBUG, :info => Logger::INFO, @@ -211,14 +211,14 @@ def self.internal_init :error => Logger::ERROR, :fatal => Logger::FATAL} LOG.level = log_level_map[self.log_level] - + # init has been executed self.inited = true - + # not yet running self.running = false 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. @@ -231,7 +231,7 @@ def self.internal_init def self.watch(&block) self.task(Watch, &block) end - + # Instantiate a new, empty Task object and yield it to the mandatory # block. The attributes of the task will be set by the configuration # file. @@ -243,13 +243,13 @@ def self.watch(&block) # Returns nothing def self.task(klass = Task) self.internal_init - + t = klass.new yield(t) - + # do the post-configuration t.prepare - + # if running, completely remove the watch (if necessary) to # prepare for the reload existing_watch = self.watches[t.name] @@ -257,35 +257,35 @@ def self.task(klass = Task) self.pending_watch_states[existing_watch.name] = existing_watch.state self.unwatch(existing_watch) end - + # ensure the new watch has a unique name if self.watches[t.name] || self.groups[t.name] abort "Task name '#{t.name}' already used for a Task or Group" end - + # ensure watch is internally valid t.valid? || abort("Task '#{t.name}' is not valid (see above)") - + # add to list of watches self.watches[t.name] = t - + # add to pending watches self.pending_watches << t - + # add to group if specified if t.group # ensure group name hasn't been used for a watch already if self.watches[t.group] abort "Group name '#{t.group}' already used for a Task" end - + self.groups[t.group] ||= [] self.groups[t.group] << t end - + # register watch t.register! - + # log if self.running && existing_watch applog(t, :info, "#{t.name} Reloaded config") @@ -293,7 +293,7 @@ def self.task(klass = Task) applog(t, :info, "#{t.name} Loaded config") end end - + # Unmonitor and remove the given watch from god. # +watch+ is the Watch to remove # @@ -301,21 +301,21 @@ def self.task(klass = Task) def self.unwatch(watch) # unmonitor watch.unmonitor unless watch.state == :unmonitored - + # unregister watch.unregister! - + # remove from watches self.watches.delete(watch.name) - + # remove from groups if watch.group self.groups[watch.group].delete(watch) end - + applog(watch, :info, "#{watch.name} unwatched") end - + # Instantiate a new Contact of the given kind and send it to the block. # Then prepare, validate, and record the Contact. # +kind+ is the contact class specifier @@ -328,53 +328,53 @@ def self.unwatch(watch) # Returns nothing def self.contact(kind) self.internal_init - + # create the contact begin c = Contact.generate(kind) rescue NoSuchContactError => e abort e.message end - + # send to block so config can set attributes yield(c) if block_given? - + # call prepare on the contact c.prepare - + # remove existing contacts of same name existing_contact = self.contacts[c.name] if self.running && existing_contact self.uncontact(existing_contact) end - + # warn and noop if the contact has been defined before if self.contacts[c.name] || self.contact_groups[c.name] applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group") return end - + # abort if the Contact is invalid, the Contact will have printed # out its own error messages by now unless Contact.valid?(c) && c.valid? abort "Exiting on invalid contact" end - + # add to list of contacts self.contacts[c.name] = c - + # add to contact group if specified if c.group # ensure group name hasn't been used for a contact already if self.contacts[c.group] abort "Contact Group name '#{c.group}' already used for a Contact" end - + self.contact_groups[c.group] ||= [] self.contact_groups[c.group] << c end end - + # Remove the given contact from god. # +contact+ is the Contact to remove # @@ -385,7 +385,7 @@ def self.uncontact(contact) self.contact_groups[contact.group].delete(contact) end end - + # Control the lifecycle of the given task(s). # +name+ is the name of a task/group (String) # +command+ is the command to run (String) @@ -400,9 +400,9 @@ def self.uncontact(contact) def self.control(name, command) # get the list of items items = Array(self.watches[name] || self.groups[name]).dup - + jobs = [] - + # do the command case command when "start", "monitor" @@ -418,12 +418,12 @@ def self.control(name, command) else raise InvalidCommandError.new end - + jobs.each { |j| j.join } - + items.map { |x| x.name } end - + # Unmonitor and stop all tasks. # # Returns true on success @@ -435,15 +435,15 @@ def self.stop_all w.action(:stop) if w.alive? end end - + 10.times do return true unless self.watches.map { |name, w| w.alive? }.any? sleep 1 end - + return false end - + # Force the termination of god. # * Clean up pid file if one exists # * Stop DRb service @@ -455,7 +455,7 @@ def self.terminate self.server.stop if self.server exit!(0) end - + # Gather the status of each task. # # Examples @@ -470,7 +470,7 @@ def self.status end info end - + # Log lines for the given task since the specified time. # +watch_name+ is the name of the task (may be abbreviated) # +since+ is the Time since which to report log lines @@ -480,14 +480,14 @@ def self.status # Returns String:joined_log_lines def self.running_log(watch_name, since) matches = pattern_match(watch_name, self.watches.keys) - + unless matches.first raise NoSuchWatchError.new end - + LOG.watch_log_since(matches.first, since) end - + # Load a config file into a running god instance. Rescues any exceptions # that the config may raise and reports these back to the caller. # +code+ is a String containing the config file @@ -497,10 +497,10 @@ def self.running_log(watch_name, since) def self.running_load(code, filename) errors = "" watches = [] - + begin LOG.start_capture - + Gem.clear_paths eval(code, root_binding, filename) self.pending_watches.each do |w| @@ -516,17 +516,17 @@ def self.running_load(code, filename) rescue Exception => e # don't ever let running_load take down god errors << LOG.finish_capture - + unless e.instance_of?(SystemExit) errors << e.message << "\n" errors << e.backtrace.join("\n") end end - + names = watches.map { |x| x.name } [names, errors] end - + # Load the given file(s) according to the given glob. # +glob+ is the glob-enabled path to load # @@ -536,7 +536,7 @@ def self.load(glob) Kernel.load f end end - + def self.setup if self.pid_file_directory # pid file dir was specified, ensure it is created and writable @@ -547,7 +547,7 @@ def self.setup abort "Failed to create pid file directory: #{e.message}" end end - + unless File.writable?(self.pid_file_directory) abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}" end @@ -564,54 +564,54 @@ def self.setup rescue Errno::EACCES => e end end - + unless self.pid_file_directory dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) } abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}" end end - + applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}") end - + # Initialize and startup the machinery that makes god work. # # Returns nothing def self.start self.internal_init - + # instantiate server self.server = Socket.new(self.port) - + # 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 - + # don't exit - self.main = + self.main = Thread.new do loop do sleep 60 end end - + self.main.join end - + # To be called on program exit to start god # # Returns nothing def self.at_exit self.start end - + # private - + # Match a shortened pattern against a list of String candidates. # The pattern is expanded into a regular expression by # inserting .* between each character. @@ -628,7 +628,7 @@ def self.at_exit # Returns String[]:matched_elements def self.pattern_match(pattern, list) regex = pattern.split('').join('.*') - + list.select do |item| item =~ Regexp.new(regex) end diff --git a/lib/god/behavior.rb b/lib/god/behavior.rb index 478e6ef2..a2721b84 100644 --- a/lib/god/behavior.rb +++ b/lib/god/behavior.rb @@ -1,10 +1,10 @@ module God - + class Behavior include Configurable - + attr_accessor :watch - + # Generate a Behavior of the given kind. The proper class is found by camel casing the # kind (which is given as an underscored symbol). # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Behaviors::FooBar) @@ -16,31 +16,31 @@ def self.generate(kind, watch) rescue NameError raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}") end - + def valid? true end - + ####### - + def before_start end - + def after_start end - + def before_restart end - + def after_restart end - + def before_stop end - + def after_stop end - + # Construct the friendly name of this Behavior, looks like: # # Behavior FooBar on Watch 'baz' @@ -48,5 +48,5 @@ def friendly_name "Behavior " + super + " on Watch '#{self.watch.name}'" end end - + end \ No newline at end of file diff --git a/lib/god/behaviors/clean_pid_file.rb b/lib/god/behaviors/clean_pid_file.rb index df404fa8..11839b48 100644 --- a/lib/god/behaviors/clean_pid_file.rb +++ b/lib/god/behaviors/clean_pid_file.rb @@ -1,21 +1,21 @@ module God module Behaviors - + class CleanPidFile < Behavior def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil? valid end - + def before_start File.delete(self.watch.pid_file) - + "deleted pid file" rescue "no pid file to delete" end end - + end end \ No newline at end of file diff --git a/lib/god/behaviors/clean_unix_socket.rb b/lib/god/behaviors/clean_unix_socket.rb index 6b505311..da93d0c3 100644 --- a/lib/god/behaviors/clean_unix_socket.rb +++ b/lib/god/behaviors/clean_unix_socket.rb @@ -1,21 +1,21 @@ module God module Behaviors - + class CleanUnixSocket < Behavior def valid? valid = true valid &= complain("Attribute 'unix_socket' must be specified", self) if self.watch.unix_socket.nil? valid - end - - def before_start + end + + def before_start File.delete(self.watch.unix_socket) - - "deleted unix socket" - rescue - "no unix socket to delete" + + "deleted unix socket" + rescue + "no unix socket to delete" end end - + end end \ No newline at end of file diff --git a/lib/god/behaviors/notify_when_flapping.rb b/lib/god/behaviors/notify_when_flapping.rb index 6ede3a4a..a7c20c8e 100644 --- a/lib/god/behaviors/notify_when_flapping.rb +++ b/lib/god/behaviors/notify_when_flapping.rb @@ -1,44 +1,44 @@ module God module Behaviors - + class NotifyWhenFlapping < Behavior - attr_accessor :failures # number of failures + attr_accessor :failures # number of failures attr_accessor :seconds # number of seconds attr_accessor :notifier # class to notify with - + def initialize super @startup_times = [] end - + def valid? valid = true valid &= complain("Attribute 'failures' must be specified", self) unless self.failures valid &= complain("Attribute 'seconds' must be specified", self) unless self.seconds valid &= complain("Attribute 'notifier' must be specified", self) unless self.notifier - + # Must take one arg or variable args unless self.notifier.respond_to?(:notify) and [1,-1].include?(self.notifier.method(:notify).arity) valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args", self) end - + valid end - + def before_start now = Time.now.to_i @startup_times << now check_for_flapping(now) end - + def before_restart now = Time.now.to_i @startup_times << now check_for_flapping(now) end - + private - + def check_for_flapping(now) @startup_times.select! {|time| time >= now - self.seconds } if @startup_times.length >= self.failures @@ -46,6 +46,6 @@ def check_for_flapping(now) end end end - + end end \ No newline at end of file diff --git a/lib/god/cli/command.rb b/lib/god/cli/command.rb index 9da357f7..8c4dbf86 100644 --- a/lib/god/cli/command.rb +++ b/lib/god/cli/command.rb @@ -1,20 +1,20 @@ module God module CLI - + class Command def initialize(command, options, args) @command = command @options = options @args = args - + dispatch end - + def setup # connect to drb unix socket DRb.start_service("druby://127.0.0.1:0") @server = DRbObject.new(nil, God::Socket.socket(@options[:port])) - + # ping server to ensure that it is responsive begin @server.ping @@ -23,7 +23,7 @@ def setup abort end end - + def dispatch if %w{load status log quit terminate}.include?(@command) setup @@ -38,19 +38,19 @@ def dispatch abort end end - + def load_command file = @args[1] - + puts "Sending '#{@command}' command" puts - + unless File.exist?(file) abort "File not found: #{file}" end - + names, errors = *@server.running_load(File.read(file), File.expand_path(file)) - + # output response unless names.empty? puts 'The following tasks were affected:' @@ -58,13 +58,13 @@ def load_command puts ' ' + w end end - + unless errors.empty? puts errors exit(1) end end - + def status_command watches = {} @server.status.each do |name, status| @@ -83,17 +83,17 @@ def status_command end end end - + def log_command begin Signal.trap('INT') { exit } name = @args[1] - + unless name puts "You must specify a Task or Group name" exit! end - + t = Time.at(0) loop do print @server.running_log(name, t) @@ -106,7 +106,7 @@ def log_command puts "The server went away" end end - + def quit_command begin @server.terminate @@ -115,7 +115,7 @@ def quit_command puts 'Stopped god' end end - + def terminate_command t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } } if @server.stop_all @@ -125,7 +125,7 @@ def terminate_command t.kill; STDOUT.puts puts 'Could not stop all watches within 10 seconds' end - + begin @server.terminate abort 'Could not stop god' @@ -133,13 +133,13 @@ def terminate_command puts 'Stopped god' end end - + def check_command Thread.new do begin event_system = God::EventHandler.event_system puts "using event system: #{event_system}" - + if God::EventHandler.loaded? puts "starting event handler" God::EventHandler.start @@ -147,48 +147,48 @@ def check_command puts "[fail] event system did not load" exit(1) end - + puts 'forking off new process' - + pid = fork do loop { sleep(1) } end - + puts "forked process with pid = #{pid}" - + God::EventHandler.register(pid, :proc_exit) do puts "[ok] process exit event received" exit!(0) end - + sleep(1) - + puts "killing process" - + ::Process.kill('KILL', pid) rescue => e puts e.message puts e.backtrace.join("\n") end end - + sleep(2) - + puts "[fail] never received process exit event" exit(1) end - + def lifecycle_command # get the name of the watch/group name = @args[1] - + puts "Sending '#{@command}' command" - + t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } } - + # send @command watches = @server.control(name, @command) - + # output response t.kill; STDOUT.puts unless watches.empty? @@ -201,6 +201,6 @@ def lifecycle_command end end end # Command - + end end diff --git a/lib/god/cli/run.rb b/lib/god/cli/run.rb index 80161fde..69db91ea 100644 --- a/lib/god/cli/run.rb +++ b/lib/god/cli/run.rb @@ -1,6 +1,6 @@ module God module CLI - + class Run def initialize(options) @options = options @@ -8,14 +8,14 @@ def initialize(options) unless @options[:syslog] Logger.syslog = false end - + dispatch end - + def dispatch # have at_exit start god $run = true - + # run if @options[:daemonize] run_daemonized @@ -23,7 +23,7 @@ def dispatch run_in_front end end - + def attach process = System::Process.new(@options[:attach]) Thread.new do @@ -36,54 +36,54 @@ def attach end end end - + def default_run # start attached pid watcher if necessary if @options[:attach] self.attach end - + if @options[:port] God.port = @options[:port] end - + if @options[:events] God::EventHandler.load end - + # set log level, defaults to WARN if @options[:log_level] God.log_level = @options[:log_level] else God.log_level = @options[:daemonize] ? :warn : :info end - + if @options[:config] unless File.exist?(@options[:config]) abort "File not found: #{@options[:config]}" end - + # start the event handler God::EventHandler.start if God::EventHandler.loaded? - + load_config @options[:config] end end - + def run_in_front require 'god' - + if @options[:bleakhouse] BleakHouseDiagnostic.install end - + default_run - + log_file = God.log_file - log_file = File.expand_path(@options[:log]) if @options[:log] + log_file = File.expand_path(@options[:log]) if @options[:log] if log_file puts "Sending output to log file: #{log_file}" - + # reset file descriptors STDIN.reopen "/dev/null" STDOUT.reopen(log_file, "a") @@ -91,30 +91,30 @@ def run_in_front STDOUT.sync = true end end - + def run_daemonized # trap and ignore SIGHUP Signal.trap('HUP') {} - + pid = fork do begin require 'god' - + log_file = @options[:log] || God.log_file || "/dev/null" - + # reset file descriptors STDIN.reopen "/dev/null" STDOUT.reopen(log_file, "a") STDERR.reopen STDOUT STDOUT.sync = true - + # set pid if requested if @options[:pid] # and as deamon - God.pid = @options[:pid] + God.pid = @options[:pid] end - + default_run - + unless God::EventHandler.loaded? puts puts "***********************************************************************" @@ -125,23 +125,23 @@ def run_daemonized puts "***********************************************************************" puts end - + rescue => e puts e.message puts e.backtrace.join("\n") abort "There was a fatal system error while starting god (see above)" end end - + if @options[:pid] File.open(@options[:pid], 'w') { |f| f.write pid } end - + ::Process.detach pid - + exit end - + def load_config(config) if File.directory? config files_loaded = false @@ -157,7 +157,7 @@ def load_config(config) end end end - + def load_god_file(god_file) load File.expand_path(god_file) rescue Exception => e @@ -170,8 +170,8 @@ def load_god_file(god_file) return false end end - + end # Run - + end end diff --git a/lib/god/cli/version.rb b/lib/god/cli/version.rb index fa1f6d85..57b87f1e 100644 --- a/lib/god/cli/version.rb +++ b/lib/god/cli/version.rb @@ -1,23 +1,23 @@ module God module CLI - + class Version def self.version require 'god' - + # print version puts "Version #{God::VERSION}" exit end - + def self.version_extended puts "Version: #{God::VERSION}" puts "Polls: enabled" puts "Events: " + God::EventHandler.event_system - + exit end end - + end end \ No newline at end of file diff --git a/lib/god/condition.rb b/lib/god/condition.rb index 7eb11ea7..3ede8ece 100644 --- a/lib/god/condition.rb +++ b/lib/god/condition.rb @@ -1,29 +1,29 @@ module God - + class Condition < Behavior attr_accessor :transition, :notify, :info, :phase - + # Generate a Condition of the given kind. The proper class if found by camel casing the # kind (which is given as an underscored symbol). # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar) def self.generate(kind, watch) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern c = God::Conditions.const_get(sym).new - + unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition) - abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition" + abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition" end - + if !EventHandler.loaded? && c.kind_of?(EventCondition) abort "Condition '#{c.class.name}' requires an event system but none has been loaded" end - + c.watch = watch c rescue NameError raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}") end - + def self.valid?(condition) valid = true if condition.notify @@ -35,7 +35,7 @@ def self.valid?(condition) end valid end - + # Construct the friendly name of this Condition, looks like: # # Condition FooBar on Watch 'baz' @@ -43,15 +43,15 @@ def friendly_name "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'" end end - + class PollCondition < Condition - # all poll conditions can specify a poll interval + # all poll conditions can specify a poll interval attr_accessor :interval - + # Override this method in your Conditions (optional) def before end - + # Override this method in your Conditions (mandatory) # # Return true if the test passes (everything is ok) @@ -59,38 +59,38 @@ def before def test raise AbstractMethodNotOverriddenError.new("PollCondition#test must be overridden in subclasses") end - + # Override this method in your Conditions (optional) def after end end - + class EventCondition < Condition def register raise AbstractMethodNotOverriddenError.new("EventCondition#register must be overridden in subclasses") end - + def deregister raise AbstractMethodNotOverriddenError.new("EventCondition#deregister must be overridden in subclasses") end end - + class TriggerCondition < Condition def process(event, payload) raise AbstractMethodNotOverriddenError.new("TriggerCondition#process must be overridden in subclasses") end - + def trigger self.watch.trigger(self) end - + def register Trigger.register(self) end - + def deregister Trigger.deregister(self) end end - + end \ No newline at end of file diff --git a/lib/god/conditions/always.rb b/lib/god/conditions/always.rb index f651863a..d817bd00 100644 --- a/lib/god/conditions/always.rb +++ b/lib/god/conditions/always.rb @@ -3,21 +3,21 @@ module Conditions class Always < PollCondition attr_accessor :what - + def initialize self.info = "always" end - + def valid? valid = true valid &= complain("Attribute 'what' must be specified", self) if self.what.nil? valid end - + def test @what end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/complex.rb b/lib/god/conditions/complex.rb index 50dd8ed4..42b39810 100644 --- a/lib/god/conditions/complex.rb +++ b/lib/god/conditions/complex.rb @@ -1,60 +1,60 @@ module God module Conditions - + class Complex < PollCondition - AND = 0x1 + AND = 0x1 OR = 0x2 NOT = 0x4 - + def initialize() super - + @oper_stack = [] @op_stack = [] - + @this = nil end - + def valid? @oper_stack.inject(true) { |acc, oper| acc & oper.valid? } end - + def prepare @oper_stack.each { |oper| oper.prepare } end - + def new_oper(kind, op) oper = Condition.generate(kind, self.watch) @oper_stack.push(oper) @op_stack.push(op) oper end - + def this(kind) @this = Condition.generate(kind, self.watch) yield @this if block_given? end - + def and(kind) oper = new_oper(kind, 0x1) yield oper if block_given? end - + def and_not(kind) oper = new_oper(kind, 0x5) yield oper if block_given? end - + def or(kind) oper = new_oper(kind, 0x2) yield oper if block_given? end - + def or_not(kind) oper = new_oper(kind, 0x6) yield oper if block_given? end - + def test if @this.nil? # Although this() makes sense semantically and therefore @@ -71,16 +71,16 @@ def test else res = @this.test end - + @op_stack.each do |op| cond = @oper_stack.shift eval "res " + ((0 < op & AND) ? "&&" : "||") + "= " + ((0 < op & NOT) ? "!" : "") + "cond.test" @oper_stack.push cond end - + res end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/cpu_usage.rb b/lib/god/conditions/cpu_usage.rb index c52007fc..886b2be1 100644 --- a/lib/god/conditions/cpu_usage.rb +++ b/lib/god/conditions/cpu_usage.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :cpu_usage # Type: Poll - # + # # Trigger when the percent of CPU use of a process is above a specified limit. # On multi-core systems, this number could conceivably be above 100. # @@ -11,7 +11,7 @@ module Conditions # Required # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. - # +above+ is the percent CPU above which to trigger the condition. You + # +above+ is the percent CPU above which to trigger the condition. You # may use #percent to clarify this amount (see examples). # # Examples @@ -30,42 +30,42 @@ module Conditions # end class CpuUsage < PollCondition attr_accessor :above, :times, :pid_file - + def initialize super self.above = nil self.times = [1, 1] end - + def prepare if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) end - + def reset @timeline.clear end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test process = System::Process.new(self.pid) @timeline.push(process.percent_cpu) - + history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}%%" }.join(", ") + "]" - + if @timeline.select { |x| x > self.above }.size >= self.times.first self.info = "cpu out of bounds #{history}" return true @@ -75,6 +75,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/degrading_lambda.rb b/lib/god/conditions/degrading_lambda.rb index 5bb97dd0..7616318b 100644 --- a/lib/god/conditions/degrading_lambda.rb +++ b/lib/god/conditions/degrading_lambda.rb @@ -1,21 +1,21 @@ module God module Conditions - + # This condition degrades its interval by a factor of two for 3 tries before failing class DegradingLambda < PollCondition attr_accessor :lambda - + def initialize super @tries = 0 end - + def valid? valid = true valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil? valid end - + def test puts "Calling test. Interval at #{self.interval}" @original_interval ||= self.interval @@ -30,13 +30,13 @@ def test @tries = 0 self.interval = @original_interval end - + self.info = "lambda condition was not satisfied" false end - + private - + def pass? begin Timeout::timeout(@interval) { diff --git a/lib/god/conditions/disk_usage.rb b/lib/god/conditions/disk_usage.rb index 140b57b2..a9fff710 100644 --- a/lib/god/conditions/disk_usage.rb +++ b/lib/god/conditions/disk_usage.rb @@ -1,27 +1,27 @@ module God module Conditions - + class DiskUsage < PollCondition attr_accessor :above, :mount_point - + def initialize super self.above = nil self.mount_point = nil end - + def valid? valid = true valid &= complain("Attribute 'mount_point' must be specified", self) if self.mount_point.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test usage = `df | grep -i " #{self.mount_point}$" | awk '{print $5}' | sed 's/%//'` usage.to_i > self.above end end - + end end diff --git a/lib/god/conditions/flapping.rb b/lib/god/conditions/flapping.rb index 78bf3293..bc63a817 100644 --- a/lib/god/conditions/flapping.rb +++ b/lib/god/conditions/flapping.rb @@ -1,11 +1,11 @@ module God module Conditions - + # Condition Symbol :flapping # Type: Trigger - # + # # Trigger when a Task transitions to or from a state or states a given number - # of times within a given period. + # of times within a given period. # # Paramaters # Required @@ -24,7 +24,7 @@ module Conditions # it has been disabled by the condition. # +retry_times+ is the number of times after which to permanently unmonitor # the Task. - # +retry_within+ is the number of seconds within which + # +retry_within+ is the number of seconds within which # # Examples # @@ -37,16 +37,16 @@ class Flapping < TriggerCondition :retry_in, :retry_times, :retry_within - + def initialize self.info = "process is flapping" end - + def prepare @timeline = Timeline.new(self.times) @retry_timeline = Timeline.new(self.retry_times) end - + def valid? valid = true valid &= complain("Attribute 'times' must be specified", self) if self.times.nil? @@ -54,21 +54,21 @@ def valid? valid &= complain("Attributes 'from_state', 'to_state', or both must be specified", self) if self.from_state.nil? && self.to_state.nil? valid end - + def process(event, payload) begin if event == :state_change event_from_state, event_to_state = *payload - + from_state_match = !self.from_state || self.from_state && Array(self.from_state).include?(event_from_state) to_state_match = !self.to_state || self.to_state && Array(self.to_state).include?(event_to_state) - + if from_state_match && to_state_match @timeline << Time.now - + concensus = (@timeline.size == self.times) duration = (@timeline.last - @timeline.first) < self.within - + if concensus && duration @timeline.clear trigger @@ -81,21 +81,21 @@ def process(event, payload) puts e.backtrace.join("\n") end end - + private - + def retry_mechanism if self.retry_in @retry_timeline << Time.now - + concensus = (@retry_timeline.size == self.retry_times) duration = (@retry_timeline.last - @retry_timeline.first) < self.retry_within - + if concensus && duration # give up Thread.new do sleep 1 - + # log msg = "#{self.watch.name} giving up" applog(self.watch, :info, msg) @@ -104,17 +104,17 @@ def retry_mechanism # try again later Thread.new do sleep 1 - + # log msg = "#{self.watch.name} auto-reenable monitoring in #{self.retry_in} seconds" applog(self.watch, :info, msg) - + sleep self.retry_in - + # log msg = "#{self.watch.name} auto-reenabling monitoring" applog(self.watch, :info, msg) - + if self.watch.state == :unmonitored self.watch.monitor end @@ -123,6 +123,6 @@ def retry_mechanism end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/http_response_code.rb b/lib/god/conditions/http_response_code.rb index a005ef2e..f31489ae 100644 --- a/lib/god/conditions/http_response_code.rb +++ b/lib/god/conditions/http_response_code.rb @@ -2,10 +2,10 @@ module God module Conditions - + # Condition Symbol :http_response_code # Type: Poll - # + # # Trigger based on the response from an HTTP request. # # Paramaters @@ -71,7 +71,7 @@ class HttpResponseCode < PollCondition :timeout, # e.g. 60.seconds :path, # e.g. '/' :headers # e.g. {'Host' => 'myvirtual.mydomain.com'} - + def initialize super self.port = 80 @@ -80,24 +80,24 @@ def initialize self.times = [1, 1] self.timeout = 60.seconds end - + def prepare self.code_is = Array(self.code_is).map { |x| x.to_i } if self.code_is self.code_is_not = Array(self.code_is_not).map { |x| x.to_i } if self.code_is_not - + if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) @history = Timeline.new(self.times[1]) end - + def reset @timeline.clear @history.clear end - + def valid? valid = true valid &= complain("Attribute 'host' must be specified", self) if self.host.nil? @@ -105,15 +105,15 @@ def valid? (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not) valid end - + def test response = nil - + Net::HTTP.start(self.host, self.port) do |http| http.read_timeout = self.timeout response = http.get(self.path, self.headers) end - + actual_response_code = response.code.to_i if self.code_is && self.code_is.include?(actual_response_code) pass(actual_response_code) @@ -135,9 +135,9 @@ def test rescue Exception => failure self.code_is ? fail(failure.class.name) : pass(failure.class.name) end - + private - + def pass(code) @timeline << true if @timeline.select { |x| x }.size >= self.times.first @@ -148,21 +148,21 @@ def pass(code) false end end - + def fail(code) @timeline << false self.info = "http response nominal #{history(code, false)}" false end - + def history(code, passed) entry = code.to_s.dup entry = '*' + entry if passed @history << entry '[' + @history.join(", ") + ']' end - + end - + end end \ No newline at end of file diff --git a/lib/god/conditions/lambda.rb b/lib/god/conditions/lambda.rb index 0eb72c26..5d8a1aa5 100644 --- a/lib/god/conditions/lambda.rb +++ b/lib/god/conditions/lambda.rb @@ -1,6 +1,6 @@ module God module Conditions - + class Lambda < PollCondition attr_accessor :lambda diff --git a/lib/god/conditions/memory_usage.rb b/lib/god/conditions/memory_usage.rb index dc573c88..a5d95e10 100644 --- a/lib/god/conditions/memory_usage.rb +++ b/lib/god/conditions/memory_usage.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :memory_usage # Type: Poll - # + # # Trigger when the resident memory of a process is above a specified limit. # # Paramaters @@ -32,42 +32,42 @@ module Conditions # end class MemoryUsage < PollCondition attr_accessor :above, :times, :pid_file - + def initialize super self.above = nil self.times = [1, 1] end - + def prepare if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) end - + def reset @timeline.clear end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test process = System::Process.new(self.pid) @timeline.push(process.memory) - + history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}kb" }.join(", ") + "]" - + if @timeline.select { |x| x > self.above }.size >= self.times.first self.info = "memory out of bounds #{history}" return true @@ -77,6 +77,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/process_exits.rb b/lib/god/conditions/process_exits.rb index 54a82b92..4566a65a 100644 --- a/lib/god/conditions/process_exits.rb +++ b/lib/god/conditions/process_exits.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :process_exits # Type: Event - # + # # Trigger when a process exits. # # Paramaters @@ -24,41 +24,41 @@ module Conditions # end class ProcessExits < EventCondition attr_accessor :pid_file - + def initialize self.info = "process exited" end - + def valid? true end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def register pid = self.pid - + begin EventHandler.register(pid, :proc_exit) do |extra| formatted_extra = extra.size > 0 ? " #{extra.inspect}" : "" self.info = "process #{pid} exited#{formatted_extra}" self.watch.trigger(self) end - + msg = "#{self.watch.name} registered 'proc_exit' event for pid #{pid}" applog(self.watch, :info, msg) rescue StandardError raise EventRegistrationFailedError.new end end - + def deregister pid = self.pid if pid EventHandler.deregister(pid, :proc_exit) - + msg = "#{self.watch.name} deregistered 'proc_exit' event for pid #{pid}" applog(self.watch, :info, msg) else @@ -67,6 +67,6 @@ def deregister end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/process_running.rb b/lib/god/conditions/process_running.rb index 2877f364..0720a473 100644 --- a/lib/god/conditions/process_running.rb +++ b/lib/god/conditions/process_running.rb @@ -1,16 +1,16 @@ module God module Conditions - + # Condition Symbol :process_running # Type: Poll - # + # # Trigger when a process is running or not running depending on attributes. # # Paramaters # Required # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. - # +running" specifies whether you want to trigger if the process is + # +running" specifies whether you want to trigger if the process is # running (true) or whether it is not running (false) # # Examples @@ -35,24 +35,24 @@ module Conditions # end class ProcessRunning < PollCondition attr_accessor :running, :pid_file - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'running' must be specified", self) if self.running.nil? valid end - + def test self.info = [] - + pid = self.pid active = pid && System::Process.new(pid).exists? - + if (self.running && active) self.info.concat(["process is running"]) true @@ -69,6 +69,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/tries.rb b/lib/god/conditions/tries.rb index fdc03add..d666485c 100644 --- a/lib/god/conditions/tries.rb +++ b/lib/god/conditions/tries.rb @@ -1,35 +1,35 @@ module God module Conditions - + class Tries < PollCondition attr_accessor :times, :within - + def prepare @timeline = Timeline.new(self.times) end - + def reset @timeline.clear end - + def valid? valid = true valid &= complain("Attribute 'times' must be specified", self) if self.times.nil? valid end - + def test @timeline << Time.now - + concensus = (@timeline.size == self.times) duration = self.within.nil? || (@timeline.last - @timeline.first) < self.within - + if within history = "[#{@timeline.size}/#{self.times} within #{(@timeline.last - @timeline.first).to_i}s]" else history = "[#{@timeline.size}/#{self.times}]" end - + if concensus && duration self.info = "tries exceeded #{history}" return true @@ -39,6 +39,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/configurable.rb b/lib/god/configurable.rb index ae6f0f02..c2cc2496 100644 --- a/lib/god/configurable.rb +++ b/lib/god/configurable.rb @@ -1,18 +1,18 @@ module God - + module Configurable # Override this method in your Configurable (optional) # # Called once after the Configurable has been sent to the block and attributes have been # set. Do any post-processing on attributes here def prepare - + end - + def reset - + end - + # Override this method in your Configurable (optional) # # Called once during evaluation of the config file. Return true if valid, false otherwise @@ -29,16 +29,16 @@ def reset def valid? true end - + def base_name x = 1 # fix for MRI's local scope optimization bug DO NOT REMOVE! self.class.name.split('::').last end - + def friendly_name base_name end - + def self.complain(text, c = nil) watch = c.watch rescue nil msg = "" @@ -48,10 +48,10 @@ def self.complain(text, c = nil) applog(watch, :error, msg) false end - + def complain(text, c = nil) Configurable.complain(text, c) end end - + end \ No newline at end of file diff --git a/lib/god/contact.rb b/lib/god/contact.rb index 5de3181d..7b782dc5 100644 --- a/lib/god/contact.rb +++ b/lib/god/contact.rb @@ -1,29 +1,29 @@ module God - + class Contact include Configurable - + attr_accessor :name, :group, :info - + def self.generate(kind) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern c = God::Contacts.const_get(sym).new - + unless c.kind_of?(Contact) - abort "Contact '#{c.class.name}' must subclass God::Contact" + abort "Contact '#{c.class.name}' must subclass God::Contact" end - + c rescue NameError raise NoSuchContactError.new("No Contact found with the class name God::Contacts::#{sym}") end - + def self.valid?(contact) valid = true valid &= Configurable.complain("Attribute 'name' must be specified", contact) if contact.name.nil? valid end - + # Normalize the given notify specification into canonical form. # +spec+ is the notify spec as a String, Array of Strings, or Hash # @@ -32,7 +32,7 @@ def self.valid?(contact) # Where :contacts will be present and point to an Array of Strings. Both # :priority and :category may not be present but if they are, they will each # contain a single String. - # + # # Returns normalized notify spec # Raises ArgumentError on invalid spec (message contains details) def self.normalize(spec) @@ -46,7 +46,7 @@ def self.normalize(spec) {:contacts => spec} when Hash copy = spec.dup - + # check :contacts if contacts = copy.delete(:contacts) case contacts @@ -63,27 +63,27 @@ def self.normalize(spec) else raise ArgumentError.new("must have a :contacts key") end - + # remove priority and category copy.delete(:priority) copy.delete(:category) - + # check for invalid keys unless copy.empty? raise ArgumentError.new("contains extra elements: #{copy.inspect}") end - + # normalize spec[:contacts] &&= Array(spec[:contacts]) spec[:priority] &&= spec[:priority].to_s spec[:category] &&= spec[:category].to_s - + spec else raise ArgumentError.new("must be a String (contact name), Array (of contact names), or Hash (contact specification)") end end - + # Abstract # Send the message to the external source # +message+ is the message body returned from the condition @@ -94,7 +94,7 @@ def self.normalize(spec) def notify(message, time, priority, category, host) raise AbstractMethodNotOverriddenError.new("Contact#notify must be overridden in subclasses") end - + # Construct the friendly name of this Contact, looks like: # # Contact FooBar @@ -102,5 +102,5 @@ def friendly_name super + " Contact '#{self.name}'" end end - + end \ No newline at end of file diff --git a/lib/god/contacts/email.rb b/lib/god/contacts/email.rb index 36b59eae..e56785ff 100644 --- a/lib/god/contacts/email.rb +++ b/lib/god/contacts/email.rb @@ -3,16 +3,16 @@ module God module Contacts - + class Email < Contact class << self attr_accessor :message_settings, :delivery_method, :server_settings, :sendmail_settings, :format end - + self.message_settings = {:from => 'god@example.com'} - + self.delivery_method = :smtp # or :sendmail - + self.server_settings = {:address => 'localhost', :port => 25} # :domain @@ -23,7 +23,7 @@ class << self self.sendmail_settings = {:location => '/usr/sbin/sendmail', :arguments => '-i -t' } - + self.format = lambda do |name, email, message, time, priority, category, host| <<-EOF From: god <#{self.message_settings[:from]}> @@ -38,15 +38,15 @@ class << self Category: #{category} EOF end - + attr_accessor :email - + def valid? valid = true valid &= complain("Attribute 'email' must be specified", self) if self.email.nil? valid end - + def notify(message, time, priority, category, host) begin body = Email.format.call(self.name, self.email, message, time, priority, category, host) @@ -59,7 +59,7 @@ def notify(message, time, priority, category, host) else raise "unknown delivery method: #{Email.delivery_method}" end - + self.info = "sent email to #{self.email}" rescue => e applog(nil, :info, "failed to send email to #{self.email}: #{e.message}") @@ -75,7 +75,7 @@ def notify_smtp(mail) args << Email.server_settings[:domain] args << Email.server_settings[:user_name] args << Email.server_settings[:password] - args << Email.server_settings[:authentication] + args << Email.server_settings[:authentication] end Net::SMTP.start(*args) do |smtp| diff --git a/lib/god/contacts/jabber.rb b/lib/god/contacts/jabber.rb index a52f8617..d1469534 100644 --- a/lib/god/contacts/jabber.rb +++ b/lib/god/contacts/jabber.rb @@ -17,11 +17,11 @@ module XMPP4R module God module Contacts - class Jabber < Contact + class Jabber < Contact class << self attr_accessor :settings, :format end - + self.format = lambda do |message, priority, category, host| text = "Message: #{message}\n" text += "Host: #{host}\n" if host @@ -29,13 +29,13 @@ class << self text += "Category: #{category}\n" if category return text end - + attr_accessor :jabber_id - + def valid? valid = true end - + def notify(message, time, priority, category, host) begin jabber_id = XMPP4R::JID::new "#{Jabber.settings[:jabber_id]}/God" @@ -44,7 +44,7 @@ def notify(message, time, priority, category, host) jabber_client.auth Jabber.settings[:password] body = Jabber.format.call message, priority, category, host - + message = XMPP4R::Message::new self.jabber_id, body message.set_type :normal message.set_id '1' @@ -55,11 +55,11 @@ def notify(message, time, priority, category, host) rescue => e puts e.message puts e.backtrace.join("\n") - + self.info = "failed to send jabber message to #{self.jabber_id}: #{e.message}" end end end - + end end \ No newline at end of file diff --git a/lib/god/dependency_graph.rb b/lib/god/dependency_graph.rb index 7a863427..13713671 100644 --- a/lib/god/dependency_graph.rb +++ b/lib/god/dependency_graph.rb @@ -1,17 +1,17 @@ module God class DependencyGraph attr_accessor :nodes - + def initialize self.nodes = {} end - + def add(a, b) node_a = self.nodes[a] || Node.new(a) node_b = self.nodes[b] || Node.new(b) - + node_a.add(node_b) - + self.nodes[a] ||= node_a self.nodes[b] ||= node_b end @@ -23,16 +23,16 @@ class DependencyGraph class Node attr_accessor :name attr_accessor :dependencies - + def initialize(name) self.name = name self.dependencies = [] end - + def add(node) self.dependencies << node unless self.dependencies.include?(node) end - + def has_node?(node) (self == node) || self.dependencies.any { |x| x.has_node?(node) } end diff --git a/lib/god/diagnostics.rb b/lib/god/diagnostics.rb index d9be6e4a..2ce69094 100644 --- a/lib/god/diagnostics.rb +++ b/lib/god/diagnostics.rb @@ -11,21 +11,21 @@ def start_dike class BleakHouseDiagnostic LOG_FILE = File.join(File.dirname(__FILE__), *%w[.. .. logs bleak.log]) - + class << self attr_accessor :logger end - + def self.install require 'bleak_house' self.logger = BleakHouse::Logger.new File.delete(LOG_FILE) rescue nil end - + def self.snapshot(name) self.logger.snapshot(LOG_FILE, name, false) if self.logger end - + def self.spin(delay = 1) Thread.new do loop do diff --git a/lib/god/driver.rb b/lib/god/driver.rb index 8188faf3..bd536a0b 100644 --- a/lib/god/driver.rb +++ b/lib/god/driver.rb @@ -3,7 +3,7 @@ class TimedEvent include Comparable attr_accessor :at - + # Instantiate a new TimedEvent that will be triggered after the specified delay # +delay+ is the number of seconds from now at which to trigger # @@ -23,13 +23,13 @@ def <=>(other) class DriverEvent < TimedEvent attr_accessor :condition, :task - + def initialize(delay, task, condition) super delay self.task = task self.condition = condition end - + def handle_event @task.handle_poll(@condition) end @@ -44,7 +44,7 @@ def initialize(task, name, args) self.name = name self.args = args end - + # Handle the next queued operation that was issued asynchronously # # Returns nothing @@ -52,8 +52,8 @@ def handle_event @task.send(@name, *@args) end end - - class DriverEventQueue + + class DriverEventQueue def initialize @shutdown = false @waiting = [] @@ -63,9 +63,9 @@ def initialize self.taint end - # + # # Wake any sleeping threads after setting the sentinel - # + # def shutdown @shutdown = true begin @@ -103,8 +103,8 @@ def pop alias shift pop alias deq pop - # - # Add an event to the queue, wake any waiters if what we added needs to + # + # Add an event to the queue, wake any waiters if what we added needs to # happen sooner than the next pending event # def push(event) @@ -150,7 +150,7 @@ def num_waiting class Driver attr_reader :thread - + # Instantiate a new Driver and start the scheduler loop to handle events # +task+ is the Task this Driver belongs to # @@ -158,7 +158,7 @@ class Driver def initialize(task) @task = task @events = God::DriverEventQueue.new - + @thread = Thread.new do loop do begin @@ -174,14 +174,14 @@ def initialize(task) end end end - + # Clear all events for this Driver - # + # # Returns nothing def clear_events @events.clear end - + # Queue an asynchronous message # +name+ is the Symbol name of the operation # +args+ is an optional Array of arguments @@ -190,7 +190,7 @@ def clear_events def message(name, args = []) @events.push(DriverOperation.new(@task, name, args)) end - + # Create and schedule a new DriverEvent # +condition+ is the Condition # +delay+ is the number of seconds to delay (default: interval defined in condition) @@ -198,9 +198,9 @@ def message(name, args = []) # Returns nothing def schedule(condition, delay = condition.interval) applog(nil, :debug, "driver schedule #{condition} in #{delay} seconds") - + @events.push(DriverEvent.new(delay, @task, condition)) end end # Driver - + end # God diff --git a/lib/god/errors.rb b/lib/god/errors.rb index 6ba50c16..5b4814d9 100644 --- a/lib/god/errors.rb +++ b/lib/god/errors.rb @@ -1,24 +1,24 @@ module God - + class AbstractMethodNotOverriddenError < StandardError end - + class NoSuchWatchError < StandardError end - + class NoSuchConditionError < StandardError end - + class NoSuchBehaviorError < StandardError end - + class NoSuchContactError < StandardError end - + class InvalidCommandError < StandardError end - + class EventRegistrationFailedError < StandardError end - + end \ No newline at end of file diff --git a/lib/god/event_handler.rb b/lib/god/event_handler.rb index c46965ef..2bd6b3e9 100644 --- a/lib/god/event_handler.rb +++ b/lib/god/event_handler.rb @@ -3,15 +3,15 @@ class EventHandler @@actions = {} @@handler = nil @@loaded = false - + def self.loaded? @@loaded end - + def self.event_system @@handler::EVENT_SYSTEM end - + def self.load begin case RUBY_PLATFORM @@ -31,13 +31,13 @@ def self.load @@loaded = false end end - + def self.register(pid, event, &block) @@actions[pid] ||= {} @@actions[pid][event] = block @@handler.register_process(pid, @@actions[pid].keys) end - + def self.deregister(pid, event=nil) if watching_pid? pid running = ::Process.kill(0, pid.to_i) rescue false @@ -50,15 +50,15 @@ def self.deregister(pid, event=nil) end end end - + def self.call(pid, event, extra_data = {}) @@actions[pid][event].call(extra_data) if watching_pid?(pid) && @@actions[pid][event] end - + def self.watching_pid?(pid) @@actions[pid] end - + def self.start Thread.new do loop do @@ -71,41 +71,41 @@ def self.start end end end - + # do a real test to make sure events are working properly @@loaded = self.operational? end - + def self.operational? com = [false] - + Thread.new do begin event_system = God::EventHandler.event_system - + pid = fork do loop { sleep(1) } end - + self.register(pid, :proc_exit) do com[0] = true end - + ::Process.kill('KILL', pid) - + sleep(0.1) - + self.deregister(pid, :proc_exit) rescue nil rescue => e puts e.message puts e.backtrace.join("\n") end end.join - + sleep(0.1) - + com.first end - + end end \ No newline at end of file diff --git a/lib/god/event_handlers/dummy_handler.rb b/lib/god/event_handlers/dummy_handler.rb index e453dbbd..2e700d4b 100644 --- a/lib/god/event_handlers/dummy_handler.rb +++ b/lib/god/event_handlers/dummy_handler.rb @@ -1,11 +1,11 @@ module God class DummyHandler EVENT_SYSTEM = "none" - + def self.register_process raise NotImplementedError end - + def self.handle_events raise NotImplementedError end diff --git a/lib/god/event_handlers/kqueue_handler.rb b/lib/god/event_handlers/kqueue_handler.rb index 1068bed4..7d01e1a9 100644 --- a/lib/god/event_handlers/kqueue_handler.rb +++ b/lib/god/event_handlers/kqueue_handler.rb @@ -3,11 +3,11 @@ module God class KQueueHandler EVENT_SYSTEM = "kqueue" - + def self.register_process(pid, events) monitor_process(pid, events_mask(events)) end - + def self.events_mask(events) events.inject(0) do |mask, event| mask |= event_mask(event) diff --git a/lib/god/event_handlers/netlink_handler.rb b/lib/god/event_handlers/netlink_handler.rb index eb7e1573..6d775850 100644 --- a/lib/god/event_handlers/netlink_handler.rb +++ b/lib/god/event_handlers/netlink_handler.rb @@ -3,7 +3,7 @@ module God class NetlinkHandler EVENT_SYSTEM = "netlink" - + def self.register_process(pid, events) # netlink doesn't need to do this # it just reads from the eventhandler actions to see if the pid diff --git a/lib/god/logger.rb b/lib/god/logger.rb index 5215c6b0..bb59707b 100644 --- a/lib/god/logger.rb +++ b/lib/god/logger.rb @@ -1,20 +1,20 @@ module God - + class Logger < SimpleLogger SYSLOG_EQUIVALENTS = {:fatal => :crit, :error => :err, :warn => :debug, :info => :debug, :debug => :debug} - + attr_accessor :logs - + class << self attr_accessor :syslog end - + self.syslog ||= true - + # Instantiate a new Logger object def initialize super($stdout) @@ -26,17 +26,17 @@ def initialize @templog.level = Logger::INFO load_syslog end - + # If Logger.syslog is true then attempt to load the syslog bindings. If syslog # cannot be loaded, then set Logger.syslog to false and continue. # # Returns nothing def load_syslog return unless Logger.syslog - + begin require 'syslog' - + # Ensure that Syslog is open begin Syslog.open('god') @@ -47,7 +47,7 @@ def load_syslog Logger.syslog = false end end - + # Log a message # +watch+ is the String name of the Watch (may be nil if not Watch is applicable) # +level+ is the log level [:debug|:info|:warn|:error|:fatal] @@ -57,7 +57,7 @@ def load_syslog def log(watch, level, text) # initialize watch log if necessary self.logs[watch.name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) if watch - + # push onto capture and timeline for the given watch @templogio.truncate(0) @templogio.rewind @@ -66,14 +66,14 @@ def log(watch, level, text) @capture.puts(@templogio.string.dup) if @capture self.logs[watch.name] << [Time.now, @templogio.string.dup] if watch end - + # send to regular logger self.send(level, text % []) - + # send to syslog Syslog.send(SYSLOG_EQUIVALENTS[level], text) if Logger.syslog end - + # Get all log output for a given Watch since a certain Time. # +watch_name+ is the String name of the Watch # +since+ is the Time since which to fetch log lines @@ -82,7 +82,7 @@ def log(watch, level, text) def watch_log_since(watch_name, since) # initialize watch log if necessary self.logs[watch_name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) - + # get and join lines since given time @mutex.synchronize do self.logs[watch_name].select do |x| @@ -92,9 +92,9 @@ def watch_log_since(watch_name, since) end.join end end - + # private - + # Enable capturing of log # # Returns nothing @@ -103,7 +103,7 @@ def start_capture @capture = StringIO.new end end - + # Disable capturing of log and return what was captured since # capturing was enabled with Logger#start_capture # @@ -116,5 +116,5 @@ def finish_capture end end end - + end \ No newline at end of file diff --git a/lib/god/metric.rb b/lib/god/metric.rb index 936f2ab6..7a709a82 100644 --- a/lib/god/metric.rb +++ b/lib/god/metric.rb @@ -1,14 +1,14 @@ module God - + class Metric attr_accessor :watch, :destination, :conditions - + def initialize(watch, destination = nil) self.watch = watch self.destination = destination self.conditions = [] end - + # Instantiate a Condition of type +kind+ and pass it into the optional # block. Attributes of the condition must be set in the config file def condition(kind) @@ -18,18 +18,18 @@ def condition(kind) rescue NoSuchConditionError => e abort e.message end - + # send to block so config can set attributes yield(c) if block_given? - + # call prepare on the condition c.prepare - + # test generic and specific validity unless Condition.valid?(c) && c.valid? abort "Exiting on invalid condition" end - + # inherit interval from watch if no poll condition specific interval was set if c.kind_of?(PollCondition) && !c.interval if self.watch.interval @@ -38,22 +38,22 @@ def condition(kind) abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.watch.name}', and no default Watch interval from which to inherit" end end - + # remember self.conditions << c end - + def enable self.conditions.each do |c| self.watch.attach(c) end end - + def disable self.conditions.each do |c| self.watch.detach(c) end end end - + end \ No newline at end of file diff --git a/lib/god/process.rb b/lib/god/process.rb index 6817416e..fdb16e67 100644 --- a/lib/god/process.rb +++ b/lib/god/process.rb @@ -1,19 +1,19 @@ module God class Process WRITES_PID = [:start, :restart] - + attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart, :unix_socket, :chroot, :env - + def initialize self.log = '/dev/null' - + @pid_file = nil @tracking_pid = true @user_log = false @pid = nil @unix_socket = nil end - + def alive? if self.pid System::Process.new(self.pid).exists? @@ -21,7 +21,7 @@ def alive? false end end - + def file_writable?(file) pid = fork do uid_num = Etc.getpwnam(self.uid).uid if self.uid @@ -34,29 +34,29 @@ def file_writable?(file) File.writable?(file_in_chroot(file)) ? exit(0) : exit(1) end - + wpid, status = ::Process.waitpid2(pid) status.exitstatus == 0 ? true : false end - + def valid? # determine if we're tracking pid or not self.pid_file - + valid = true - + # a start command must be specified if self.start.nil? valid = false applog(self, :error, "No start command was specified") end - + # self-daemonizing processes must specify a stop command if !@tracking_pid && self.stop.nil? valid = false applog(self, :error, "No stop command was specified") end - + # uid must exist if specified if self.uid begin @@ -66,7 +66,7 @@ def valid? applog(self, :error, "UID for '#{self.uid}' does not exist") end end - + # gid must exist if specified if self.gid begin @@ -76,25 +76,25 @@ def valid? applog(self, :error, "GID for '#{self.gid}' does not exist") end end - + # pid dir must exist if specified if !@tracking_pid && !File.exist?(File.dirname(self.pid_file)) valid = false applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist") end - + # pid dir must be writable if specified if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file)) valid = false applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}") end - + # log dir must exist if !File.exist?(File.dirname(self.log)) valid = false applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist") end - + # log file or dir must be writable if File.exist?(self.log) unless file_writable?(self.log) @@ -120,10 +120,10 @@ def valid? LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'") end end - + valid end - + # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev # No really, trust me. Use the instance variable. def pid_file=(value) @@ -133,14 +133,14 @@ def pid_file=(value) else @tracking_pid = true end - + @pid_file = value end - + def pid_file @pid_file ||= default_pid_file end - + # Fetch the PID from pid_file. If the pid_file does not # exist, then use the PID from the last time it was read. # If it has never been read, then return nil. @@ -149,7 +149,7 @@ def pid_file def pid contents = File.read(self.pid_file).strip rescue '' real_pid = contents =~ /^\d+$/ ? contents.to_i : nil - + if real_pid @pid = real_pid real_pid @@ -157,35 +157,35 @@ def pid @pid end end - + def start! call_action(:start) end - + def stop! call_action(:stop) end - + def restart! call_action(:restart) end - + def default_pid_file File.join(God.pid_file_directory, "#{self.name}.pid") end - + def call_action(action) command = send(action) - + if action == :stop && command.nil? pid = self.pid name = self.name command = lambda do applog(self, :info, "#{self.name} stop: default lambda killer") - + ::Process.kill('TERM', pid) rescue nil applog(self, :info, "#{self.name} sent SIGTERM") - + # Poll to see if it's dead 5.times do begin @@ -195,18 +195,18 @@ def call_action(action) applog(self, :info, "#{self.name} process stopped") return end - + sleep 1 end - + ::Process.kill('KILL', pid) rescue nil applog(self, :info, "#{self.name} still alive; sent SIGKILL") end end - + if command.kind_of?(String) pid = nil - + if @tracking_pid # double fork god-daemonized processes # we don't want to wait for them to finish @@ -218,7 +218,7 @@ def call_action(action) pid = self.spawn(command) puts pid.to_s # send pid back to forker end - + ::Process.waitpid(opid, 0) w.close pid = r.gets.chomp @@ -233,19 +233,19 @@ def call_action(action) pid = self.spawn(command) status = ::Process.waitpid2(pid, 0) exit_code = status[1] >> 8 - + if exit_code != 0 applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}") end - + ensure_stop if action == :stop end - + if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action)) File.open(default_pid_file, 'w') do |f| f.write pid end - + @tracking_pid = true @pid_file = default_pid_file end @@ -256,7 +256,7 @@ def call_action(action) raise NotImplementedError end end - + # Fork/exec the given command, returns immediately # +command+ is the String containing the shell command # @@ -276,7 +276,7 @@ def spawn(command) STDIN.reopen "/dev/null" STDOUT.reopen file_in_chroot(self.log), "a" STDERR.reopen STDOUT - + # close any other file descriptors 3.upto(256){|fd| IO::new(fd).close rescue nil} @@ -289,7 +289,7 @@ def spawn(command) exec command unless command.empty? end end - + # Ensure that a stop command actually stops the process. Force kill # if necessary. # @@ -299,7 +299,7 @@ def ensure_stop applog(self, :warn, "#{self.name} stop called but pid is uknown") return end - + # Poll to see if it's dead 10.times do begin @@ -308,10 +308,10 @@ def ensure_stop # It died. Good. return end - + sleep 1 end - + # last resort ::Process.kill('KILL', self.pid) rescue nil applog(self, :warn, "#{self.name} process still running 10 seconds after stop command returned. Force killing.") diff --git a/lib/god/registry.rb b/lib/god/registry.rb index 2a5e71d9..d0f7542e 100644 --- a/lib/god/registry.rb +++ b/lib/god/registry.rb @@ -2,29 +2,29 @@ module God def self.registry @registry ||= Registry.new end - + class Registry def initialize @storage = {} end - + def add(item) # raise TypeError unless item.is_a? God::Process @storage[item.name] = item end - + def remove(item) @storage.delete(item.name) end - + def size @storage.size end - + def [](name) @storage[name] end - + def reset @storage.clear end diff --git a/lib/god/simple_logger.rb b/lib/god/simple_logger.rb index 97677095..afd15117 100644 --- a/lib/god/simple_logger.rb +++ b/lib/god/simple_logger.rb @@ -1,53 +1,53 @@ module God - + class SimpleLogger DEBUG = 2 INFO = 4 WARN = 8 ERROR = 16 FATAL = 32 - + SEV_LABEL = {DEBUG => 'DEBUG', INFO => 'INFO', WARN => 'WARN', ERROR => 'ERROR', FATAL => 'FATAL'} - + attr_accessor :datetime_format, :level - + def initialize(io) @io = io @level = INFO @datetime_format = "%Y-%m-%d %H:%M:%S" end - + def output(level, msg) return if level < self.level - + time = Time.now.strftime(self.datetime_format) label = SEV_LABEL[level] @io.print("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}\n") end - + def fatal(msg) self.output(FATAL, msg) end - + def error(msg) self.output(ERROR, msg) end - + def warn(msg) self.output(WARN, msg) end - + def info(msg) self.output(INFO, msg) end - + def debug(msg) self.output(DEBUG, msg) end end - + end \ No newline at end of file diff --git a/lib/god/socket.rb b/lib/god/socket.rb index 72914032..0b178f8f 100644 --- a/lib/god/socket.rb +++ b/lib/god/socket.rb @@ -5,7 +5,7 @@ module God # The God::Server oversees the DRb server which dishes out info on this God daemon. class Socket attr_reader :port - + # The location of the socket for a given port # +port+ is the port number # @@ -13,7 +13,7 @@ class Socket def self.socket_file(port) "/tmp/god.#{port}.sock" end - + # The address of the socket for a given port # +port+ is the port number # @@ -21,40 +21,40 @@ def self.socket_file(port) def self.socket(port) "drbunix://#{self.socket_file(port)}" end - + # The location of the socket for this Server # # Returns String (file location) def socket_file self.class.socket_file(@port) end - + # The address of the socket for this Server # # Returns String (drb address) def socket self.class.socket(@port) end - + # Create a new Server and star the DRb server # +port+ is the port on which to start the DRb service (default nil) def initialize(port = nil) @port = port start end - + # Returns true def ping true end - + # Forward API calls to God # # Returns whatever the forwarded call returns def method_missing(*args, &block) God.send(*args, &block) end - + # Stop the DRb server and delete the socket file # # Returns nothing @@ -62,9 +62,9 @@ def stop DRb.stop_service FileUtils.rm_f(self.socket_file) end - + private - + # Start the DRb server. Abort if there is already a running god instance # on the socket. # @@ -77,7 +77,7 @@ def start applog(nil, :info, "Socket already in use") DRb.start_service server = DRbObject.new(nil, self.socket) - + begin Timeout.timeout(5) do server.ping diff --git a/lib/god/sugar.rb b/lib/god/sugar.rb index 11795572..6b625baf 100644 --- a/lib/god/sugar.rb +++ b/lib/god/sugar.rb @@ -2,45 +2,45 @@ class Numeric def seconds self end - + alias :second :seconds - + def minutes self * 60 end - + alias :minute :minutes - + def hours self * 3600 end - + alias :hour :hours - + def days self * 86400 end - + alias :day :days - + def kilobytes self end - + alias :kilobyte :kilobytes - + def megabytes self * 1024 end - + alias :megabyte :megabytes - + def gigabytes self * (1024 ** 2) end - + alias :gigabyte :gigabytes - + def percent self end diff --git a/lib/god/system/portable_poller.rb b/lib/god/system/portable_poller.rb index 95eb0e5a..d9a28759 100644 --- a/lib/god/system/portable_poller.rb +++ b/lib/god/system/portable_poller.rb @@ -8,31 +8,31 @@ def initialize(pid) def memory ps_int('rss') end - + # Percentage memory usage def percent_memory ps_float('%mem') end - + # Percentage CPU usage def percent_cpu ps_float('%cpu') end - + private - + def ps_int(keyword) `ps -o #{keyword}= -p #{@pid}`.to_i end - + def ps_float(keyword) `ps -o #{keyword}= -p #{@pid}`.to_f end - + def ps_string(keyword) `ps -o #{keyword}= -p #{@pid}`.strip end - + def time_string_to_seconds(text) _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/) (minutes.to_i * 60) + seconds.to_i diff --git a/lib/god/system/process.rb b/lib/god/system/process.rb index e2741bf0..0ce0051b 100644 --- a/lib/god/system/process.rb +++ b/lib/god/system/process.rb @@ -1,34 +1,34 @@ module God module System - + class Process def initialize(pid) @pid = pid.to_i @poller = fetch_system_poller.new(@pid) end - + # Return true if this process is running, false otherwise def exists? !!::Process.kill(0, @pid) rescue false end - + # Memory usage in kilobytes (resident set size) def memory @poller.memory end - + # Percentage memory usage def percent_memory @poller.percent_memory end - + # Percentage CPU usage def percent_cpu @poller.percent_cpu end - + private - + def fetch_system_poller if SlashProcPoller.usable? SlashProcPoller @@ -37,6 +37,6 @@ def fetch_system_poller end end end - + end end \ No newline at end of file diff --git a/lib/god/system/slash_proc_poller.rb b/lib/god/system/slash_proc_poller.rb index 31879c8c..2c256edb 100644 --- a/lib/god/system/slash_proc_poller.rb +++ b/lib/god/system/slash_proc_poller.rb @@ -4,12 +4,12 @@ class SlashProcPoller < PortablePoller @@kb_per_page = 4 # TODO: Need to make this portable @@hertz = 100 @@total_mem = nil - + MeminfoPath = '/proc/meminfo' UptimePath = '/proc/uptime' - + RequiredPaths = [MeminfoPath, UptimePath] - + # FreeBSD has /proc by default, but nothing mounted there! # So we should check for the actual required paths! # Returns true if +RequiredPaths+ are readable. @@ -18,29 +18,29 @@ def self.usable? test(?r, path) end end - + def initialize(pid) super(pid) - + unless @@total_mem # in K File.open(MeminfoPath) do |f| @@total_mem = f.gets.split[1] end end end - + def memory stat[:rss].to_i * @@kb_per_page rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + def percent_memory (memory / @@total_mem.to_f) * 100 rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + # TODO: Change this to calculate the wma instead def percent_cpu stats = stat @@ -54,14 +54,14 @@ def percent_cpu rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + private - + # in seconds def uptime File.read(UptimePath).split[0].to_f end - + def stat stats = {} stats[:pid], stats[:comm], stats[:state], stats[:ppid], stats[:pgrp], diff --git a/lib/god/task.rb b/lib/god/task.rb index 7c88da79..bd0ff185 100644 --- a/lib/god/task.rb +++ b/lib/god/task.rb @@ -1,87 +1,87 @@ module God - + class Task attr_accessor :name, :interval, :group, :valid_states, :initial_state, :driver - + attr_writer :autostart def autostart?; @autostart; end - + # api attr_accessor :state, :behaviors, :metrics, :directory - + def initialize @autostart ||= true - + # initial state is unmonitored self.state = :unmonitored - + # the list of behaviors self.behaviors = [] - + # the list of conditions for each action self.metrics = {nil => [], :unmonitored => [], :stop => []} - + # the condition -> metric lookup self.directory = {} - + # driver self.driver = Driver.new(self) end - + def prepare self.valid_states.each do |state| self.metrics[state] ||= [] end end - + def valid? valid = true - + # a name must be specified if self.name.nil? valid = false applog(self, :error, "No name was specified") end - + # valid_states must be specified if self.valid_states.nil? valid = false applog(self, :error, "No valid_states array was specified") end - + # valid_states must be specified if self.initial_state.nil? valid = false applog(self, :error, "No initial_state was specified") end - + valid end - + ########################################################################### # # Advanced mode # ########################################################################### - + def canonical_hash_form(to) to.instance_of?(Symbol) ? {true => to} : to end - + # Define a transition handler which consists of a set of conditions def transition(start_states, end_states) # convert end_states into canonical hash form canonical_end_states = canonical_hash_form(end_states) - + Array(start_states).each do |start_state| # validate start state unless self.valid_states.include?(start_state) abort "Invalid state :#{start_state}. Must be one of the symbols #{self.valid_states.map{|x| ":#{x}"}.join(', ')}" end - + # create a new metric to hold the watch, end states, and conditions m = Metric.new(self, canonical_end_states) - + if block_given? # let the config file define some conditions on the metric yield(m) @@ -91,54 +91,54 @@ def transition(start_states, end_states) c.what = true end end - + # populate the condition -> metric directory m.conditions.each do |c| self.directory[c] = m end - + # record the metric self.metrics[start_state] ||= [] self.metrics[start_state] << m end end - + def lifecycle # create a new metric to hold the watch and conditions m = Metric.new(self) - + # let the config file define some conditions on the metric yield(m) - + # populate the condition -> metric directory m.conditions.each do |c| self.directory[c] = m end - + # record the metric self.metrics[nil] << m end - + ########################################################################### # # Lifecycle # ########################################################################### - + # Enable monitoring # # Returns nothing def monitor self.move(self.initial_state) end - + # Disable monitoring # # Returns nothing def unmonitor self.move(:unmonitored) end - + # Move to the givent state # +to_state+ is the Symbol representing the state to move to # @@ -146,85 +146,85 @@ def unmonitor def move(to_state) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:move, [to_state]) else # called from within Driver - + # record original info orig_to_state = to_state from_state = self.state - + # log msg = "#{self.name} move '#{from_state}' to '#{to_state}'" applog(self, :info, msg) - + # cleanup from current state self.driver.clear_events self.metrics[from_state].each { |m| m.disable } if to_state == :unmonitored self.metrics[nil].each { |m| m.disable } end - + # perform action self.action(to_state) - + # enable simple mode if [:start, :restart].include?(to_state) && self.metrics[to_state].empty? to_state = :up end - + # move to new state self.metrics[to_state].each { |m| m.enable } - + # if no from state, enable lifecycle metric if from_state == :unmonitored self.metrics[nil].each { |m| m.enable } end - + # set state self.state = to_state - + # broadcast to interested TriggerConditions Trigger.broadcast(self, :state_change, [from_state, orig_to_state]) - + # log msg = "#{self.name} moved '#{from_state}' to '#{to_state}'" applog(self, :info, msg) end - + self end - + # Notify the Driver that an EventCondition has triggered # # Returns nothing def trigger(condition) self.driver.message(:handle_event, [condition]) end - + ########################################################################### # # Actions # ########################################################################### - + def method_missing(sym, *args) unless (sym.to_s =~ /=$/) super end - + base = sym.to_s.chop.intern - + unless self.valid_states.include?(base) super end - + self.class.send(:attr_accessor, base) self.send(sym, *args) end - + # Perform the given action # +a+ is the action Symbol # +c+ is the Condition @@ -233,25 +233,25 @@ def method_missing(sym, *args) def action(a, c = nil) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:action, [a, c]) else # called from within Driver - + if self.respond_to?(a) command = self.send(a) - + case command when String msg = "#{self.name} #{a}: #{command}" applog(self, :info, msg) - + system(command) when Proc msg = "#{self.name} #{a}: lambda" applog(self, :info, msg) - + command.call else raise NotImplementedError @@ -259,13 +259,13 @@ def action(a, c = nil) end end end - + ########################################################################### # # Events # ########################################################################### - + def attach(condition) case condition when PollCondition @@ -274,7 +274,7 @@ def attach(condition) condition.register end end - + def detach(condition) case condition when PollCondition @@ -283,27 +283,27 @@ def detach(condition) condition.deregister end end - + ########################################################################### # # Registration # ########################################################################### - + def register! # override if necessary end - + def unregister! # override if necessary end - + ########################################################################### # # Handlers # ########################################################################### - + # Evaluate and handle the given poll condition. Handles logging # notifications, and moving to the new state if necessary # +condition+ is the Condition to handle @@ -312,23 +312,23 @@ def unregister! def handle_poll(condition) # lookup metric metric = self.directory[condition] - + # run the test result = condition.test - + # log messages = self.log_line(self, metric, condition, result) - + # notify if condition.notify && self.trigger?(metric, result) self.notify(condition, messages.last) end - + # after-condition condition.after - + # get the destination - dest = + dest = if result && condition.transition # condition override condition.transition @@ -336,7 +336,7 @@ def handle_poll(condition) # regular metric.destination && metric.destination[result] end - + # transition or reschedule if dest # transition @@ -345,7 +345,7 @@ def handle_poll(condition) rescue EventRegistrationFailedError msg = self.name + ' Event registration failed, moving back to previous state' applog(self, :info, msg) - + dest = self.state retry end @@ -354,7 +354,7 @@ def handle_poll(condition) self.driver.schedule(condition) end end - + # Asynchronously evaluate and handle the given event condition. Handles logging # notifications, and moving to the new state if necessary # +condition+ is the Condition to handle @@ -363,17 +363,17 @@ def handle_poll(condition) def handle_event(condition) # lookup metric metric = self.directory[condition] - + # log messages = self.log_line(self, metric, condition, true) - + # notify if condition.notify && self.trigger?(metric, true) self.notify(condition, messages.last) end - + # get the destination - dest = + dest = if condition.transition # condition override condition.transition @@ -381,12 +381,12 @@ def handle_event(condition) # regular metric.destination && metric.destination[true] end - + if dest self.move(dest) end end - + # Determine whether a trigger happened # +metric+ is the Metric # +result+ is the result from the condition's test @@ -395,7 +395,7 @@ def handle_event(condition) def trigger?(metric, result) metric.destination && metric.destination[result] end - + # Log info about the condition and return the list of messages logged # +watch+ is the Watch # +metric+ is the Metric @@ -404,15 +404,15 @@ def trigger?(metric, result) # # Returns String[] def log_line(watch, metric, condition, result) - status = + status = if self.trigger?(metric, result) "[trigger]" else "[ok]" end - + messages = [] - + # log info if available if condition.info Array(condition.info).each do |condition_info| @@ -423,14 +423,14 @@ def log_line(watch, metric, condition, result) messages << "#{watch.name} #{status} (#{condition.base_name})" status == '[trigger]' ? applog(watch, :warn, messages.last) : applog(watch, :info, messages.last) end - + # log debug_message = watch.name + ' ' + condition.base_name + " [#{result}] " + self.dest_desc(metric, condition) applog(watch, :debug, debug_message) - + messages end - + # Format the destination specification for use in debug logging # +metric+ is the Metric # +condition+ is the Condition @@ -447,7 +447,7 @@ def dest_desc(metric, condition) end end end - + # Notify all recipeients of the given condition with the specified message # +condition+ is the Condition # +message+ is the String message to send @@ -456,7 +456,7 @@ def dest_desc(metric, condition) def notify(condition, message) spec = Contact.normalize(condition.notify) unmatched = [] - + # resolve contacts resolved_contacts = spec[:contacts].inject([]) do |acc, contact_name_or_group| @@ -465,23 +465,23 @@ def notify(condition, message) acc += cons acc end - + # warn about unmatched contacts unless unmatched.empty? msg = "#{condition.watch.name} no matching contacts for '#{unmatched.join(", ")}'" applog(condition.watch, :warn, msg) end - + # notify each contact resolved_contacts.each do |c| host = `hostname`.chomp rescue 'none' c.notify(message, Time.now, spec[:priority], spec[:category], host) - + msg = "#{condition.watch.name} #{c.info ? c.info : "notification sent for contact: #{c.name}"} (#{c.base_name})" - + applog(condition.watch, :info, msg % []) end end end - + end diff --git a/lib/god/timeline.rb b/lib/god/timeline.rb index 4d021bd4..61f2f59e 100644 --- a/lib/god/timeline.rb +++ b/lib/god/timeline.rb @@ -1,5 +1,5 @@ module God - + class Timeline < Array # Instantiate a new Timeline # +max_size+ is the maximum size to which the timeline should grow @@ -9,7 +9,7 @@ def initialize(max_size) super() @max_size = max_size end - + # Push a value onto the Timeline # +val+ is the value to push # @@ -18,8 +18,8 @@ def push(val) self.concat([val]) shift if size > @max_size end - + alias_method :<<, :push end - + end \ No newline at end of file diff --git a/lib/god/trigger.rb b/lib/god/trigger.rb index 0f022840..f753dc2b 100644 --- a/lib/god/trigger.rb +++ b/lib/god/trigger.rb @@ -1,43 +1,43 @@ module God - + class Trigger - + class << self attr_accessor :triggers # {task.name => condition} end - + # init self.triggers = {} @mutex = Mutex.new - + def self.register(condition) @mutex.synchronize do self.triggers[condition.watch.name] ||= [] self.triggers[condition.watch.name] << condition end end - + def self.deregister(condition) @mutex.synchronize do self.triggers[condition.watch.name].delete(condition) self.triggers.delete(condition.watch.name) if self.triggers[condition.watch.name].empty? end end - + def self.broadcast(task, message, payload) return unless self.triggers[task.name] - + @mutex.synchronize do self.triggers[task.name].each do |t| t.process(message, payload) end end end - + def self.reset self.triggers.clear end - + end - + end \ No newline at end of file diff --git a/lib/god/watch.rb b/lib/god/watch.rb index 34589cf4..ececbdd8 100644 --- a/lib/god/watch.rb +++ b/lib/god/watch.rb @@ -2,43 +2,43 @@ require 'forwardable' module God - + class Watch < Task VALID_STATES = [:init, :up, :start, :restart] INITIAL_STATE = :init - + # config attr_accessor :grace, :start_grace, :stop_grace, :restart_grace - + extend Forwardable def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart, :name=, :uid=, :gid=, :start=, :stop=, :restart=, :pid_file, :pid_file=, :log, :log=, :alive?, :pid, :unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env= - # + # def initialize super - + @process = God::Process.new - + # valid states self.valid_states = VALID_STATES self.initial_state = INITIAL_STATE - + # no grace period by default self.grace = self.start_grace = self.stop_grace = self.restart_grace = 0 end - + def valid? super && @process.valid? end - + ########################################################################### # # Behavior # ########################################################################### - + def behavior(kind) # create the behavior begin @@ -46,47 +46,47 @@ def behavior(kind) rescue NoSuchBehaviorError => e abort e.message end - + # send to block so config can set attributes yield(b) if block_given? - + # abort if the Behavior is invalid, the Behavior will have printed # out its own error messages by now abort unless b.valid? - + self.behaviors << b end - + ########################################################################### # # Simple mode # ########################################################################### - + def start_if self.transition(:up, :start) do |on| yield(on) end end - + def restart_if self.transition(:up, :restart) do |on| yield(on) end end - - def stop_if - self.transition(:up, :stop) do |on| - yield(on) - end + + def stop_if + self.transition(:up, :stop) do |on| + yield(on) + end end - + ########################################################################### # # Lifecycle # ########################################################################### - + # Enable monitoring def monitor # start monitoring at the first available of the init or up states @@ -96,22 +96,22 @@ def monitor self.move(:up) end end - + ########################################################################### # # Actions # ########################################################################### - + def action(a, c = nil) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:action, [a, c]) else # called from within Driver - + case a when :start call_action(c, :start) @@ -129,10 +129,10 @@ def action(a, c = nil) sleep(self.stop_grace + self.grace) end end - + self end - + def call_action(condition, action) # before before_items = self.behaviors @@ -144,15 +144,15 @@ def call_action(condition, action) applog(self, :info, msg) end end - + # log if self.send(action) msg = "#{self.name} #{action}: #{self.send(action).to_s}" applog(self, :info, msg) end - + @process.call_action(action) - + # after after_items = self.behaviors after_items += [condition] if condition @@ -164,20 +164,20 @@ def call_action(condition, action) end end end - + ########################################################################### # # Registration # ########################################################################### - + def register! God.registry.add(@process) end - + def unregister! God.registry.remove(@process) end end - + end diff --git a/site/index.html b/site/index.html index 7f991fd6..4f4657c1 100644 --- a/site/index.html +++ b/site/index.html @@ -46,18 +46,18 @@
The best way to get god is via rubygems:
$ sudo gem install god
You can also peruse or clone the code from git://github.com/mojombo/god.git
- +God currently only works on Linux (kernel 2.6.15+), BSD, and Darwin systems. No support for Windows is planned. Event based conditions on Linux systems require the cn (connector) kernel module loaded or compiled in to the kernel and god must be run as root.
The following systems have been tested. Help us test it on others!
- +The easiest way to understand how god will make your life better is by looking at a sample config file. The following configuration file is what I use at gravatar.com to keep the mongrels running:
- +# run with: god -c /path/to/gravatar.god
-#
+#
# This is the actual config file used to keep the mongrels of
# gravatar.com running.
@@ -79,7 +79,7 @@ Finally, a Config File that Makes Sense
%w{8200 8201 8202}.each do |port|
God.watch do |w|
w.name = "gravatar2-mongrel-#{port}"
- w.interval = 30.seconds # default
+ w.interval = 30.seconds # default
w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \
-P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d"
w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid"
@@ -87,7 +87,7 @@ Finally, a Config File that Makes Sense
w.start_grace = 10.seconds
w.restart_grace = 10.seconds
w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid")
-
+
w.behavior(:clean_pid_file)
w.start_if do |start|
@@ -96,19 +96,19 @@ Finally, a Config File that Makes Sense
c.running = false
end
end
-
+
w.restart_if do |restart|
restart.condition(:memory_usage) do |c|
c.above = 150.megabytes
c.times = [3, 5] # 3 out of 5 intervals
end
-
+
restart.condition(:cpu_usage) do |c|
c.above = 50.percent
c.times = 5
end
end
-
+
# lifecycle
w.lifecycle do |on|
on.condition(:flapping) do |c|
@@ -138,7 +138,7 @@ Finally, a Config File that Makes Sense
God.watch do |w|
w.name = "gravatar2-mongrel-#{port}"
- w.interval = 30.seconds # default
+ w.interval = 30.seconds # default
w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \
-P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d"
w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid"
@@ -146,14 +146,14 @@ Finally, a Config File that Makes Sense
w.start_grace = 10.seconds
w.restart_grace = 10.seconds
w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid")
-
+
...
end
A watch represents a single process that has concrete start, stop, and/or restart operations. You can define as many watches as you like. In the example above, I've got some Rails instances running in Mongrels that I need to keep alive. Every watch must have a unique name so that it can be identified later on. The interval option sets the default poll interval (this can be overridden in each condition). The start and stop attributes specify the commands to start and stop the process. If no restart attribute is set, restart will be represented by a call to stop followed by a call to start. The optional grace attribute sets the amount of time following a start/stop/restart command to wait before resuming normal monitoring operations. To be more specific, I can set just start_grace, stop_grace, and/or restart_grace. If the process you're watching runs as a daemon (as mine does), you'll need to set the pid_file attribute.
w.behavior(:clean_pid_file)
-
+
Behaviors allow you to execute additional commands around start/stop/restart commands. In our case, if the process dies it will leave a PID file behind. The next time a start command is issued, it will fail, complaining about the leftover PID file. We'd like the PID file cleaned up before a start command is issued. The built-in behavior clean_pid_file will do just that.
w.start_if do |start|
@@ -162,8 +162,8 @@ Finally, a Config File that Makes Sense
c.running = false
end
end
-
-Watches contain conditions grouped by the action to execute should they return true. I start with a start_if block that contains a single condition. Conditions are specified by calling condition with an identifier, in this case
+
+
Watches contain conditions grouped by the action to execute should they return true. I start with a start_if block that contains a single condition. Conditions are specified by calling condition with an identifier, in this case
:process_running. Each condition can specify a poll interval that will override the default watch interval. In this case, I want to check that the process is still running every 5 seconds instead of the 30 second interval that other conditions will inherit. The ability to set condition specific poll intervals makes it possible to run critical tests (such as :process_running) more often than less critical tests (such as :memory_usage and :cpu_usage).
w.restart_if do |restart|
@@ -171,7 +171,7 @@ Finally, a Config File that Makes Sense
c.above = 150.megabytes
c.times = [3, 5] # 3 out of 5 intervals
end
-
+
...
end
@@ -179,13 +179,13 @@ Finally, a Config File that Makes Sense
w.restart_if do |restart|
...
-
+
restart.condition(:cpu_usage) do |c|
c.above = 50.percent
c.times = 5
end
end
-
+
To keep an eye on CPU usage, I've employed the cpu_usage condition. When CPU usage for a Mongrel process is over 50% for 5 consecutive intervals, it will be restarted.
w.lifecycle do |on|
@@ -199,9 +199,9 @@ Finally, a Config File that Makes Sense
c.retry_within = 2.hours
end
end
-
-Conditions inside a lifecycle section are active as long as the process is being monitored (they live across state changes).
-
+
+
Conditions inside a lifecycle section are active as long as the process is being monitored (they live across state changes).
+
The :flapping condition guards against the edge case wherein god rapidly starts or restarts your application. Things like server configuration changes or the unavailability of external services could make it impossible for my process to start. In that case, god will try to start my process over and over to no avail. The :flapping condition provides two levels of giving up on flapping processes. If I were to translate the options of the code above, it would be something like: If this watch is started or restarted five times withing 5 minutes, then unmonitor it...then after ten minutes, monitor it again to see if it was just a temporary problem; if the process is seen to be flapping five times within two hours, then give up completely.
That's it! Simple, huh?
@@ -214,13 +214,13 @@ Changing UID/GID for processes
God.watch do |w|
...
-
+
w.uid = 'tom'
w.gid = 'devs'
-
+
...
end
-
+
This only works for commands specified as a string. Lambda commands are unaffected.
@@ -232,9 +232,9 @@ Lambda commands
God.watch do |w|
...
-
+
w.start = lambda { ENV['APACHE'] ? `apachectl -k graceful` : `lighttpd restart` }
-
+
...
end
@@ -262,12 +262,12 @@ Grouping Watches
God.watch do |w|
...
-
+
w.group = 'mongrels'
-
+
...
end
-
+
The above configuration now allows you to control the watch (and any others that are in the group) with a single command:
$ sudo god stop mongrels
@@ -289,23 +289,23 @@ Advanced Configuration with Transitions and Events
w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.pid"
w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.pid"
w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid")
-
+
# clean pid files before start if necessary
w.behavior(:clean_pid_file)
-
+
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
-
+
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
end
-
+
# failsafe
on.condition(:tries) do |c|
c.times = 5
@@ -317,7 +317,7 @@ Advanced Configuration with Transitions and Events
w.transition(:up, :start) do |on|
on.condition(:process_exits)
end
-
+
# restart if memory or cpu is too high
w.transition(:up, :restart) do |on|
on.condition(:memory_usage) do |c|
@@ -325,14 +325,14 @@ Advanced Configuration with Transitions and Events
c.above = 50.megabytes
c.times = [3, 5]
end
-
+
on.condition(:cpu_usage) do |c|
c.interval = 10
c.above = 10.percent
c.times = [3, 5]
end
end
-
+
# lifecycle
w.lifecycle do |on|
on.condition(:flapping) do |c|
@@ -351,7 +351,7 @@ Advanced Configuration with Transitions and Events
A bit longer, I know, but very straighforward once you understand how the transition calls work. The name, interval, start, stop, and pid_file attributes should be familiar. We also specify the clean_pid_file behavior.
Before jumping into the code, it's important to understand the different states that a Watch can have, and how that state changes over time. At any given time, a Watch will be in one of the init, up, start, or restart states. As different conditions are satisfied, the Watch will progress from state to state, enabling and disabling conditions along the way.
-
+
When god first starts, each Watch is placed in the init state.
You'll use the transition method to tell god how to transition between states. It takes two arguments. The first argument may be either a symbol or an array of symbols representing the state or states during which the specified conditions should be enabled. The second argument may be either a symbol or a hash. If it is a symbol, then that is the state that will be transitioned to if any of the conditions return true. If it is a hash, then that hash must have both true and false keys, each of which point to a symbol that represents the state to transition to given the corresponding return from the single condition that must be specified.
@@ -370,7 +370,7 @@ Advanced Configuration with Transitions and Events
on.condition(:process_running) do |c|
c.running = true
end
-
+
...
end
@@ -379,7 +379,7 @@ # determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
...
-
+
# failsafe
on.condition(:tries) do |c|
c.times = 5
@@ -403,7 +403,7 @@ Advanced Configuration with Transitions and Events
c.above = 50.megabytes
c.times = [3, 5]
end
-
+
on.condition(:cpu_usage) do |c|
c.interval = 10
c.above = 10.percent
@@ -516,15 +516,15 @@ Notifications
c.notify = 'tom'
end
end
-
+
There are two ways to specify that a notification should be sent. The first, easier way is shown above. Every condition can take an optional notify attribute that specifies which contacts should be notified when the condition is triggered. The value can be a contact name or contact group *or* an array of contact names and/or contact groups.
w.transition(:up, :start) do |on|
on.condition(:process_exits) do |c|
c.notify = {:contacts => ['tom', 'developers'], :priority => 1, :category => 'product'}
end
end
-
+
The second way allows you to specify the priority and category in addition to the contacts. The extra attributes can be arbitrary integers or strings and will be passed as-is to the notification subsystem.
The above notification will arrive as an email similar to the following.
diff --git a/test/configs/child_events/child_events.god b/test/configs/child_events/child_events.god index d946affa..3d3d9627 100644 --- a/test/configs/child_events/child_events.god +++ b/test/configs/child_events/child_events.god @@ -3,20 +3,20 @@ God.watch do |w| w.interval = 5.seconds w.start = File.join(GOD_ROOT, *%w[test configs child_events simple_server.rb]) # w.log = File.join(GOD_ROOT, *%w[test configs child_events god.log]) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 2 @@ -28,7 +28,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/test/configs/child_polls/child_polls.god b/test/configs/child_polls/child_polls.god index c563ad4b..5b032c49 100644 --- a/test/configs/child_polls/child_polls.god +++ b/test/configs/child_polls/child_polls.god @@ -3,25 +3,25 @@ God.watch do |w| w.start = File.join(GOD_ROOT, *%w[test configs child_polls simple_server.rb]) w.interval = 5 w.grace = 2 - + w.start_if do |start| start.condition(:process_running) do |c| c.running = false end end - + w.restart_if do |restart| restart.condition(:cpu_usage) do |c| c.above = 30.percent c.times = [3, 5] end - + restart.condition(:memory_usage) do |c| c.above = 10.megabytes c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/test/configs/complex/complex.god b/test/configs/complex/complex.god index d03d6358..f20ac7bc 100644 --- a/test/configs/complex/complex.god +++ b/test/configs/complex/complex.god @@ -3,20 +3,20 @@ God.watch do |w| w.interval = 5.seconds w.start = File.join(GOD_ROOT, *%w[test configs complex simple_server.rb]) # w.log = File.join(GOD_ROOT, *%w[test configs child_events god.log]) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 2 @@ -28,7 +28,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if process is misbehaving w.transition(:up, :restart) do |on| on.condition(:complex) do |cc| @@ -36,14 +36,14 @@ God.watch do |w| c.above = 0.percent c.times = 1 end - + cc.and(:memory_usage) do |c| c.above = 0.megabytes c.times = 3 end end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/test/configs/contact/contact.god b/test/configs/contact/contact.god index da76deec..df5c5891 100644 --- a/test/configs/contact/contact.god +++ b/test/configs/contact/contact.god @@ -31,20 +31,20 @@ God.watch do |w| w.uid = 'tom' w.gid = 'tom' w.log = "/Users/tom/contact.log" - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 2 @@ -58,7 +58,7 @@ God.watch do |w| c.notify = {:contacts => ['tom', 'foobar'], :priority => 1, :category => 'product'} end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/test/configs/daemon_events/daemon_events.god b/test/configs/daemon_events/daemon_events.god index 3a91f980..7d34698b 100644 --- a/test/configs/daemon_events/daemon_events.god +++ b/test/configs/daemon_events/daemon_events.god @@ -7,22 +7,22 @@ God.watch do |w| w.log = File.join(File.dirname(__FILE__), 'daemon_events.log') w.uid = 'tom' w.gid = 'tom' - + w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 2 diff --git a/test/configs/daemon_polls/daemon_polls.god b/test/configs/daemon_polls/daemon_polls.god index c770042a..af1daccf 100644 --- a/test/configs/daemon_polls/daemon_polls.god +++ b/test/configs/daemon_polls/daemon_polls.god @@ -6,9 +6,9 @@ God.watch do |w| w.pid_file = '/var/run/daemon-polls.pid' w.start_grace = 2.seconds w.log = File.join(File.dirname(__FILE__), *%w[out.log]) - + w.behavior(:clean_pid_file) - + w.start_if do |start| start.condition(:process_running) do |c| c.running = false diff --git a/test/configs/degrading_lambda/degrading_lambda.god b/test/configs/degrading_lambda/degrading_lambda.god index 87dd2a37..33e37951 100644 --- a/test/configs/degrading_lambda/degrading_lambda.god +++ b/test/configs/degrading_lambda/degrading_lambda.god @@ -4,13 +4,13 @@ God.watch do |w| w.interval = 5 w.grace = 2 w.group = 'test' - + w.start_if do |start| start.condition(:process_running) do |c| c.running = false end end - + w.restart_if do |restart| restart.condition(:degrading_lambda) do |c| require 'socket' diff --git a/test/configs/matias/matias.god b/test/configs/matias/matias.god index fd0ba0dd..737cc85c 100644 --- a/test/configs/matias/matias.god +++ b/test/configs/matias/matias.god @@ -5,7 +5,7 @@ God.task do |w| w.interval = 5.seconds w.valid_states = [:init, :up, :down] w.initial_state = :init - + # determine the state on startup w.transition(:init, { true => :up, false => :down }) do |on| on.condition(:process_running) do |c| @@ -13,7 +13,7 @@ God.task do |w| c.pid_file = $pid_file end end - + # when process is up w.transition(:up, :down) do |on| # transition to 'start' if process goes down @@ -21,7 +21,7 @@ God.task do |w| c.running = false c.pid_file = $pid_file end - + # send up info on.condition(:lambda) do |c| c.lambda = lambda do @@ -30,7 +30,7 @@ God.task do |w| end end end - + # when process is down w.transition(:down, :up) do |on| # transition to 'up' if process comes up @@ -38,7 +38,7 @@ God.task do |w| c.running = true c.pid_file = $pid_file end - + # send down info on.condition(:lambda) do |c| c.lambda = lambda do diff --git a/test/configs/real.rb b/test/configs/real.rb index 1beaad70..457d5758 100644 --- a/test/configs/real.rb +++ b/test/configs/real.rb @@ -10,14 +10,14 @@ w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -d" w.stop = "mongrel_rails stop -P ./log/mongrel.pid -c #{RAILS_ROOT}" w.grace = 5 - + pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) do |b| b.pid_file = pid_file end - + # start if process is not running w.start_if do |start| start.condition(:process_running) do |c| @@ -25,7 +25,7 @@ c.pid_file = pid_file end end - + # restart if memory or cpu is too high w.restart_if do |restart| restart.condition(:memory_usage) do |c| @@ -34,7 +34,7 @@ c.above = (50 * 1024) # 50mb c.times = [3, 5] end - + restart.condition(:cpu_usage) do |c| c.interval = 10 c.pid_file = pid_file @@ -52,7 +52,7 @@ # File.mtime(f) < Time.now - (7 * 24 * 60 * 60) # end.each { |f| File.delete(f) } # end -# +# # w.start_if do |start| # start.condition(:always) # end diff --git a/test/configs/running_load/running_load.god b/test/configs/running_load/running_load.god index 6bc70fb9..e62b9bfd 100644 --- a/test/configs/running_load/running_load.god +++ b/test/configs/running_load/running_load.god @@ -7,7 +7,7 @@ God.watch do |w| w.uid = 'tom' w.gid = 'tom' w.group = 'test' - + w.start_if do |start| start.condition(:process_running) do |c| c.running = false diff --git a/test/configs/stress/stress.god b/test/configs/stress/stress.god index f140053f..e55564b9 100644 --- a/test/configs/stress/stress.god +++ b/test/configs/stress/stress.god @@ -5,7 +5,7 @@ w.interval = 0 w.grace = 2 w.group = 'test' - + w.start_if do |start| start.condition(:process_running) do |c| c.running = false diff --git a/test/configs/task/task.god b/test/configs/task/task.god index eb81eaa3..fbf6cc19 100644 --- a/test/configs/task/task.god +++ b/test/configs/task/task.god @@ -5,17 +5,17 @@ God.task do |t| t.valid_states = [:ok, :clean] t.initial_state = :ok t.interval = 5 - + # t.clean = lambda do # Dir[File.join(LOG_DIR, '*.log')].each do |f| # File.delete(f) # end # end - + t.clean = "rm #{File.join(LOG_DIR, '*.log')}" - + t.transition(:clean, :ok) - + t.transition(:ok, :clean) do |on| on.condition(:lambda) do |c| c.lambda = lambda do diff --git a/test/configs/test.rb b/test/configs/test.rb index 00cdcb27..1b786181 100644 --- a/test/configs/test.rb +++ b/test/configs/test.rb @@ -12,17 +12,17 @@ w.stop = "mongrel_rails stop -P ./log/mongrel.pid -c #{RAILS_ROOT}" w.group = 'mongrels' w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| @@ -34,7 +34,7 @@ w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -42,13 +42,13 @@ c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 1 c.above = 10.percent c.times = [3, 5] end - + on.condition(:http_response_code) do |c| c.host = 'localhost' c.port = port diff --git a/test/helper.rb b/test/helper.rb index 078669f0..9a8cd676 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -37,13 +37,13 @@ def test true end end - + class FakePollCondition < PollCondition def test true end end - + class FakeEventCondition < EventCondition def register end @@ -51,7 +51,7 @@ def deregister end end end - + module Behaviors class FakeBehavior < Behavior def before_start @@ -62,15 +62,15 @@ def after_start end end end - + module Contacts class FakeContact < Contact end - + class InvalidContact end end - + def self.reset self.watches = nil self.groups = nil @@ -139,7 +139,7 @@ class Bypass def initialize(ref) @ref = ref end - + def method_missing(sym, *args) @ref.__send__(sym, *args) end diff --git a/test/test_behavior.rb b/test/test_behavior.rb index 50de69c6..3210a46f 100644 --- a/test/test_behavior.rb +++ b/test/test_behavior.rb @@ -4,13 +4,13 @@ class TestBehavior < Test::Unit::TestCase def test_generate_should_return_an_object_corresponding_to_the_given_type assert_equal Behaviors::FakeBehavior, Behavior.generate(:fake_behavior, nil).class end - + def test_generate_should_raise_on_invalid_type assert_raise NoSuchBehaviorError do Behavior.generate(:foo, nil) end end - + def test_complain Syslog.expects(:err).with('foo') # Kernel.expects(:puts).with('foo') diff --git a/test/test_condition.rb b/test/test_condition.rb index 712a7c33..e0ff9c6a 100644 --- a/test/test_condition.rb +++ b/test/test_condition.rb @@ -4,19 +4,19 @@ class BadlyImplementedCondition < PollCondition end class TestCondition < Test::Unit::TestCase - + # generate - + def test_generate_should_return_an_object_corresponding_to_the_given_type assert_equal Conditions::ProcessRunning, Condition.generate(:process_running, nil).class end - + def test_generate_should_raise_on_invalid_type assert_raise NoSuchConditionError do Condition.generate(:foo, nil) end end - + def test_generate_should_abort_on_event_condition_without_loaded_event_system God::EventHandler.stubs(:operational?).returns(false) assert_abort do @@ -24,25 +24,25 @@ def test_generate_should_abort_on_event_condition_without_loaded_event_system Condition.generate(:process_exits, nil).class end end - + def test_generate_should_return_a_good_error_message_for_invalid_types emsg = "No Condition found with the class name God::Conditions::FooBar" rmsg = nil - + begin Condition.generate(:foo_bar, nil) rescue => e rmsg = e.message end - + assert_equal emsg, rmsg end - + # test - + def test_test_should_raise_if_not_defined_in_subclass c = BadlyImplementedCondition.new - + assert_raise AbstractMethodNotOverriddenError do c.test end diff --git a/test/test_conditions_disk_usage.rb b/test/test_conditions_disk_usage.rb index 0e43e28e..cece0e65 100644 --- a/test/test_conditions_disk_usage.rb +++ b/test/test_conditions_disk_usage.rb @@ -2,55 +2,55 @@ class TestConditionsDiskUsage < Test::Unit::TestCase # valid? - + def test_valid_should_return_false_if_no_above_given c = Conditions::DiskUsage.new c.mount_point = '/' c.watch = stub(:name => 'foo') - + no_stdout do assert_equal false, c.valid? end end - + def test_valid_should_return_false_if_no_mount_point_given c = Conditions::DiskUsage.new c.above = 90 c.watch = stub(:name => 'foo') - + no_stdout do assert_equal false, c.valid? end end - + def test_valid_should_return_true_if_required_options_all_set c = Conditions::DiskUsage.new c.above = 90 c.mount_point = '/' c.watch = stub(:name => 'foo') - + assert_equal true, c.valid? end - + # test - + def test_test_should_return_true_if_above_limit c = Conditions::DiskUsage.new c.above = 90 c.mount_point = '/' - + c.expects(:`).returns('91') - + assert_equal true, c.test end - + def test_test_should_return_false_if_below_limit c = Conditions::DiskUsage.new c.above = 90 c.mount_point = '/' - + c.expects(:`).returns('90') - + assert_equal false, c.test end end \ No newline at end of file diff --git a/test/test_conditions_http_response_code.rb b/test/test_conditions_http_response_code.rb index 47eecbad..81a463ce 100644 --- a/test/test_conditions_http_response_code.rb +++ b/test/test_conditions_http_response_code.rb @@ -14,36 +14,36 @@ def valid_condition c.prepare c end - + # valid? - + def test_valid_condition_is_valid c = valid_condition assert c.valid? end - + def test_valid_should_return_false_if_both_code_is_and_code_is_not_are_set c = valid_condition do |cc| cc.code_is_not = 500 end no_stdout { assert !c.valid? } end - + def test_valid_should_return_false_if_no_host_set c = valid_condition do |cc| cc.host = nil end no_stdout { assert !c.valid? } end - + # test - + def test_test_should_return_false_if_code_is_is_set_to_200_but_response_is_500 c = valid_condition Net::HTTP.expects(:start).yields(stub(:read_timeout= => nil, :get => stub(:code => 500))) assert_equal false, c.test end - + def test_test_should_return_false_if_code_is_not_is_set_to_200_and_response_is_200 c = valid_condition do |cc| cc.code_is = nil @@ -52,13 +52,13 @@ def test_test_should_return_false_if_code_is_not_is_set_to_200_and_response_is_2 Net::HTTP.expects(:start).yields(stub(:read_timeout= => nil, :get => stub(:code => 200))) assert_equal false, c.test end - + def test_test_should_return_true_if_code_is_is_set_to_200_and_response_is_200 c = valid_condition Net::HTTP.expects(:start).yields(stub(:read_timeout= => nil, :get => stub(:code => 200))) assert_equal true, c.test end - + def test_test_should_return_false_if_code_is_not_is_set_to_200_but_response_is_500 c = valid_condition do |cc| cc.code_is = nil @@ -67,13 +67,13 @@ def test_test_should_return_false_if_code_is_not_is_set_to_200_but_response_is_5 Net::HTTP.expects(:start).yields(stub(:read_timeout= => nil, :get => stub(:code => 500))) assert_equal true, c.test end - + def test_test_should_return_false_if_code_is_is_set_to_200_but_response_times_out c = valid_condition Net::HTTP.expects(:start).raises(Timeout::Error, '') assert_equal false, c.test end - + def test_test_should_return_true_if_code_is_not_is_set_to_200_and_response_times_out c = valid_condition do |cc| cc.code_is = nil @@ -82,13 +82,13 @@ def test_test_should_return_true_if_code_is_not_is_set_to_200_and_response_times Net::HTTP.expects(:start).raises(Timeout::Error, '') assert_equal true, c.test end - + def test_test_should_return_false_if_code_is_is_set_to_200_but_cant_connect c = valid_condition Net::HTTP.expects(:start).raises(Errno::ECONNREFUSED, '') assert_equal false, c.test end - + def test_test_should_return_true_if_code_is_not_is_set_to_200_and_cant_connect c = valid_condition do |cc| cc.code_is = nil @@ -97,7 +97,7 @@ def test_test_should_return_true_if_code_is_not_is_set_to_200_and_cant_connect Net::HTTP.expects(:start).raises(Errno::ECONNREFUSED, '') assert_equal true, c.test end - + def test_test_should_return_true_if_code_is_is_set_to_200_and_response_is_200_twice_for_times_two_of_two c = valid_condition do |cc| cc.times = [2, 2] diff --git a/test/test_conditions_process_running.rb b/test/test_conditions_process_running.rb index c0b709d7..c1d5d709 100644 --- a/test/test_conditions_process_running.rb +++ b/test/test_conditions_process_running.rb @@ -5,39 +5,39 @@ def test_missing_pid_file_returns_opposite [true, false].each do |r| c = Conditions::ProcessRunning.new c.running = r - + c.stubs(:watch).returns(stub(:pid => 123, :name => 'foo')) - + # no_stdout do assert_equal !r, c.test # end end end - + def test_not_running_returns_opposite [true, false].each do |r| c = Conditions::ProcessRunning.new c.running = r - + File.stubs(:exist?).returns(true) c.stubs(:watch).returns(stub(:pid => 123)) File.stubs(:read).returns('5') System::Process.any_instance.stubs(:exists?).returns(false) - + assert_equal !r, c.test end end - + def test_running_returns_same [true, false].each do |r| c = Conditions::ProcessRunning.new c.running = r - + File.stubs(:exist?).returns(true) c.stubs(:watch).returns(stub(:pid => 123)) File.stubs(:read).returns('5') System::Process.any_instance.stubs(:exists?).returns(true) - + assert_equal r, c.test end end diff --git a/test/test_conditions_tries.rb b/test/test_conditions_tries.rb index 0b1fc2c4..15d4287b 100644 --- a/test/test_conditions_tries.rb +++ b/test/test_conditions_tries.rb @@ -2,7 +2,7 @@ class TestConditionsTries < Test::Unit::TestCase # valid? - + def test_valid_should_return_false_if_times_not_set c = Conditions::Tries.new c.watch = stub(:name => 'foo') @@ -17,23 +17,23 @@ def setup @c.times = 3 @c.prepare end - + # prepare - + def test_prepare_should_create_timeline assert 3, @c.instance_variable_get(:@timeline).instance_variable_get(:@max_size) end - + # test - + def test_test_should_return_true_if_called_three_times_within_one_second assert !@c.test assert !@c.test assert @c.test end - + # reset - + def test_test_should_return_false_on_fourth_call_if_called_three_times_within_one_second 3.times { @c.test } @c.reset @@ -49,15 +49,15 @@ def setup @c.within = 1.seconds @c.prepare end - + # test - + def test_test_should_return_true_if_called_three_times_within_one_second assert !@c.test assert !@c.test assert @c.test end - + def test_test_should_return_false_if_called_three_times_within_two_seconds assert !@c.test assert !@c.test diff --git a/test/test_contact.rb b/test/test_contact.rb index 1d4427f2..3a28736a 100644 --- a/test/test_contact.rb +++ b/test/test_contact.rb @@ -4,103 +4,103 @@ class TestContact < Test::Unit::TestCase def test_exists God::Contact end - + # generate - + def test_generate_should_raise_on_invalid_kind assert_raise(NoSuchContactError) do Contact.generate(:invalid) end end - + def test_generate_should_abort_on_invalid_contact assert_abort do Contact.generate(:invalid_contact) end end - + # normalize - + def test_normalize_should_accept_a_string input = 'tom' output = {:contacts => ['tom']} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_accept_an_array_of_strings input = ['tom', 'kevin'] output = {:contacts => ['tom', 'kevin']} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_accept_a_hash_with_contacts_string input = {:contacts => 'tom'} output = {:contacts => ['tom']} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_accept_a_hash_with_contacts_array_of_strings input = {:contacts => ['tom', 'kevin']} output = {:contacts => ['tom', 'kevin']} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_stringify_priority input = {:contacts => 'tom', :priority => 1} output = {:contacts => ['tom'], :priority => '1'} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_stringify_category input = {:contacts => 'tom', :category => :product} output = {:contacts => ['tom'], :category => 'product'} assert_equal(output, Contact.normalize(input)) end - + def test_normalize_should_raise_on_non_string_array_hash input = 1 assert_raise ArgumentError do Contact.normalize(input) end end - + def test_normalize_should_raise_on_non_string_array_contacts_key input = {:contacts => 1} assert_raise ArgumentError do Contact.normalize(input) end end - + def test_normalize_should_raise_on_non_string_containing_array input = [1] assert_raise ArgumentError do Contact.normalize(input) end end - + def test_normalize_should_raise_on_non_string_containing_array_contacts_key input = {:contacts => [1]} assert_raise ArgumentError do Contact.normalize(input) end end - + def test_normalize_should_raise_on_absent_contacts_key input = {} assert_raise ArgumentError do Contact.normalize(input) end end - + def test_normalize_should_raise_on_extra_keys input = {:contacts => ['tom'], :priority => 1, :category => 'product', :extra => 'foo'} assert_raise ArgumentError do Contact.normalize(input) end end - + # notify - + def test_notify_should_be_abstract assert_raise(AbstractMethodNotOverriddenError) do Contact.new.notify(:a, :b, :c, :d, :e) diff --git a/test/test_dependency_graph.rb b/test/test_dependency_graph.rb index 0f5cab98..8c688f52 100644 --- a/test/test_dependency_graph.rb +++ b/test/test_dependency_graph.rb @@ -4,32 +4,32 @@ class TestDependencyGraph < Test::Unit::TestCase def setup @dg = DependencyGraph.new end - + # new - + def test_new_should_accept_zero_arguments assert @dg.instance_of?(DependencyGraph) end - + # add - + def test_add_should_create_and_store_two_new_nodes @dg.add('foo', 'bar') assert_equal 2, @dg.nodes.size assert @dg.nodes['foo'].instance_of?(DependencyGraph::Node) assert @dg.nodes['bar'].instance_of?(DependencyGraph::Node) end - + def test_add_should_record_dependency @dg.add('foo', 'bar') assert_equal 1, @dg.nodes['foo'].dependencies.size assert_equal @dg.nodes['bar'], @dg.nodes['foo'].dependencies.first end - + def test_add_should_ignore_dups @dg.add('foo', 'bar') @dg.add('foo', 'bar') - assert_equal 2, @dg.nodes.size + assert_equal 2, @dg.nodes.size assert_equal 1, @dg.nodes['foo'].dependencies.size end end @@ -40,22 +40,22 @@ def setup @foo = DependencyGraph::Node.new('foo') @bar = DependencyGraph::Node.new('bar') end - + # new - + def test_new_should_accept_zero_arguments assert @foo.instance_of?(DependencyGraph::Node) end - + # add - + def test_add_should_store_node_as_dependency @foo.add(@bar) assert_equal 1, @foo.dependencies.size end - + # has_node? - + def test_has_node assert @foo.has_node?(@foo) end diff --git a/test/test_driver.rb b/test/test_driver.rb index 44584b46..f0fbd940 100644 --- a/test/test_driver.rb +++ b/test/test_driver.rb @@ -2,10 +2,10 @@ class TestDriver < Test::Unit::TestCase def setup - + end def test_ - + end end diff --git a/test/test_email.rb b/test/test_email.rb index 7378fb41..87badc37 100644 --- a/test/test_email.rb +++ b/test/test_email.rb @@ -11,7 +11,7 @@ def test_unknown_delivery_method_for_notify God::Contacts::Email.any_instance.expects(:notify_sendmail).never God::Contacts::Email.delivery_method = :foo_protocol LOG.expects(:log).times(2) - + g = God::Contacts::Email.new g.notify(:a, :b, :c, :d, :e) assert_nil g.info @@ -29,7 +29,7 @@ def test_smtp_delivery_method_for_notify assert_equal "sent email to joe@example.com", g.info end end - + def test_sendmail_delivery_method_for_notify assert_nothing_raised do God::Contacts::Email.any_instance.expects(:notify_smtp).never @@ -41,5 +41,5 @@ def test_sendmail_delivery_method_for_notify assert_equal "sent email to joe@example.com", g.info end end - + end diff --git a/test/test_event_handler.rb b/test/test_event_handler.rb index 50adecc2..c86d3fa9 100644 --- a/test/test_event_handler.rb +++ b/test/test_event_handler.rb @@ -2,15 +2,15 @@ module God class EventHandler - + def self.actions=(value) @@actions = value end - + def self.actions @@actions end - + def self.handler=(value) @@handler = value end @@ -21,40 +21,40 @@ class TestEventHandler < Test::Unit::TestCase def setup @h = God::EventHandler end - + def test_register_one_event pid = 4445 event = :proc_exit block = lambda { puts "Hi" } - + mock_handler = mock() mock_handler.expects(:register_process).with(pid, [event]) @h.handler = mock_handler - + @h.register(pid, event, &block) assert_equal @h.actions, {pid => {event => block}} end - + def test_register_multiple_events_per_process pid = 4445 exit_block = lambda { puts "Hi" } @h.actions = {pid => {:proc_exit => exit_block}} - + mock_handler = mock() mock_handler.expects(:register_process).with do |a, b| a == pid && b.to_set == [:proc_exit, :proc_fork].to_set end @h.handler = mock_handler - + fork_block = lambda { puts "Forking" } @h.register(pid, :proc_fork, &fork_block) assert_equal @h.actions, {pid => {:proc_exit => exit_block, :proc_fork => fork_block }} end - + # JIRA PLATFORM-75 def test_call_should_check_for_pid_and_action_before_executing exit_block = mock() @@ -64,7 +64,7 @@ def test_call_should_check_for_pid_and_action_before_executing @h.call(4445, :proc_fork) # shouldn't call, bad event @h.call(4445, :proc_exit) # should call end - + def teardown # Reset handler @h.actions = {} diff --git a/test/test_god.rb b/test/test_god.rb index 1fe669c8..44d3bac4 100644 --- a/test/test_god.rb +++ b/test/test_god.rb @@ -9,7 +9,7 @@ def setup God.reset God.pid_file_directory = '/var/run/god' end - + def teardown God.main && God.main.kill if God.watches @@ -18,46 +18,46 @@ def teardown end end end - + # applog - + def test_applog LOG.expects(:log).with(nil, :debug, 'foo') applog(nil, :debug, 'foo') end - + # internal_init - + def test_init_should_initialize_watches_to_empty_array God.internal_init { } assert_equal Hash.new, God.watches end - + # init - + def test_pid_file_directory_should_abort_if_called_after_watch God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + assert_abort do God.pid_file_directory = 'foo' end end - + # pid_file_directory - + def test_pid_file_directory_should_return_default_if_not_set_explicitly God.internal_init assert_equal '/var/run/god', God.pid_file_directory end - + def test_pid_file_directory_equals_should_set God.pid_file_directory = '/foo' God.internal_init assert_equal '/foo', God.pid_file_directory end - + # watch - + def test_watch_should_get_stored watch = nil God.watch do |w| @@ -65,13 +65,13 @@ def test_watch_should_get_stored w.start = 'bar' watch = w end - + assert_equal 1, God.watches.size assert_equal watch, God.watches.values.first - + assert_equal 0, God.groups.size end - + def test_watch_should_get_stored_in_pending_watches watch = nil God.watch do |w| @@ -79,11 +79,11 @@ def test_watch_should_get_stored_in_pending_watches w.start = 'bar' watch = w end - + assert_equal 1, God.pending_watches.size assert_equal watch, God.pending_watches.first end - + def test_watch_should_register_processes assert_nil God.registry['foo'] God.watch do |w| @@ -92,86 +92,86 @@ def test_watch_should_register_processes end assert_kind_of God::Process, God.registry['foo'] end - + def test_watch_should_get_stored_by_group a = nil - + God.watch do |w| a = w w.name = 'foo' w.start = 'bar' w.group = 'test' end - + assert_equal({'test' => [a]}, God.groups) end - + def test_watches_should_get_stored_by_group a = nil b = nil - + God.watch do |w| a = w w.name = 'foo' w.start = 'bar' w.group = 'test' end - + God.watch do |w| b = w w.name = 'bar' w.start = 'baz' w.group = 'test' end - + assert_equal({'test' => [a, b]}, God.groups) end - + def test_watch_should_allow_multiple_watches God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + assert_nothing_raised do God.watch { |w| w.name = 'bar'; w.start = 'bar' } end end - + def test_watch_should_disallow_duplicate_watch_names God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + assert_abort do God.watch { |w| w.name = 'foo'; w.start = 'bar' } end end - + def test_watch_should_disallow_identical_watch_and_group_names God.watch { |w| w.name = 'foo'; w.group = 'bar'; w.start = 'bar' } - + assert_abort do God.watch { |w| w.name = 'bar'; w.start = 'bar' } end end - + def test_watch_should_disallow_identical_watch_and_group_names_other_way God.watch { |w| w.name = 'bar'; w.start = 'bar' } - + assert_abort do God.watch { |w| w.name = 'foo'; w.group = 'bar'; w.start = 'bar' } end end - + def test_watch_should_unwatch_new_watch_if_running_and_duplicate_watch God.watch { |w| w.name = 'foo'; w.start = 'bar' } God.running = true - + assert_nothing_raised do no_stdout do God.watch { |w| w.name = 'foo'; w.start = 'bar' } end end end - + # unwatch - + def test_unwatch_should_unmonitor_watch God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] @@ -181,7 +181,7 @@ def test_unwatch_should_unmonitor_watch God.unwatch(w) end end - + def test_unwatch_should_unregister_watch God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] @@ -190,7 +190,7 @@ def test_unwatch_should_unregister_watch God.unwatch(w) end end - + def test_unwatch_should_remove_same_name_watches God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] @@ -199,7 +199,7 @@ def test_unwatch_should_remove_same_name_watches end assert_equal 0, God.watches.size end - + def test_unwatch_should_remove_from_group God.watch do |w| w.name = 'bar' @@ -212,32 +212,32 @@ def test_unwatch_should_remove_from_group end assert !God.groups[w.group].include?(w) end - + # contact - + def test_contact_should_ensure_init_is_called God.contact(:fake_contact) { |c| c.name = 'tom' } assert God.inited end - + def test_contact_should_abort_on_invalid_contact_kind assert_abort do God.contact(:foo) { |c| c.name = 'tom' } end end - + def test_contact_should_create_and_store_contact contact = nil God.contact(:fake_contact) { |c| c.name = 'tom'; contact = c } assert [contact], God.contacts end - + def test_contact_should_add_to_group God.contact(:fake_contact) { |c| c.name = 'tom'; c.group = 'devs' } God.contact(:fake_contact) { |c| c.name = 'chris'; c.group = 'devs' } assert 2, God.contact_groups.size end - + def test_contact_should_abort_on_no_name no_stdout do assert_abort do @@ -245,7 +245,7 @@ def test_contact_should_abort_on_no_name end end end - + def test_contact_should_abort_on_duplicate_contact_name God.contact(:fake_contact) { |c| c.name = 'tom' } no_stdout do @@ -254,7 +254,7 @@ def test_contact_should_abort_on_duplicate_contact_name end end end - + def test_contact_should_abort_on_contact_with_same_name_as_group God.contact(:fake_contact) { |c| c.name = 'tom'; c.group = 'devs' } no_stdout do @@ -263,14 +263,14 @@ def test_contact_should_abort_on_contact_with_same_name_as_group end end end - + def test_contact_should_abort_on_contact_with_same_group_as_name God.contact(:fake_contact) { |c| c.name = 'tom' } assert_abort do God.contact(:fake_contact) { |c| c.name = 'chris'; c.group = 'tom' } end end - + def test_contact_should_abort_if_contact_is_invalid assert_abort do God.contact(:fake_contact) do |c| @@ -279,111 +279,111 @@ def test_contact_should_abort_if_contact_is_invalid end end end - + # control - + def test_control_should_monitor_on_start God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.expects(:monitor) God.control('foo', 'start') end - + def test_control_should_move_to_restart_on_restart God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.expects(:move).with(:restart) God.control('foo', 'restart') end - + def test_control_should_unmonitor_and_stop_on_stop God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.state = :up w.expects(:unmonitor).returns(w) w.expects(:action).with(:stop) God.control('foo', 'stop') end - + def test_control_should_unmonitor_on_unmonitor God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.state = :up w.expects(:unmonitor).returns(w) God.control('foo', 'unmonitor') end - + def test_control_should_unwatch_on_remove God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.state = :up God.expects(:unwatch) God.control('foo', 'remove') end - + def test_control_should_raise_on_invalid_command God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + assert_raise InvalidCommandError do God.control('foo', 'invalid') end end - + def test_control_should_operate_on_each_watch_in_group God.watch do |w| w.name = 'foo1' w.start = 'go' w.group = 'bar' end - + God.watch do |w| w.name = 'foo2' w.start = 'go' w.group = 'bar' end - + God.watches['foo1'].expects(:monitor) God.watches['foo2'].expects(:monitor) - + God.control('bar', 'start') end - + # stop_all - + # terminate - + def test_terminate_should_exit God.pid = nil FileUtils.expects(:rm_f).never God.expects(:exit!) God.terminate end - + def test_terminate_should_delete_pid God.pid = '/foo/bar' FileUtils.expects(:rm_f).with("/foo/bar") God.expects(:exit!) God.terminate end - + # status - + def test_status_should_show_state God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] w.state = :up assert_equal({'foo' => {:state => :up, :group => nil}}, God.status) end - + def test_status_should_show_state_with_group God.watch { |w| w.name = 'foo'; w.start = 'bar'; w.group = 'g' } - + w = God.watches['foo'] w.state = :up assert_equal({'foo' => {:state => :up, :group => 'g'}}, God.status) @@ -391,29 +391,29 @@ def test_status_should_show_state_with_group def test_status_should_show_unmonitored_for_nil_state God.watch { |w| w.name = 'foo'; w.start = 'bar' } - + w = God.watches['foo'] assert_equal({'foo' => {:state => :unmonitored, :group => nil}}, God.status) end - + # running_log - + def test_running_log_should_call_watch_log_since_on_main_log God.watch { |w| w.name = 'foo'; w.start = 'bar' } t = Time.now LOG.expects(:watch_log_since).with('foo', t) God.running_log('foo', t) end - + def test_running_log_should_raise_on_unknown_watch God.internal_init assert_raise(NoSuchWatchError) do God.running_log('foo', Time.now) end end - + # running_load - + def test_running_load_should_eval_code code = <<-EOF God.watch do |w| @@ -421,14 +421,14 @@ def test_running_load_should_eval_code w.start = 'go' end EOF - + no_stdout do God.running_load(code, '/foo/bar.god') end - + assert_equal 1, God.watches.size end - + def test_running_load_should_monitor_new_watches code = <<-EOF God.watch do |w| @@ -436,13 +436,13 @@ def test_running_load_should_monitor_new_watches w.start = 'go' end EOF - + Watch.any_instance.expects(:monitor) no_stdout do God.running_load(code, '/foo/bar.god') end end - + def test_running_load_should_not_monitor_new_watches_with_autostart_false code = <<-EOF God.watch do |w| @@ -451,13 +451,13 @@ def test_running_load_should_not_monitor_new_watches_with_autostart_false w.autostart = false end EOF - + Watch.any_instance.expects(:monitor).never no_stdout do God.running_load(code, '/foo/bar.god') end end - + def test_running_load_should_return_array_of_affected_watches code = <<-EOF God.watch do |w| @@ -465,7 +465,7 @@ def test_running_load_should_return_array_of_affected_watches w.start = 'go' end EOF - + w = nil no_stdout do w, e = *God.running_load(code, '/foo/bar.god') @@ -473,7 +473,7 @@ def test_running_load_should_return_array_of_affected_watches assert_equal 1, w.size assert_equal 'foo', w.first end - + def test_running_load_should_clear_pending_watches code = <<-EOF God.watch do |w| @@ -481,69 +481,69 @@ def test_running_load_should_clear_pending_watches w.start = 'go' end EOF - + no_stdout do God.running_load(code, '/foo/bar.god') end assert_equal 0, God.pending_watches.size end - + # load - + def test_load_should_collect_and_load_globbed_path Dir.expects(:[]).with('/path/to/*.thing').returns(['a', 'b']) Kernel.expects(:load).with('a').once Kernel.expects(:load).with('b').once God.load('/path/to/*.thing') end - + # start - + def test_start_should_kick_off_a_server_instance God::Socket.expects(:new).returns(true) God.start end - + def test_start_should_begin_monitoring_autostart_watches God.watch do |w| w.name = 'foo' w.start = 'go' end - + Watch.any_instance.expects(:monitor).once God.start end - + def test_start_should_not_begin_monitoring_non_autostart_watches God.watch do |w| w.name = 'foo' w.start = 'go' w.autostart = false end - + Watch.any_instance.expects(:monitor).never God.start end - + def test_start_should_get_and_join_timer God.watch { |w| w.name = 'foo'; w.start = 'bar' } no_stdout do God.start end end - + # at_exit - + def test_at_exit_should_call_start God.expects(:start).once God.at_exit end - + # pattern_match - + def test_pattern_match list = %w{ mongrel-3000 mongrel-3001 fuzed fuzed2 apache mysql} - + assert_equal %w{ mongrel-3000 }, God.pattern_match('m3000', list) assert_equal %w{ mongrel-3001 }, God.pattern_match('m31', list) assert_equal %w{ fuzed fuzed2 }, God.pattern_match('fu', list) @@ -558,37 +558,37 @@ def test_pattern_match # God.internal_init # God.reset # end -# +# # def teardown # God.main && God.main.kill # end -# +# # # setup -# +# # def test_setup_should_create_pid_file_directory_if_it_doesnt_exist # God.expects(:test).returns(false) # FileUtils.expects(:mkdir_p).with(God.pid_file_directory) # God.setup # end -# +# # def test_setup_should_raise_if_no_permissions_to_create_pid_file_directory # God.expects(:test).returns(false) # FileUtils.expects(:mkdir_p).raises(Errno::EACCES) -# +# # assert_abort do # God.setup # end # end -# +# # # validate -# +# # def test_validate_should_abort_if_pid_file_directory_is_unwriteable # God.expects(:test).returns(false) # assert_abort do # God.validater # end # end -# +# # def test_validate_should_not_abort_if_pid_file_directory_is_writeable # God.expects(:test).returns(true) # assert_nothing_raised do diff --git a/test/test_handlers_kqueue_handler.rb b/test/test_handlers_kqueue_handler.rb index 24dcb82b..68a17611 100644 --- a/test/test_handlers_kqueue_handler.rb +++ b/test/test_handlers_kqueue_handler.rb @@ -1,16 +1,16 @@ require File.dirname(__FILE__) + '/helper' if God::EventHandler.event_system == "kqueue" - + class TestHandlersKqueueHandler < Test::Unit::TestCase def test_register_process KQueueHandler.expects(:monitor_process).with(1234, 2147483648) KQueueHandler.register_process(1234, [:proc_exit]) end - + def test_events_mask assert_equal 2147483648, KQueueHandler.events_mask([:proc_exit]) end end - + end \ No newline at end of file diff --git a/test/test_logger.rb b/test/test_logger.rb index 8f9e4e53..d373c560 100644 --- a/test/test_logger.rb +++ b/test/test_logger.rb @@ -4,56 +4,56 @@ class TestLogger < Test::Unit::TestCase def setup @log = God::Logger.new end - + # log - + def test_log @log.expects(:info).with("qux") - + no_stdout do @log.log(stub(:name => 'foo'), :info, "qux") end - + assert_equal 1, @log.logs.size assert_instance_of Time, @log.logs['foo'][0][0] assert_match(/qux/, @log.logs['foo'][0][1]) end - + def test_log_should_send_to_syslog Syslog.expects(:crit).with('foo') - + no_stdout do @log.log(stub(:name => 'foo'), :fatal, "foo") end end - + # watch_log_since - + def test_watch_log_since t1 = Time.now - + no_stdout do @log.log(stub(:name => 'foo'), :info, "one") @log.log(stub(:name => 'foo'), :info, "two") end - + assert_match(/one.*two/m, @log.watch_log_since('foo', t1)) - + t2 = Time.now - + no_stdout do @log.log(stub(:name => 'foo'), :info, "three") end - + out = @log.watch_log_since('foo', t2) - + assert_no_match(/one/, out) assert_no_match(/two/, out) assert_match(/three/, out) end - + # regular methods - + def test_fatal no_stdout do @log.fatal('foo') diff --git a/test/test_metric.rb b/test/test_metric.rb index 83ec90f5..1fcd9036 100644 --- a/test/test_metric.rb +++ b/test/test_metric.rb @@ -4,66 +4,66 @@ class TestMetric < Test::Unit::TestCase def setup @metric = Metric.new(stub(:interval => 10), nil) end - + # watch - + def test_watch w = stub() m = Metric.new(w, nil) assert_equal w, m.watch end - + # destination - + def test_destination d = stub() m = Metric.new(nil, d) assert_equal d, m.destination end - + # condition - + def test_condition_should_be_block_optional @metric.condition(:fake_poll_condition) assert_equal 1, @metric.conditions.size end - + def test_poll_condition_should_inherit_interval_from_watch_if_not_specified @metric.condition(:fake_poll_condition) assert_equal 10, @metric.conditions.first.interval end - + def test_poll_condition_should_abort_if_no_interval_and_no_watch_interval metric = Metric.new(stub(:name => 'foo', :interval => nil), nil) - + assert_abort do metric.condition(:fake_poll_condition) end end - + def test_condition_should_allow_generation_of_subclasses_of_poll_or_event metric = Metric.new(stub(:name => 'foo', :interval => 10), nil) - + assert_nothing_raised do metric.condition(:fake_poll_condition) metric.condition(:fake_event_condition) end end - + def test_condition_should_abort_if_not_subclass_of_poll_or_event metric = Metric.new(stub(:name => 'foo', :interval => 10), nil) - + assert_abort do metric.condition(:fake_condition) { |c| } end end - + def test_condition_should_abort_on_invalid_condition assert_abort do @metric.condition(:fake_poll_condition) { |c| c.stubs(:valid?).returns(false) } end end - + def test_condition_should_abort_on_no_such_condition assert_abort do @metric.condition(:invalid) { } diff --git a/test/test_process.rb b/test/test_process.rb index 8f0aafd8..4138bee2 100644 --- a/test/test_process.rb +++ b/test/test_process.rb @@ -5,7 +5,7 @@ class Process # def fork # raise "You forgot to stub fork" # end - + def exec(*args) raise "You forgot to stub exec" end @@ -19,60 +19,60 @@ def setup @p.name = 'foo' @p.stubs(:test).returns true # so we don't try to mkdir_p Process.stubs(:detach) # because we stub fork - + ::Process::Sys.stubs(:setuid).returns(true) ::Process::Sys.stubs(:setgid).returns(true) end - + # valid? - + def test_valid_should_return_true_if_auto_daemonized_and_log @p.start = 'qux' @p.log = 'bar' - + assert @p.valid? end - + def test_valid_should_return_true_if_auto_daemonized_and_no_stop @p.start = 'qux' @p.log = 'bar' - + assert @p.valid? end - + def test_valid_should_return_true_if_uid_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.uid = 'root' - + assert @p.valid? end - + def test_valid_should_return_true_if_uid_does_not_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.uid = 'foobarbaz' - + no_stdout do no_stderr do assert !@p.valid? end end end - + def test_valid_should_return_true_if_gid_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.gid = 'wheel' - + assert @p.valid? end - + def test_valid_should_return_false_if_gid_does_not_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.gid = 'foobarbaz' - + no_stdout do no_stderr do assert !@p.valid? @@ -101,9 +101,9 @@ def test_valid_should_return_true_with_chroot_and_valid_log assert @p.valid? end - + # call_action - + def test_call_action_should_write_pid # Only for start, restart [:start, :restart].each do |action| @@ -133,22 +133,22 @@ def setup @p.stubs(:test).returns true # so we don't try to mkdir_p Process.stubs(:detach) # because we stub fork end - + # alive? - + def test_alive_should_call_system_process_exists File.expects(:read).with('blah.pid').times(2).returns('1234') System::Process.any_instance.expects(:exists?).returns(false) assert !@p.alive? end - + def test_alive_should_return_false_if_no_such_file File.expects(:read).with('blah.pid').raises(Errno::ENOENT) assert !@p.alive? end - + # valid? - + def test_valid_should_return_false_if_no_start @p.name = 'foo' @p.stop = 'baz' @@ -156,87 +156,87 @@ def test_valid_should_return_false_if_no_start assert !@p.valid? end end - + def test_valid_should_return_false_if_self_daemonized_and_no_stop @p.start = 'bar' @p.pid_file = 'foo' - + no_stdout do assert !@p.valid? end end - + # pid - + def test_pid_should_return_integer_for_valid_pid_files File.stubs(:read).returns("123") assert_equal 123, @p.pid end - + def test_pid_should_return_nil_for_missing_files @p.pid_file = '' assert_equal nil, @p.pid end - + def test_pid_should_return_nil_for_invalid_pid_files File.stubs(:read).returns("four score and seven years ago") assert_equal nil, @p.pid end - + def test_pid_should_retain_last_pid_value_if_pid_file_is_removed File.stubs(:read).returns("123") assert_equal 123, @p.pid - + File.stubs(:read).raises(Errno::ENOENT) assert_equal 123, @p.pid - + File.stubs(:read).returns("246") assert_equal 246, @p.pid end - + # default_pid_file - + def test_default_pid_file assert_equal File.join(God.pid_file_directory, 'foo.pid'), @p.default_pid_file end - + # unix socket - + def test_unix_socket_should_return_path_specified @p.unix_socket = '/path/to-socket' assert_equal '/path/to-socket', @p.unix_socket end - + # call_action # These actually excercise call_action in the back at this point - Kev - + def test_call_action_with_string_should_call_system @p.start = "do something" @p.expects(:fork) Process.expects(:waitpid2).returns([123, 0]) @p.call_action(:start) end - + def test_call_action_with_lambda_should_call cmd = lambda { puts "Hi" } cmd.expects(:call) @p.start = cmd @p.call_action(:start) end - + def test_call_action_with_invalid_command_class_should_raise @p.start = 5 @p.stop = 'baz' - + assert @p.valid? - + assert_raise NotImplementedError do @p.call_action(:start) end end - + # start!/stop!/restart! - + def test_start_stop_restart_bang [:start, :stop, :restart].each do |x| @p.expects(:call_action).with(x) diff --git a/test/test_registry.rb b/test/test_registry.rb index 1c15f466..02ac61bc 100644 --- a/test/test_registry.rb +++ b/test/test_registry.rb @@ -4,7 +4,7 @@ class TestRegistry < Test::Unit::TestCase def setup God.registry.reset end - + def test_add foo = God::Process.new foo.name = 'foo' diff --git a/test/test_socket.rb b/test/test_socket.rb index 8ae42198..bf0daabb 100644 --- a/test/test_socket.rb +++ b/test/test_socket.rb @@ -2,7 +2,7 @@ class TestSocket < Test::Unit::TestCase def setup - silence_warnings do + silence_warnings do Object.const_set(:DRb, stub_everything) end end @@ -29,9 +29,9 @@ def test_should_forward_foreign_method_calls_to_god God.expects(:send).with(:something_random) server.something_random end - + # ping - + def test_ping_should_return_true server = nil no_stdout do diff --git a/test/test_sugar.rb b/test/test_sugar.rb index 911a77f4..9c2875f6 100644 --- a/test/test_sugar.rb +++ b/test/test_sugar.rb @@ -5,37 +5,37 @@ def test_seconds assert_equal 1, 1.seconds assert_equal 1, 1.second end - + def test_minutes assert_equal 60, 1.minutes assert_equal 60, 1.minute end - + def test_hours assert_equal 3600, 1.hours assert_equal 3600, 1.hour end - + def test_days assert_equal 86400, 1.days assert_equal 86400, 1.day end - + def test_kilobytes assert_equal 1, 1.kilobytes assert_equal 1, 1.kilobyte end - + def test_megabytes assert_equal 1024, 1.megabytes assert_equal 1024, 1.megabyte end - + def test_gigabytes assert_equal 1024 ** 2, 1.gigabytes assert_equal 1024 ** 2, 1.gigabyte end - + def test_percent assert_equal 1, 1.percent end diff --git a/test/test_system_portable_poller.rb b/test/test_system_portable_poller.rb index f3f78516..44a2bb78 100644 --- a/test/test_system_portable_poller.rb +++ b/test/test_system_portable_poller.rb @@ -5,7 +5,7 @@ def setup pid = Process.pid @process = System::PortablePoller.new(pid) end - + def test_time_string_to_seconds assert_equal 0, @process.bypass.time_string_to_seconds('0:00:00') assert_equal 0, @process.bypass.time_string_to_seconds('0:00:55') diff --git a/test/test_system_process.rb b/test/test_system_process.rb index 9f1411a8..3fe0b5c2 100644 --- a/test/test_system_process.rb +++ b/test/test_system_process.rb @@ -5,24 +5,24 @@ def setup pid = Process.pid @process = System::Process.new(pid) end - + def test_exists_should_return_true_for_running_process assert_equal true, @process.exists? end - + def test_exists_should_return_false_for_non_existant_process assert_equal false, System::Process.new(9999999).exists? end - + def test_memory assert_kind_of Integer, @process.memory assert @process.memory > 0 end - + def test_percent_memory assert_kind_of Float, @process.percent_memory end - + def test_percent_cpu assert_kind_of Float, @process.percent_cpu end diff --git a/test/test_task.rb b/test/test_task.rb index ceb3a8b4..43f5199b 100644 --- a/test/test_task.rb +++ b/test/test_task.rb @@ -10,75 +10,75 @@ def setup @task.interval = 5 @task.prepare end - + # valid? - + def test_valid_should_return_false_if_no_name @task.name = nil no_stdout do assert !@task.valid? end end - + def test_valid_should_return_false_if_no_valid_states @task.valid_states = nil no_stdout do assert !@task.valid? end end - + def test_valid_should_return_false_if_no_initial_state @task.initial_state = nil no_stdout do assert !@task.valid? end end - + # transition - + def test_transition_should_be_always_if_no_block_was_given @task.transition(:foo, :bar) - + assert 1, @task.metrics.size assert Conditions::Always, @task.metrics.keys.first.class end - + # method_missing - + def test_method_missing_should_create_accessor_for_states assert_nothing_raised do @task.foo = 'testing' end end - + def test_method_missing_should_raise_for_non_states assert_raise NoMethodError do @task.baz = 5 end end - + def test_method_missing_should_raise_for_non_setters assert_raise NoMethodError do @task.baz end end - + # action - + def test_action_should_send_string_commands_to_system @task.foo = 'foo' Thread.current.stubs(:==).returns(true) @task.expects(:system).with('foo') no_stdout { @task.action(:foo, nil) } end - + def test_action_should_call_lambda_commands @task.foo = lambda { } Thread.current.stubs(:==).returns(true) @task.foo.expects(:call) no_stdout { @task.action(:foo, nil) } end - + def test_action_should_raise_not_implemented_on_non_string_or_lambda_action Thread.current.stubs(:==).returns(true) assert_raise NotImplementedError do @@ -86,175 +86,175 @@ def test_action_should_raise_not_implemented_on_non_string_or_lambda_action @task.action(:foo, nil) end end - + def test_action_from_outside_driver_should_send_message_to_driver @task.foo = 'foo' @task.driver.expects(:message).with(:action, [:foo, nil]) @task.action(:foo, nil) end - + # attach - + def test_attach_should_schedule_for_poll_condition c = Conditions::FakePollCondition.new @task.driver.expects(:schedule).with(c, 0) @task.attach(c) end - + def test_attach_should_regsiter_for_event_condition c = Conditions::FakeEventCondition.new c.expects(:register) @task.attach(c) end - + # detach - + def test_detach_should_reset_poll_condition c = Conditions::FakePollCondition.new c.expects(:reset) c.expects(:deregister).never @task.detach(c) end - + def test_detach_should_deregister_event_conditions c = Conditions::FakeEventCondition.new c.expects(:deregister).once @task.detach(c) end - + # trigger - + def test_trigger_should_send_message_to_driver c = Conditions::FakePollCondition.new @task.driver.expects(:message).with(:handle_event, [c]) @task.trigger(c) end - + # handle_poll - + def test_handle_poll_no_change_should_reschedule c = Conditions::FakePollCondition.new c.watch = @task c.interval = 10 - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + c.expects(:test).returns(false) @task.driver.expects(:schedule) - + no_stdout do @task.handle_poll(c) end end - + def test_handle_poll_change_should_move c = Conditions::FakePollCondition.new c.watch = @task c.interval = 10 - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + c.expects(:test).returns(true) @task.expects(:move).with(:up) - + no_stdout do @task.handle_poll(c) end end - + def test_handle_poll_should_use_overridden_transition c = Conditions::Tries.new c.watch = @task c.times = 1 c.transition = :start c.prepare - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + @task.expects(:move).with(:start) - + no_stdout do @task.handle_poll(c) end end - + def test_handle_poll_should_notify_if_triggering c = Conditions::FakePollCondition.new c.watch = @task c.interval = 10 c.notify = 'tom' - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + c.expects(:test).returns(true) @task.expects(:notify) - + no_stdout do @task.handle_poll(c) end end - + def test_handle_poll_should_not_notify_if_not_triggering c = Conditions::FakePollCondition.new c.watch = @task c.interval = 10 c.notify = 'tom' - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + c.expects(:test).returns(false) @task.expects(:notify).never - + no_stdout do @task.handle_poll(c) end end - + # handle_event - + def test_handle_event_should_move c = Conditions::FakeEventCondition.new c.watch = @task - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + @task.expects(:move).with(:up) - + no_stdout do @task.handle_event(c) end end - + def test_handle_event_should_notify_if_triggering c = Conditions::FakeEventCondition.new c.watch = @task c.notify = 'tom' - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + @task.expects(:notify) - + no_stdout do @task.handle_event(c) end end - + def test_handle_event_should_not_notify_if_no_notify_set c = Conditions::FakeEventCondition.new c.watch = @task - + m = Metric.new(@task, {true => :up}) @task.directory[c] = m - + @task.expects(:notify).never - + no_stdout do @task.handle_event(c) end diff --git a/test/test_timeline.rb b/test/test_timeline.rb index 2b138877..8f362d94 100644 --- a/test/test_timeline.rb +++ b/test/test_timeline.rb @@ -4,32 +4,32 @@ class TestTimeline < Test::Unit::TestCase def setup @timeline = Timeline.new(5) end - + def test_new_should_be_empty assert_equal 0, @timeline.size end - + def test_should_not_grow_to_more_than_size (1..10).each do |i| @timeline.push(i) end - + assert_equal [6, 7, 8, 9, 10], @timeline end - + def test_clear_should_clear_array @timeline << 1 assert_equal [1], @timeline assert_equal [], @timeline.clear end - + # def test_benchmark # require 'benchmark' - # + # # count = 1_000_000 - # + # # t = Timeline.new(10) - # + # # Benchmark.bmbm do |x| # x.report("go") { count.times { t.push(5) } } # end diff --git a/test/test_trigger.rb b/test/test_trigger.rb index 26fd1a50..9d260b2d 100644 --- a/test/test_trigger.rb +++ b/test/test_trigger.rb @@ -4,56 +4,56 @@ class TestTrigger < Test::Unit::TestCase def setup Trigger.reset end - + # base case - + def test_should_have_empty_triggers assert_equal({}, Trigger.triggers) end - + # register - + def test_register_should_add_condition_to_triggers c = Condition.new c.watch = stub(:name => 'foo') Trigger.register(c) - + assert_equal({'foo' => [c]}, Trigger.triggers) end - + def test_register_should_add_condition_to_triggers_twice watch = stub(:name => 'foo') c = Condition.new c.watch = watch Trigger.register(c) - + c2 = Condition.new c2.watch = watch Trigger.register(c2) - + assert_equal({'foo' => [c, c2]}, Trigger.triggers) end - + # deregister - + def test_deregister_should_remove_condition_from_triggers c = Condition.new c.watch = stub(:name => 'foo') Trigger.register(c) Trigger.deregister(c) - + assert_equal({}, Trigger.triggers) end - + # broadcast - + def test_broadcast_should_call_process_on_each_condition c = Condition.new c.watch = stub(:name => 'foo') Trigger.register(c) - + c.expects(:process).with(:state_change, [:up, :start]) - + Trigger.broadcast(c.watch, :state_change, [:up, :start]) end end \ No newline at end of file diff --git a/test/test_watch.rb b/test/test_watch.rb index 82861e3a..72feffd7 100644 --- a/test/test_watch.rb +++ b/test/test_watch.rb @@ -9,19 +9,19 @@ def setup @watch.stop = lambda { } @watch.prepare end - + # new - + def test_new_should_have_no_behaviors assert_equal [], @watch.behaviors end - + def test_new_should_have_no_metrics Watch::VALID_STATES.each do |state| assert_equal [], @watch.metrics[state] end end - + def test_new_should_have_standard_attributes assert_nothing_raised do @watch.name = 'foo' @@ -32,41 +32,41 @@ def test_new_should_have_standard_attributes @watch.grace = 5 end end - + def test_new_should_have_unmonitored_state assert_equal :unmonitored, @watch.state end - + # valid? - + def test_valid? God::Process.any_instance.expects(:valid?) @watch.valid? end - + # behavior - + def test_behavior_should_record_behavior beh = nil @watch.behavior(:fake_behavior) { |b| beh = b } assert_equal 1, @watch.behaviors.size assert_equal beh, @watch.behaviors.first end - + def test_invalid_behavior_should_abort assert_abort do @watch.behavior(:invalid) end end - + # transition - + def test_transition_should_abort_on_invalid_start_state assert_abort do @watch.transition(:foo, :bar) end end - + def test_transition_should_accept_all_valid_start_states assert_nothing_raised do Watch::VALID_STATES.each do |state| @@ -74,35 +74,35 @@ def test_transition_should_accept_all_valid_start_states end end end - + def test_transition_should_create_and_record_a_metric_for_the_given_start_state @watch.transition(:init, :start) { } assert_equal 1, @watch.metrics[:init].size end - + # lifecycle - + def test_lifecycle_should_create_and_record_a_metric_for_nil_start_state @watch.lifecycle { } assert_equal 1, @watch.metrics[nil].size end - + # start_if - + def test_start_if_should_place_a_metric_on_up_state @watch.start_if { } assert_equal 1, @watch.metrics[:up].size end - + # restart_if - + def test_restart_if_should_place_a_metric_on_up_state @watch.restart_if { } assert_equal 1, @watch.metrics[:up].size end - + # monitor - + def test_monitor_should_move_to_init_if_available @watch.instance_eval do transition(:init, :up) { } @@ -110,27 +110,27 @@ def test_monitor_should_move_to_init_if_available @watch.expects(:move).with(:init) @watch.monitor end - + def test_monitor_should_move_to_up_if_no_init_available @watch.expects(:move).with(:up) @watch.monitor end - + # unmonitor - + def test_unmonitor_should_move_to_nil @watch.expects(:move).with(:unmonitored) @watch.unmonitor end - + # move - + def test_move_should_not_clean_up_if_from_state_is_nil Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + metric = nil - + @watch.instance_eval do transition(:init, :up) do |on| metric = on @@ -140,18 +140,18 @@ def test_move_should_not_clean_up_if_from_state_is_nil end end end - + metric.expects(:disable).never - + no_stdout { @watch.move(:init) } end - + def test_move_should_clean_up_from_state_if_not_nil Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + metric = nil - + @watch.instance_eval do transition(:init, :up) do |on| metric = on @@ -161,40 +161,40 @@ def test_move_should_clean_up_from_state_if_not_nil end end end - + no_stdout { @watch.move(:init) } - + metric.expects(:disable) - + no_stdout { @watch.move(:up) } end - + def test_move_should_call_action Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + @watch.expects(:action).with(:start) - + no_stdout { @watch.move(:start) } end - + def test_move_should_move_to_up_state_if_no_start_or_restart_metric Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + [:start, :restart].each do |state| @watch.expects(:action) no_stdout { @watch.move(state) } assert_equal :up, @watch.state end end - + def test_move_should_enable_destination_metric Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + metric = nil - + @watch.instance_eval do transition(:init, :up) do |on| metric = on @@ -204,53 +204,53 @@ def test_move_should_enable_destination_metric end end end - + metric.expects(:enable) - + no_stdout { @watch.move(:init) } end - + # action - + def test_action_should_pass_start_and_stop_actions_to_call_action Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + c = Conditions::FakePollCondition.new [:start, :stop].each do |cmd| @watch.expects(:call_action).with(c, cmd) @watch.action(cmd, c) end end - + def test_action_should_do_stop_then_start_if_no_restart_command Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + c = Conditions::FakePollCondition.new @watch.expects(:call_action).with(c, :stop) @watch.expects(:call_action).with(c, :start) @watch.action(:restart, c) end - + def test_action_should_restart_to_call_action_if_present Thread.current.stubs(:==).returns(true) @watch.driver.expects(:message).never - + @watch.restart = lambda { } c = Conditions::FakePollCondition.new @watch.expects(:call_action).with(c, :restart) @watch.action(:restart, c) end - + # call_action - + def test_call_action c = Conditions::FakePollCondition.new God::Process.any_instance.expects(:call_action).with(:start) no_stdout { @watch.call_action(c, :start) } end - + def test_call_action_should_call_before_start_when_behavior_has_that @watch.behavior(:fake_behavior) c = Conditions::FakePollCondition.new @@ -258,7 +258,7 @@ def test_call_action_should_call_before_start_when_behavior_has_that Behaviors::FakeBehavior.any_instance.expects(:before_start) no_stdout { @watch.call_action(c, :start) } end - + def test_call_action_should_call_after_start_when_behavior_has_that @watch.behavior(:fake_behavior) c = Conditions::FakePollCondition.new @@ -266,13 +266,13 @@ def test_call_action_should_call_after_start_when_behavior_has_that Behaviors::FakeBehavior.any_instance.expects(:after_start) no_stdout { @watch.call_action(c, :start) } end - + # canonical_hash_form - + def test_canonical_hash_form_should_convert_symbol_to_hash assert_equal({true => :foo}, @watch.canonical_hash_form(:foo)) end - + def test_canonical_hash_form_should_convert_hash_to_hash assert_equal({true => :foo}, @watch.canonical_hash_form(true => :foo)) end