pax_global_header00006660000000000000000000000064122605246550014521gustar00rootroot0000000000000052 comment=79142b14b75d0ac213100c715e4d24783852131d ruby-god-0.13.3/000077500000000000000000000000001226052465500133355ustar00rootroot00000000000000ruby-god-0.13.3/Announce.txt000066400000000000000000000106671226052465500156560ustar00rootroot00000000000000Subject: [ANN] god 0.6.0 released (and mailing list) !!!NEW MAILING LIST!!! We now have a mailing list at http://groups.google.com/group/god-rb This release is primarily focused on increased stability, robustness, and code cleanliness. The last release (0.5.0) switched from TCP sockets to Unix Domain Sockets for the CLI tools. There were some issues regarding file descriptors that would cause god to hang when restarting (the unix socket was being held open by spawned processes). These problems are all fixed in 0.6.0. You may need to kill any processes that were started under god 0.5.0 when you upgrade to 0.6.0 (you will only need to do this once during the upgrade process). More attention is now paid to Syslogging behavior. God will delete its own PID file when terminated via a user request. A new command line option now allows you to check whether your installation properly handles events (sometimes the C extension will compile ok, but still not support events. Gentoo, I'm looking at you). Run `god check` to see an attempt to use the event system. It will tell you if your installation does not support events. A watch can now be removed entirely at runtime using `god remove `. A new condition DiskUsage allows you to check the available disk space on a given volume. Updated documentation is now available on the website: http://god.rubyforge.org/ WHAT IS GOD? 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 monitoring application available. DISCLAIMER God is still beta so I do not yet recommend you use it for mission critical tasks. We are using it at Powerset, Inc. to monitor our public facing applications, but then, we're daring fellows. INSTALL sudo gem install god FEATURES * Config file is written in Ruby * Easily write your own custom conditions in Ruby * Supports both poll and event based conditions * Different poll conditions can have different intervals * Easily control non-daemonized processes 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. 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.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" w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" 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| start.condition(:process_running) do |c| c.interval = 5.seconds 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| c.to_state = [:start, :restart] c.times = 5 c.within = 5.minute c.transition = :unmonitored c.retry_in = 10.minutes c.retry_times = 5 c.retry_within = 2.hours end end end end DOCS Detailed documentation is available at http://god.rubyforge.org/ CHANGES == 0.6.0 / 2007-12-4 * Minor Enhancement * Move Syslog calls into God::Logger and clean up all calling code * Remove god's pid file on user requested termination * Better handling and cleanup of DRb server's unix domain socket * Allow shorthand for requesting a god log * Add `god check` to make it easier to diagnose event problems * Refactor god binary into class/method structure * Implement `god remove` to remove a Task altogether * New Conditions * DiskUsage < PollCondition - trigger if disk usage is above limit on mount [Rudy Desjardins] AUTHORS Tom Preston-Werner Kevin Clark ruby-god-0.13.3/Gemfile000066400000000000000000000000461226052465500146300ustar00rootroot00000000000000source 'https://rubygems.org' gemspec ruby-god-0.13.3/History.txt000066400000000000000000000406121226052465500155420ustar00rootroot00000000000000== 0.13.3 / 2013-09-25 * Minor Enhancements * Invoke commands for all watchers * Airbrake reporter * Improvements to socket responding condition == 0.13.2 / 2013-02-26 * Minor Enhancements * Added file_touched condition (#86) * Bug fixes * Ensure Ruby 1.9 fixes only apply to 1.9.0 and 1.9.1 (#125) * Documentation fixes (#109, #84, #92) == 0.13.1 / 2012-09-18 * Minor Changes * Prevent auto-loading from bundler by requiring $load_god to require (#97) == 0.13.0 / 2012-09-17 * Minor Changes * Reduce verbosity of non-failing conditions (#111) == 0.12.1 / 2012-01-21 * Bug Fixes * Fix undefined variable problem in CLI (#82) == 0.12.0 / 2012-01-13 * Minor Enhancements * Add umask support * Add socket response condition (#25) * Don't require tests run as sudo under non-linux systems (#15) * Add Bundler support * Add keepalive simple conditional setups (#71) * Better load command to act upon removed watches (#70) * Add support for ssl in http_response_code condition (#36) * New documentation at http://godrb.com * Bug Fixes * Use IO.print instead of IO.puts for thread safety (#35) * Fix Slashproc poller for commands with spaces (#31) * Various segfault and kernel panic fixes * Fix SMTP Auth documentation (#29) * Fix a bunch of tests that were failing on Ruby 1.9 == 0.11.0 / 2010-07-01 * Major Changes * Rewrite notification system to be more consistent and flexible. == 0.10.1 / 2010-05-17 * Bug Fixes * Fix date in gemspec == 0.10.0 / 2010-05-17 * Minor Enhancements * Add stop_timeout and stop_signal options to Watch * Bug Fixes * Stop command string was being ignored == 0.9.0 / 2010-04-03 * Minor Enhancements * Allow kqueue for OpenBSD and NetBSD * Add err_log and err_log_cmd * Add God.terminate_timeout option * Respect --log-level in Syslog * Add configuration parameters to set permissions on socket * Add Scout contact * Add Prowl contact * Bug Fixes * Fix interleaved log messages * Experimental * Ruby 1.9 support == 0.8.0 / 2009-11-30 * Minor Enhancements * Rubygems decontamination * Use Monitor instead of Mutex to provide ability to wait with a timeout * Only generate log messages when they're being used * Remove usage of Thread.critical in DriverEventQueue * Update to work with latest bleak-house * Cache some frequent lookups to reduce object creation * Changing the @io.print call in SimpleLogger to not concatenate the formatted results before printing * Bug fixes * Make sure we don't leak hash slots when processes die * Make sure the driver is shutdown on Task#unregister! * Fix memory leak when issuing "god load" successfully * Fix defunct process == NOTE At this point I will stop giving credit in the history. Look at the author and committer in the commit for that info. == 0.7.22 / 2009-10-29 * Minor Enhancements * Save ARGV so we can get access to it later if we want [github.com/eric] == 0.7.21 / 2009-10-29 * Minor Enhancements * Cache some frequent lookups to reduce object creation [github.com/eric] * Try to make SimpleLogger less leaky [github.com/eric] == 0.7.20 / 2009-09-24 * Minor Enhancements * Rewrite `god status` command to be not as horrible. Add ability to get status for individual tasks. == 0.7.19 / 2009-09-21 * Minor Enhancements * Teach `god status` to take a task name as a param and return an exit code of 0 if all watches are up or a non-zero exit code (equal to the number of non-up watches) if they are not. == 0.7.18 / 2009-09-09 * Minor Enhancements * Better handling of unexpected exceptions in conditions * Added support for running processes in a directory other than '/' [github.com/samhendley] * Bug Fixes * Generate an actual unique identifier for email contact [github.com/underley] == 0.7.17 / 2009-08-25 * Bug Fixes * Fix the glob and directory config loading for -c option == 0.7.16 / 2009-08-24 * Minor Enhancements * Better logging for disk_usage condition [github.com/lettherebecode] * Bug Fixes * Only sleep if driver delay is > 0 [github.com/ps2] * Rescue Timeout::Error exception due to smtp server timing out [github.com/ps2] * Disk usage condition should use `df -P` to prevent line splitting [github.com/mseppae] * Always require YAML so binary works on dumb systems == 0.7.15 / 2009-08-19 * Minor Enhancements * Support SSL Campfire connections [github.com/eric] * Allow wildcards in -c configuration file option == 0.7.14 / 2009-08-10 * Minor Enhancements * Only store log lines when a client wishes to see them * Add an lsb-compliant init script into god/init [Woody Peterson] * Never require stop command; use default killer if none is specified * Bug Fixes * Fix redefinition error for time.h and allow it to compile on Ubuntu Edgy [github.com/tbuser] * Fix a memory leak in jabber by adding a call to jabber_client.close [github.com/woahdae] * Make jabber code manage one connection to make it faster, use less memory, and not leak [github.com/woahdae] == 0.7.13 / 2009-05-04 * Bug Fixes * Auto daemonized processes are now stopped/unmonitored correctly [github.com/jcapote] == 0.7.12 / 2008-12-10 * Bug Fixes * Fix capistrano deployability [github.com/eric] * Fix event handling [brianw] == 0.7.11 / 2008-11-14 * Bug Fixes * Make notifications work inside lifecycle blocks == 0.7.10 / 2008-11-13 * Major Enhancements * Enable sending of arbitrary signals to a task or group via `god signal` * Bug Fixes * setup logging *after* loading a given config file when daemonized. enables logging to the 'God.log_file' specified in a config file. [github.com/jnewland] * New Conditions * FileMtime < PollCondition - trigger on file mtime durations [github.com/jwilkins] * New Contacts * Twitter - allow messages to twitter [github.com/jwilkins] * Campfire - send messages to 37signals' Campfire [github.com/hellvinz] * Minor Enhancements * Add watch log_cmd that can be reopened with STDOUT instead of a log file [github.com/jberkel] * Added webhook output support [Martyn Loughran] == 0.7.9 / 2008-08-06 * Major Enhancements * Use a psuedo-priority queue for more efficient driver loop [Darrell Kresge] * Bug Fixes * Fix file_writable? when using chroot [github.com/eric] == 0.7.8 / 2008-07-09 * Bug Fixes * Catch all Exceptions from HttpResponseCode condition [github.com/rliebling] * Don't error out if the process went away in SlashProcPoller [Kevin Clark] * Correction of Task#handle_poll to prevent crash under event registration failure conditions. [github.com/raggi] * Cleaned up logging of failed e-mail sends. [github.com/raggi] * Listen on 127.0.0.1 when using God as a client. [github.com/halorgium] * New Behaviors * clean_unix_socket [github.com/gma] * New Contacts * jabber [github.com/jwulff] * email via sendmail [github.com/monde] * Minor Enhancements * chroot support [github.com/eric] * Added God.log_file for the main god log, overridden by command line option. [github.com/raggi] * Print groups from `god status` command if present [github.com/pdlug] * Allow headers to be specified for http_response_code condition [github.com/pdlug] == 0.7.7 / 2008-06-17 * Bug Fixes * Fix detection of proc file system [raggi] == 0.7.6 / 2008-05-13 * Major Enhancements * Implement System::Process methods for Linux based on /proc [Kevin Clark] * Minor Enhancements * Allowing directories to be loaded at start [Bert Goethals] * Bug Fixes * Don't leak events on error in the kqueue handler [Kevin Clark] == 0.7.5 / 2008-02-21 * Bug Fixes * Remove Ruby's Logger and replace with custom SimpleLogger to stop threaded leak == 0.7.4 / 2008-02-18 * Bug Fixes * Introduce local scope to prevent faulty optimization that causes memory to leak == 0.7.3 / 2008-02-14 * Minor Enhancements * Add --bleakhouse to make running diagnostics easier * Bug Fixes * Use ::Process.kill(0, ...) instead of `kill -0` [queso] * Fix pid_file behavior in process-centric conditions so they work with tasks [matias] * Redirect output of daemonized god to log file or /dev/null earlier [_eric] == 0.7.2 / 2008-02-04 * Bug Fixes * Start event system for CLI commands * Up internal history to 100 lines per watch == 0.7.1 / 2008-02-04 * Minor Enhancements * Add --no-events option to completely disable events system == 0.7.0 / 2008-02-01 * Minor Enhancements * Better default pid_file_directory behavior * Add --attach to specify that god should quit if exits * Bug Fixes * Handle ECONNRESET in HttpResponseCode == 0.6.12 / 2008-01-31 * Minor Enhancements * Allow log file output for non-daemonized god * Switch to SIGTERM from SIGHUP for default lambda killer == 0.6.11 / 2008-01-31 * Major Enhancements * HUGE refactor of timer system to simplify scheduling * Minor Enhancements * Check for a truly working event system and disallow event conditions if none is present == 0.6.10 / 2008-01-24 * Bug Fixes * Fix ensure_stop nil pid no local variable bug == 0.6.9 / 2008-01-23 * Bug Fixes * Fix Timer condition dedup behavior == 0.6.8 / 2008-01-23 * Minor Enhancements * Warn if a command returns a non-zero exit code * Ensure that stop command actually stops process == 0.6.7 / 2008-01-22 * Minor Enhancements * Add --no-syslog option to disable Syslog * Allow contact redeclaration (dups are ignored) == 0.6.6 / 2008-01-07 * Bug Fixes * Redo Timer mutexing to reduce synchronization needs == 0.6.5 / 2008-01-04 * Bug Fixes * Fix Timer descheduling deadlock issue * Change HttpResponseCode to use GET instead of HEAD == 0.6.4 / 2008-12-31 * Bug Fixes * Refactor Hub to clarify mutexing * Eliminate potential iteration problem in Timer * Add caching PID accessor to process to solve event deregistration failure == 0.6.3 / 2007-12-18 * Minor Enhancements * Output ProcessExits registration/deregistration info == 0.6.2 / 2007-12-17 * Minor Enhancements * Output registered PID for ProcessExits * Bug Fixes * Fix `god remove ` not working for unmonitored watches == 0.6.1 / 2007-12-14 * Minor Enhancement * Log when state change is complete == 0.6.0 / 2007-12-4 * Minor Enhancement * Move Syslog calls into God::Logger and clean up all calling code * Remove god's pid file on user requested termination * Better handling and cleanup of DRb server's unix domain socket * Allow shorthand for requesting a god log * Add `god check` to make it easier to diagnose event problems * Refactor god binary into class/method structure * Implement `god remove` to remove a Task altogether * New Conditions * DiskUsage < PollCondition - trigger if disk usage is above limit on mount [Rudy Desjardins] == 0.5.2 / 2007-10-10 * Minor Enhancement * Allow extra args to pass through to config file == 0.5.1 / 2007-10-08 * Bug Fixes * Rescue connection refused in http response code condition == 0.5.0 / 2007-10-05 * Major Enhancements * Implement lifecycle scoped metric to allow for cross-state conditions * Add TriggerCondition for conditions that need info about state changes * Implement notification system * Add Tasks (a generalization of Watches) to do non-process related tasks * Add example init.d file in GOD_INSTALL_DIR/init/god [scott becker] * Add human readable info to conditions (and make low level log lines debug) * Switch DRb to use a unix domain socket for security reasons * Minor Enchancements * Allow EventConditions to do transition overloading * Report errors during god startup instead of failing silently * Make transition block optional (default to Always condition returning true) * Better usage info for `god --help` * Explain what's going on when attempting to rebind to an in-use port * Add -b option to god binary to auto-bind to an unused port * Add `god quit` to stop god without stopping any tasks * Make self-daemonized Watch commands synchronous (as they should be) * Allow self-daemonized Watches to specify a log (could be useful) * Check for existence of config file if specified * Robustify `god load` and report errors back to the command issuer * Warn when `god load` tries to set global options * Add Configurable.clear method and make built-in conditions clear on entry * New Conditions * Flapping < TriggerCondition - trigger on state change * HttpResponseCode < PollCondition - trigger on http response code or timeout (thx scott becker) * New Contacts * Email < Contact - notify via email (smtp) * Bug Fixes * Fix abort not aborting problem * Fix -p option not working for god binary * Fix God.init not accepting block (thx _eric) * Fix SIGHUP ignore (thx _eric) * Fix error reporting on `god --help` (don't error report a normal SystemExit) == 0.4.3 / 2007-09-10 * Bug Fixes * fix Process#alive? to not raise on no such file (affects `god terminate`) == 0.4.2 / 2007-09-10 * Bug Fixes * fix netlink buffer issue that prevented events on Linux from working consistently [dkresge] == 0.4.1 / 2007-09-10 * Bug Fixes * require 'stringio' for ruby 1.8.5 == 0.4.0 / 2007-09-10 * Major Enhancements * Add the ability for conditions to override transition state (for exceptional cases) * Implement dynamic load of config files while god is running (god load ) * Add ability to save auto-daemonized process output to a log file * Add robust default stop lambda command for auto-daemonized processes (inspired by _eric) * Add status command for god binary (shows status of each watch) * Create proper logger with timestamps * Add log command to god binary to get real time logs for a specific watch from a running god instance * Add terminate command for god binary (stop god and all watches) * Minor Enhancements * Enforce validity of Watches * Enforce that God.init is not called after a Watch * Move pid_file_directory creation and validation to God.start * Remove check for at least one Watch during startup (now that dynamic loading exists) * New Conditions * Tries < PollCondition - triggers after the specified number of tries * Add :notify_when_flapping behavior to check for oscillation [kevinclark] * Add :degrading_lambda condition. [kevinclark] It uses a decaying interval (1/2 rate) for 3 cycles before failing. * Bug Fixes * Use exit!(0) instead of exit! in god binary to exit with code 0 (instead of default -1) * Command line group control fixed * Fix cross-thread return problem == 0.3.0 / 2007-08-17 * Fix netlink header problem on Ubuntu Edgy [Dan Sully] * Add uid/gid setting for processes [kevinclark] * Add autostart flag for watches so they don't necessarily startup with god [kevinclark] * Change command line call options for god binary to accommodate watch start/stop functionality * Add individual start/stop/restart grace periods for finer grained control * Change default DRb port to 17165 ('god'.to_i(32)) * Implement command line control to start/restart/stop/monitor/unmonitor watches/groups by name * Watches can now belong to a group that can be controlled as a whole * Allow god to be installed (sans events) on systems that don't support events * Daemonize and handle PID files for non-daemonizing scripts [kevinclark] * Fix simple mode lifecycle gap * Remove necessity to specify pid_file for conditions * Change config file to use God.init and God.watch directly instead of God.meddle block * Move god binary command logic to main library * Enhance god binary with better reporting * Fix synchronization bug in Timer (reported by Srini Panguluri) * Add Lambda condition for easy custom conditions [Mike Mintz] * Add sugar for numerics (seconds, minutes, kilobytes, megabytes, percent, etc) * Add optional PID and log file generation to god binary for daemon mode * Add God.load to do glob enabled loading * Add -V option to god binary for detailed version/build info == 0.2.0 / 2007-07-18 * Rewrote innards to use a state and event based lifecycle * Basic support for events via kqueue (bsd/darwin) and netlink/pec (linux) [kevinclark] * Added advanced syntax (simple syntax calls advanced api underneath) * Condition returns have changed meaning. With simple syntax, a true return activates block * Updated http://god.rubyforge.org with updated simple config and new advanced config == 0.1.0 / 2007-07-07 * 1 major enhancement * Birthday! ruby-god-0.13.3/LICENSE000066400000000000000000000020711226052465500143420ustar00rootroot00000000000000(The MIT License) Copyright (c) 2007 Tom Preston-Werner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-god-0.13.3/README.md000066400000000000000000000013211226052465500146110ustar00rootroot00000000000000God: The Ruby Framework for Process Management ============================================== * Authors: Tom Preston-Werner, Kevin Clark, Eric Lindvall * Website: http://godrb.com Description ----------- 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 monitoring application available. Documentation ------------- See in-repo documentation at `REPO_ROOT/doc`. See online documentation at http://godrb.com. Community --------- Sign up for the god mailing list at http://groups.google.com/group/god-rb License ------- See LICENSE file. ruby-god-0.13.3/Rakefile000066400000000000000000000110521226052465500150010ustar00rootroot00000000000000require 'rubygems' require 'rake' require 'rdoc/task' require 'date' ############################################################################# # # Helper functions # ############################################################################# def name @name ||= Dir['*.gemspec'].first.split('.').first end def version line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/] line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1] end def date Date.today.to_s end def rubyforge_project name end def gemspec_file "#{name}.gemspec" end def gem_file "#{name}-#{version}.gem" end def replace_header(head, header_name) head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"} end ############################################################################# # # Standard tasks # ############################################################################# task :default => :test require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/test_*.rb' test.verbose = true end desc "Generate RCov test coverage and open in your browser" task :coverage do require 'rcov' sh "rm -fr coverage" sh "rcov test/test_*.rb" sh "open coverage/index.html" end require 'rdoc/task' Rake::RDocTask.new do |rdoc| rdoc.rdoc_dir = 'rdoc' rdoc.title = "#{name} #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end desc "Open an irb session preloaded with this library" task :console do sh "irb -rubygems -r ./lib/#{name}.rb" end ############################################################################# # # Custom tasks (add your own tasks here) # ############################################################################# desc "Generate and view the site locally" task :site do # Generate the dynamic parts of the site. puts "Generating dynamic..." require 'gollum' wiki = Gollum::Wiki.new('.', :base_path => '/doc') html = wiki.page('god', 'HEAD').formatted_data.gsub("\342\200\231", "'") template = File.read('./site/index.template.html') index = template.sub("{{ content }}", html) File.open('./site/index.html', 'w') do |f| f.write(index) end puts "Done. Opening in browser..." sh "open site/index.html" end desc "Commit the local site to the gh-pages branch and deploy" task :site_release do # Ensure the gh-pages dir exists so we can generate into it. puts "Checking for gh-pages dir..." unless File.exist?("./gh-pages") puts "No gh-pages directory found. Run the following commands first:" puts " `git clone git@github.com:mojombo/god gh-pages" puts " `cd gh-pages" puts " `git checkout gh-pages`" exit(1) end # Copy the rest of the site over. puts "Copying static..." sh "cp -R site/* gh-pages/" # Commit the changes sha = `git log`.match(/[a-z0-9]{40}/)[0] sh "cd gh-pages && git add . && git commit -m 'Updating to #{sha}.' && git push" puts 'Done.' end ############################################################################# # # Packaging tasks # ############################################################################# desc "Create tag v#{version} and build and push #{gem_file} to Rubygems" task :release => :build do unless `git branch` =~ /^\* master$/ puts "You must be on the master branch to release!" exit! end sh "git commit --allow-empty -a -m 'Release #{version}'" sh "git tag v#{version}" sh "git push origin master" sh "git push origin v#{version}" sh "gem push pkg/#{name}-#{version}.gem" end desc "Build #{gem_file} into the pkg directory" task :build => :gemspec do sh "mkdir -p pkg" sh "gem build #{gemspec_file}" sh "mv #{gem_file} pkg" end desc "Generate #{gemspec_file}" task :gemspec do # read spec file and split out manifest section spec = File.read(gemspec_file) head, manifest, tail = spec.split(" # = MANIFEST =\n") # replace name version and date replace_header(head, :name) replace_header(head, :version) replace_header(head, :date) #comment this out if your rubyforge_project has a different name replace_header(head, :rubyforge_project) # determine file list from git ls-files files = `git ls-files`. split("\n"). sort. reject { |file| file =~ /^\./ }. reject { |file| file =~ /^(rdoc|pkg|examples|ideas|init|site)/ }. map { |file| " #{file}" }. join("\n") # piece file back together and write manifest = " s.files = %w[\n#{files}\n ]\n" spec = [head, manifest, tail].join(" # = MANIFEST =\n") File.open(gemspec_file, 'w') { |io| io.write(spec) } puts "Updated #{gemspec_file}" end ruby-god-0.13.3/bin/000077500000000000000000000000001226052465500141055ustar00rootroot00000000000000ruby-god-0.13.3/bin/god000077500000000000000000000073351226052465500146140ustar00rootroot00000000000000#!/usr/bin/env ruby STDOUT.sync = true $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) require 'optparse' require 'drb' require 'yaml' begin # Save ARGV in case someone wants to use it later ORIGINAL_ARGV = ARGV.dup options = {:daemonize => true, :port => 17165, :syslog => true, :events => true} opts = OptionParser.new do |opts| opts.banner = <<-EOF Usage: Starting: god [-c ] [-p | -b] [-P ] [-l ] [-D] Querying: god [-p ] god [-p ] god -v god -V (must be run as root to be accurate on Linux) Commands: start start task or group restart restart task or group stop stop task or group monitor monitor task or group unmonitor unmonitor task or group remove remove task or group from god load [action] load a config into a running god log show realtime log for given task status [task or group name] show status signal signal all matching tasks quit stop god terminate stop god and all tasks check run self diagnostic Options: EOF opts.on("-cCONFIG", "--config-file CONFIG", "Configuration file") do |x| options[:config] = x end opts.on("-pPORT", "--port PORT", "Communications port (default 17165)") do |x| options[:port] = x end opts.on("-b", "--auto-bind", "Auto-bind to an unused port number") do options[:port] = "0" end opts.on("-PFILE", "--pid FILE", "Where to write the PID file") do |x| options[:pid] = x end opts.on("-lFILE", "--log FILE", "Where to write the log file") do |x| options[:log] = x end opts.on("-D", "--no-daemonize", "Don't daemonize") do options[:daemonize] = false end opts.on("-v", "--version", "Print the version number and exit") do options[:version] = true end opts.on("-V", "Print extended version and build information") do options[:info] = true end opts.on("--log-level LEVEL", "Log level [debug|info|warn|error|fatal]") do |x| options[:log_level] = x.to_sym end opts.on("--no-syslog", "Disable output to syslog") do options[:syslog] = false end opts.on("--attach PID", "Quit god when the attached process dies") do |x| options[:attach] = x end opts.on("--no-events", "Disable the event system") do options[:events] = false end opts.on("--bleakhouse", "Enable bleakhouse profiling") do options[:bleakhouse] = true end end opts.parse! # validate if options[:log_level] && ![:debug, :info, :warn, :error, :fatal].include?(options[:log_level]) abort("Invalid log level '#{options[:log_level]}'") end # Use this flag to actually load all of the god infrastructure $load_god = true # dispatch if !options[:config] && options[:version] require 'god' God::CLI::Version.version elsif !options[:config] && options[:info] require 'god' God::EventHandler.load God::CLI::Version.version_extended elsif !options[:config] && command = ARGV[0] require 'god' God::EventHandler.load God::CLI::Command.new(command, options, ARGV) else require 'god/cli/run' God::CLI::Run.new(options) end rescue Exception => e if e.instance_of?(SystemExit) raise else puts 'Uncaught exception' puts e.message puts e.backtrace.join("\n") end end ruby-god-0.13.3/doc/000077500000000000000000000000001226052465500141025ustar00rootroot00000000000000ruby-god-0.13.3/doc/god.asciidoc000066400000000000000000001416401226052465500163610ustar00rootroot00000000000000Installation ------------ The best way to get god is via rubygems: ```terminal $ [sudo] gem install god ``` Requirements ------------ 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 into the kernel and god must be run as root. The following systems have been tested. Help us test it on others! * Darwin 10.4.10 * RedHat Fedora 6-15 * Ubuntu Dapper (no events) * Ubuntu Feisty * CentOS 4.5 (no events), 5, 6 Quick Start ----------- Note: this quick start guide requires god 0.12.0 or above. You can check your version by running: ```terminal $ god --version ``` The easiest way to understand how god will make your life better is by trying out a simple example. To get you up and running quickly, I'll show you how to keep a trivial server running. Open up a new directory and write a simple server. Let's call it `simple.rb`: ```ruby loop do puts 'Hello' sleep 1 end ``` Now we'll write a god config file that tells god about our process. Place it in the same directory and call it `simple.god`: ```ruby God.watch do |w| w.name = "simple" w.start = "ruby /full/path/to/simple.rb" w.keepalive end ``` This is the simplest possible god configuration. We start by declaring a `God.watch` block. A watch in god represents a process that we want to watch and control. Each watch must have, at minimum, a unique name and a command that tells god how to start the process. The `keepalive` declaration tells god to keep this process alive. If the process is not running when god starts, it will be started. If the process dies, it will be restarted. In this example the `simple` process runs foreground, so god will take care of daemonizing it and keeping track of the PID for us. When possible, it's best to let god daemonize processes for us, that way we don't have to worry about specifying and keeping track of PID files. Later on we'll see how to manage processes that can't run foreground or that require PID files to be specified. To run god, we give it the configuration file we wrote with `-c`. To see what's going on, we can ask it to run foreground with `-D`: ```terminal $ god -c path/to/simple.god -D ``` There are two ways that god can monitor your process. The first and better way is with process events. Not every system supports it, but those that do will automatically use it. With events, god will know immediately when a process exits. For those systems without process event support, god will use a polling mechanism. The output you see throughout this section will show both ways. After starting god, you should see some output like the following: ```terminal # Events I [2011-12-10 15:24:34] INFO: Loading simple.god I [2011-12-10 15:24:34] INFO: Syslog enabled. I [2011-12-10 15:24:34] INFO: Using pid file directory: /Users/tom/.god/pids I [2011-12-10 15:24:34] INFO: Started on drbunix:///tmp/god.17165.sock I [2011-12-10 15:24:34] INFO: simple move 'unmonitored' to 'init' I [2011-12-10 15:24:34] INFO: simple moved 'unmonitored' to 'init' I [2011-12-10 15:24:34] INFO: simple [trigger] process is not running (ProcessRunning) I [2011-12-10 15:24:34] INFO: simple move 'init' to 'start' I [2011-12-10 15:24:34] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-10 15:24:34] INFO: simple moved 'init' to 'start' I [2011-12-10 15:24:34] INFO: simple [trigger] process is running (ProcessRunning) I [2011-12-10 15:24:34] INFO: simple move 'start' to 'up' I [2011-12-10 15:24:34] INFO: simple registered 'proc_exit' event for pid 23298 I [2011-12-10 15:24:34] INFO: simple moved 'start' to 'up' # Polls I [2011-12-07 09:40:18] INFO: Loading simple.god I [2011-12-07 09:40:18] INFO: Syslog enabled. I [2011-12-07 09:40:18] INFO: Using pid file directory: /Users/tom/.god/pids I [2011-12-07 09:40:18] INFO: Started on drbunix:///tmp/god.17165.sock I [2011-12-07 09:40:18] INFO: simple move 'unmonitored' to 'up' I [2011-12-07 09:40:18] INFO: simple moved 'unmonitored' to 'up' I [2011-12-07 09:40:18] INFO: simple [trigger] process is not running (ProcessRunning) I [2011-12-07 09:40:18] INFO: simple move 'up' to 'start' I [2011-12-07 09:40:18] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-07 09:40:19] INFO: simple moved 'up' to 'up' I [2011-12-07 09:40:19] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 09:40:24] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 09:40:29] INFO: simple [ok] process is running (ProcessRunning) ``` Here you can see god starting up, noticing that the `simple` process isn't running, starting it, and then checking every five seconds to make sure it's up. If you'd like to see god work its magic, go ahead and kill the `simple` process. You should then see something like this: ```terminal # Events I [2011-12-10 15:33:38] INFO: simple [trigger] process 23416 exited (ProcessExits) I [2011-12-10 15:33:38] INFO: simple move 'up' to 'start' I [2011-12-10 15:33:38] INFO: simple deregistered 'proc_exit' event for pid 23416 I [2011-12-10 15:33:38] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-10 15:33:38] INFO: simple moved 'up' to 'start' I [2011-12-10 15:33:38] INFO: simple [trigger] process is running (ProcessRunning) I [2011-12-10 15:33:38] INFO: simple move 'start' to 'up' I [2011-12-10 15:33:38] INFO: simple registered 'proc_exit' event for pid 23601 I [2011-12-10 15:33:38] INFO: simple moved 'start' to 'up' # Polls I [2011-12-07 09:54:59] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 09:55:04] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 09:55:09] INFO: simple [trigger] process is not running (ProcessRunning) I [2011-12-07 09:55:09] INFO: simple move 'up' to 'start' I [2011-12-07 09:55:09] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-07 09:55:09] INFO: simple moved 'up' to 'up' I [2011-12-07 09:55:09] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 09:55:14] INFO: simple [ok] process is running (ProcessRunning) ``` While keeping a process up is useful, it would be even better if we could make sure our process was behaving well and restart it when resource utilization exceeds our specifications. With a few additions, we can easily have our process restarted when memory usage or CPU goes above certain limits. Edit your `sample.god` config file to look like this: ```ruby God.watch do |w| w.name = "simple" w.start = "ruby /full/path/to/simple.rb" w.keepalive(:memory_max => 150.megabytes, :cpu_max => 50.percent) end ``` Here I've specified a `:memory_max` option to the `keepalive` command. Now if the process memory usage goes above 150 megabytes, god will restart it. Similarly, by setting the `:cpu_max`, god will restart my process if its CPU usage goes over 50%. By default these properties will be checked every 30 seconds and will be acted upon if there is an overage for three out of any five checks. This prevents the process from getting restarted for temporary resource spikes. To test this out, modify your `simple.rb` server script to introduce a memory leak: ```ruby data = '' loop do puts 'Hello' 100000.times { data << 'x' } end ``` Ctrl-C out of the foregrounded god instance. Notice that your current `simple` server will continue to run. Start god again with the same command as before. Now instead of starting the `simple` process, it will notice that one is already running and simply switch to the `up` state. ```terminal # Events I [2011-12-10 15:36:00] INFO: Loading simple.god I [2011-12-10 15:36:00] INFO: Syslog enabled. I [2011-12-10 15:36:00] INFO: Using pid file directory: /Users/tom/.god/pids I [2011-12-10 15:36:00] INFO: Started on drbunix:///tmp/god.17165.sock I [2011-12-10 15:36:00] INFO: simple move 'unmonitored' to 'init' I [2011-12-10 15:36:00] INFO: simple moved 'unmonitored' to 'init' I [2011-12-10 15:36:00] INFO: simple [trigger] process is running (ProcessRunning) I [2011-12-10 15:36:00] INFO: simple move 'init' to 'up' I [2011-12-10 15:36:00] INFO: simple registered 'proc_exit' event for pid 23601 I [2011-12-10 15:36:00] INFO: simple moved 'init' to 'up' # Polls I [2011-12-07 14:50:46] INFO: Loading simple.god I [2011-12-07 14:50:46] INFO: Syslog enabled. I [2011-12-07 14:50:46] INFO: Using pid file directory: /Users/tom/.god/pids I [2011-12-07 14:50:47] INFO: Started on drbunix:///tmp/god.17165.sock I [2011-12-07 14:50:47] INFO: simple move 'unmonitored' to 'up' I [2011-12-07 14:50:47] INFO: simple moved 'unmonitored' to 'up' I [2011-12-07 14:50:47] INFO: simple [ok] process is running (ProcessRunning) ``` In order to get our new `simple` server running, we can issue a command to god to have our process restarted: ```terminal $ god restart simple ``` From the logs you can see god killing and restarting the process: ```terminal # Events I [2011-12-10 15:38:13] INFO: simple move 'up' to 'restart' I [2011-12-10 15:38:13] INFO: simple deregistered 'proc_exit' event for pid 23601 I [2011-12-10 15:38:13] INFO: simple stop: default lambda killer I [2011-12-10 15:38:13] INFO: simple sent SIGTERM I [2011-12-10 15:38:14] INFO: simple process stopped I [2011-12-10 15:38:14] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-10 15:38:14] INFO: simple moved 'up' to 'restart' I [2011-12-10 15:38:14] INFO: simple [trigger] process is running (ProcessRunning) I [2011-12-10 15:38:14] INFO: simple move 'restart' to 'up' I [2011-12-10 15:38:14] INFO: simple registered 'proc_exit' event for pid 23707 I [2011-12-10 15:38:14] INFO: simple moved 'restart' to 'up' # Polls I [2011-12-07 14:51:13] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 14:51:13] INFO: simple move 'up' to 'restart' I [2011-12-07 14:51:13] INFO: simple stop: default lambda killer I [2011-12-07 14:51:13] INFO: simple sent SIGTERM I [2011-12-07 14:51:14] INFO: simple process stopped I [2011-12-07 14:51:14] INFO: simple start: ruby /Users/tom/dev/mojombo/god/simple.rb I [2011-12-07 14:51:14] INFO: simple moved 'up' to 'up' I [2011-12-07 14:51:14] INFO: simple [ok] process is running (ProcessRunning) ``` God will now start reporting on memory and CPU utilization of your process: ```terminal # Events and Polls I [2011-12-07 14:54:37] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 14:54:37] INFO: simple [ok] memory within bounds [2032kb] (MemoryUsage) I [2011-12-07 14:54:37] INFO: simple [ok] cpu within bounds [0.0%%] (CpuUsage) I [2011-12-07 14:54:42] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 14:54:42] INFO: simple [ok] memory within bounds [2032kb, 13492kb] (MemoryUsage) I [2011-12-07 14:54:42] INFO: simple [ok] cpu within bounds [0.0%%, *99.7%%] (CpuUsage) I [2011-12-07 14:54:47] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 14:54:47] INFO: simple [ok] memory within bounds [2032kb, 13492kb, 25568kb] (MemoryUsage) I [2011-12-07 14:54:47] INFO: simple [ok] cpu within bounds [0.0%%, *99.7%%, *100.0%%] (CpuUsage) I [2011-12-07 14:54:52] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 14:54:52] INFO: simple [ok] memory within bounds [2032kb, 13492kb, 25568kb, 37556kb] (MemoryUsage) I [2011-12-07 14:54:52] INFO: simple [trigger] cpu out of bounds [0.0%%, *99.7%%, *100.0%%, *98.4%%] (CpuUsage) I [2011-12-07 14:54:52] INFO: simple move 'up' to 'restart' ``` On the last line of the above log you can see that CPU usage has gone above 50% for three cycles and god will issue a restart operation. God will continue to monitor the `simple` process for as long as god is running and the process is set to be monitored. Now, before you kill the god process, let's kill the `simple` server by asking god to stop it for us. In a new terminal, issue the command: ```terminal $ god stop simple ``` You should see the following output: ```terminal Sending 'stop' command The following watches were affected: simple ``` And in the foregrounded god terminal window, you'll see the log of what happened: ```terminal # Events I [2011-12-10 15:41:04] INFO: simple stop: default lambda killer I [2011-12-10 15:41:04] INFO: simple sent SIGTERM I [2011-12-10 15:41:05] INFO: simple process stopped I [2011-12-10 15:41:05] INFO: simple move 'up' to 'unmonitored' I [2011-12-10 15:41:05] INFO: simple deregistered 'proc_exit' event for pid 23707 I [2011-12-10 15:41:05] INFO: simple moved 'up' to 'unmonitored' # Polls I [2011-12-07 09:59:59] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 10:00:04] INFO: simple [ok] process is running (ProcessRunning) I [2011-12-07 10:00:07] INFO: simple stop: default lambda killer I [2011-12-07 10:00:07] INFO: simple sent SIGTERM I [2011-12-07 10:00:08] INFO: simple process stopped I [2011-12-07 10:00:08] INFO: simple move 'up' to 'unmonitored' I [2011-12-07 10:00:08] INFO: simple moved 'up' to 'unmonitored' ``` Now feel free to Ctrl-C out of god. Congratulations! You've just taken god for a test ride and seen how easy it is to keep your processes running. This is just the beginning of what god can do, and in reality, the `keepalive` command is a convenience method written using more advanced transitional and condition constructs that may be used directly. You can configure many different kinds of conditions to have your process restarted when memory or CPU are too high, when disk usage is above a threshold, when a process returns an HTTP error code on a specific URL, and many more. In addition you can write your own custom conditions and use them in your configuration files. Many different lifecycle controls are available alongside a sophisticated and extensible notifications system. Keep reading to find out what makes god different from other monitoring systems and how it can help you solve many of your process monitoring and control problems. Config Files are Ruby Code! --------------------------- Now that you've seen how to get started quickly, let's see how to use the more powerful aspects of god. Once again, the best way to learn will be through an example. The following configuration file is what I once used at gravatar.com to keep the mongrels running: ```ruby RAILS_ROOT = "/Users/tom/dev/gravatar2" %w{8200 8201 8202}.each do |port| God.watch do |w| w.name = "gravatar2-mongrel-#{port}" 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" w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") w.behavior(:clean_pid_file) w.start_if do |start| start.condition(:process_running) do |c| c.interval = 5.seconds 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| c.to_state = [:start, :restart] c.times = 5 c.within = 5.minute c.transition = :unmonitored c.retry_in = 10.minutes c.retry_times = 5 c.retry_within = 2.hours end end end end ``` That's a lot to take in at once, so I'll break it down by section and explain what's going on in each. ```ruby RAILS_ROOT = "/var/www/gravatar2/current" ``` Here I've set a constant that is used throughout the file. Keeping the `RAILS_ROOT` value in a constant makes it easy to adapt this script to other applications. Because the config file is Ruby code, I can set whatever variables or constants I want that make the configuration more concise and easier to work with. ```ruby %w{8200 8201 8202}.each do |port| ... end ``` Because the config file is written in actual Ruby code, we can construct loops and do other intelligent things that are impossible in your every day, run of the mill config file. I need to watch three mongrels, so I simply loop over their port numbers, eliminating duplication and making my life a whole lot easier. ```ruby God.watch do |w| w.name = "gravatar2-mongrel-#{port}" 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" w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" 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 `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. If the process you're watching runs as a daemon (as mine does), you'll need to set the `pid_file` attribute. ```ruby 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. ```ruby w.start_if do |start| start.condition(:process_running) do |c| c.interval = 5.seconds 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 `: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). ```ruby 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 ... end ``` Similar to `start_if` there is a `restart_if` command that groups conditions that should trigger a restart. The `memory_usage` condition will fail if the specified process is using too much memory. The maximum allowable amount of memory is specified with the `above` attribute (you can use the `kilobytes`, `megabytes`, or `gigabytes` helpers). The number of times the test needs to fail in order to trigger a restart is set with `times`. This can be either an integer or an array. An integer means it must fail that many times in a row while an array `[x, y]` means it must fail `x` times out of the last `y` tests. ```ruby 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. ```ruby w.lifecycle do |on| on.condition(:flapping) do |c| c.to_state = [:start, :restart] c.times = 5 c.within = 5.minute c.transition = :unmonitored c.retry_in = 10.minutes c.retry_times = 5 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). 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! ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Starting and Controlling God ---------------------------- To start the god monitoring process as a daemon simply run the `god` executable passing in the path to the config file (you need to sudo if you're using events on Linux or want to use the setuid/setgid functionality): ```terminal $ sudo god -c /path/to/config.god ``` While you're writing your config file, it can be helpful to run god in the foreground so you can see the log messages. You can do that with: ```terminal $ sudo god -c /path/to/config.god -D ``` You can start/restart/stop/monitor/unmonitor your Watches with the same utility like so: ```terminal $ sudo god stop gravatar2-mongrel-8200 ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Watching Non-Daemon Processes ----------------------------- Need to watch a script that doesn't have built in daemonization? No problem! God will daemonize and keep track of your process for you. If you don't specify a `pid_file` attribute for a watch, it will be auto-daemonized and a PID file will be stored for it in `/var/run/god`. ```ruby God.pid_file_directory = '/home/tom/pids' # Watcher that auto-daemonizes and creates the pid file God.watch do |w| w.name = 'mongrel' w.pid_file = w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") w.start = "mongrel_rails start -P #{RAILS_ROOT}/log/mongrel.pid -d" # ... end # Watcher that does not auto-daemonize God.watch do |w| w.name = 'worker' # w.pid_file = is not set w.start = "rake resque:worker" # ... end ``` If you'd rather have the PID file stored in a different location, you can set it at the top of your config: ```ruby God.pid_file_directory = '/home/tom/pids' ``` The directory you specify must be writable by god. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Grouping Watches ---------------- Watches can be assigned to groups. These groups can then be controlled together from the command line. ```ruby 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: ```terminal $ sudo god stop mongrels ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Invoke Commands for all watches ------------------------------- If you need to invoke a command (e.g. Stop / Start / Restart) on all watches you can simply omit the second parameter. For example, to start all watches: ```terminal $ sudo god start ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Redirecting STDOUT and STDERR of your Process --------------------------------------------- By default, the STDOUT stream for your process is redirected to `/dev/null`. To get access to this output, you can redirect the stream either to a file or to a command. To redirect STDOUT to a file, set the `log` attribute to a file path. The file will be written in append mode and created if it does not exist. ```ruby God.watch do |w| ... w.log = '/var/log/myprocess.log' ... end ``` To redirect STDOUT to a command that will be run for you, set the `log_cmd` attribute to a command. ```ruby God.watch do |w| ... w.log_cmd = '/usr/bin/logger' ... end ``` By default, STDERR is redirected to STDOUT. You can redirect it to a file or a command just like STDOUT by setting the `err_log` or `err_log_cmd` attributes respectively. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Changing UID/GID for processes ------------------------------ It is possible to have god run your start/stop/restart commands as a specific user/group. This can be done by setting the `uid` and/or `gid` attributes of a watch. ```ruby God.watch do |w| ... w.uid = 'tom' w.gid = 'devs' ... end ``` This only works for commands specified as a string. Lambda commands are unaffected. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Setting the Working Directory ----------------------------- By default, God sets the working directory to `/` before running your process. You can change this by setting the `dir` attribute on the watch. ```ruby God.watch do |w| ... w.dir = '/var/www/myapp' ... end ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Setting environment variables ----------------------------- You can set any number of environment variables you wish via the `env` attribute of a watch. ```ruby God.watch do |w| ... w.env = { 'RAILS_ROOT' => "/var/www/myapp", 'RAILS_ENV' => "production" } ... end ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Using chroot to Change the File System Root ------------------------------------------- If you want your process to run chrooted, simply use the `chroot` attribute on the watch. The specified directory must exist and have a `/dev/null`. ```ruby God.watch do |w| ... w.chroot = '/var/myroot' ... end ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Lambda commands --------------- In addition to specifying start/stop/restart commands as strings (to be executed via the shell), you can specify a lambda that will be called. ```ruby God.watch do |w| ... w.start = lambda { ENV['APACHE'] ? `apachectl -k graceful` : `lighttpd restart` } ... end ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Customizing the Default Stop Lambda ----------------------------------- If you do not provide a stop command, God will attempt to stop your process by first sending a SIGTERM. It will then wait for ten seconds for the process to exit. If after this time it still has not exited, it will be sent a SIGKILL. You can customize the stop signal and/or the time to wait for the process to exit by setting the `stop_signal` and `stop_timeout` attributes on the watch. ```ruby God.watch do |w| ... w.stop_signal = 'QUIT' w.stop_timeout = 20.seconds ... end ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Loading Other Config Files -------------------------- You should feel free to separate your god configs into separate files for easier organization. You can load in other configs using Ruby's normal `load` method, or use the convenience method `God.load` which allows for glob-style paths: ```ruby # load in all god configs God.load "/usr/local/conf/*.god" ``` God won't start its monitoring operations until all configurations have been loaded. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Dynamically Loading Config Files Into an Already Running God ------------------------------------------------------------ God allows you to load or reload configurations into an already running instance. There are a few things to consider when doing this: * Existng Watches with the same `name` as the incoming Watches will be overidden by the new config. * All paths must be either absolute or relative to the path from which god was started. To load a config into a running god, issue the following command: ```terminal $ sudo god load path/to/config.god ``` Config files that are loaded dynamically can contain anything that a normal config file contains, however, global options such as `God.pid_file_directory` blocks will be ignored (and produce a warning in the logs). ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Getting Logs for a Single Watch ------------------------------- Sifting through the god logs for statements specific to a single Watch can be frustrating when you have many of them. You can get the realtime logs for a single Watch via the command line: ```terminal $ sudo god log local-3000 ``` This will display log output for the 'local-3000' Watch and update every second with new log messages. You can also supply a shorthand to the log command that will match one of your watches. If it happens to match several, the shortest match will be used: ```terminal $ sudo god log l3 ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Notifications ------------- God has an extensible notification framework built in that makes it easy to have notifications sent when conditions are triggered. Each notification type has a set of configuration parameters that must be set. These parameters may be set globally via Contact Defaults or individually via Contact Instances. *Contact Defaults* - Some parameters are unlikely to change on a per-contact basis. You should set those parameters via the defaults mechanism. ```ruby God::Contacts::Email.defaults do |d| d.from_email = 'god@example.com' d.from_name = 'God' d.delivery_method = :sendmail end ``` *Contact Instances* - Each contact must have a unique `name` set. You may optionally assign each contact to a `group`. ```ruby God.contact(:email) do |c| c.name = 'tom' c.group = 'developers' c.to_email = 'tom@example.com' end God.contact(:email) do |c| c.name = 'vanpelt' c.group = 'developers' c.to_email = 'vanpelt@example.com' end God.contact(:email) do |c| c.name = 'kevin' c.group = 'developers' c.to_email = 'kevin@example.com' end ``` *Condition Attachment* - To have a specific contact notified when a condition is triggered, simply set the condition's `notify` attribute to the name of the individual contact. ```ruby w.transition(:up, :start) do |on| on.condition(:process_exits) do |c| 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. ```ruby 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. ``` From: God <god@example.com> To: tom <tom@example.com> Subject: [god] mongrel-8600 [trigger] process exited (ProcessExits) Message: mongrel-8600 [trigger] process exited (ProcessExits) Host: candymountain.example.com Priority: 1 Category: product ``` Available Notification Types ---------------------------- Campfire ~~~~~~~~ Send a notice to a Campfire room (http://campfirenow.com). ```ruby God::Contacts::Campfire.defaults do |d| ... end God.contact(:campfire) do |c| ... end ``` ``` subdomain - The String subdomain of the Campfire account. If your URL is "foo.campfirenow.com" then your subdomain is "foo". token - The String token used for authentication. room - The String room name to which the message should be sent. ssl - A Boolean determining whether or not to use SSL (default: false). ``` Email ~~~~~ Send a notice to an email address. ```ruby God::Contacts::Email.defaults do |d| ... end God.contact(:email) do |c| ... end ``` ``` to_email - The String email address to which the email will be sent. to_name - The String name corresponding to the recipient. from_email - The String email address from which the email will be sent. from_name - The String name corresponding to the sender. delivery_method - The Symbol delivery method. [ :smtp | :sendmail ] (default: :smtp). === SMTP Options (when delivery_method = :smtp) === server_host - The String hostname of the SMTP server (default: localhost). server_port - The Integer port of the SMTP server (default: 25). server_auth - A Boolean or Symbol, false if no authentication else a symbol for the type of authentication [false | :plain | :login | :cram_md5] (default: false). === SMTP Auth Options (when server_auth = true) === server_domain - The String domain. server_user - The String username. server_password - The String password. === Sendmail Options (when delivery_method = :sendmail) === sendmail_path - The String path to the sendmail executable (default: "/usr/sbin/sendmail"). sendmail_args - The String args to send to sendmail (default "-i -t"). ``` Jabber ~~~~~~ Send a notice to a Jabber address (http://jabber.org/). Google Mail addresses should work. If you need a non-Gmail address, you can sign up for one at http://register.jabber.org/. ```ruby God::Contacts::Jabber.defaults do |d| ... end God.contact(:jabber) do |c| ... end ``` ``` host - The String hostname of the Jabber server. port - The Integer port of the Jabber server. from_jid - The String Jabber ID of the sender. password - The String password of the sender. to_jid - The String Jabber ID of the recipient. subject - The String subject of the message (default: "God Notification"). ``` Prowl ~~~~~ Send a notice to Prowl (http://prowl.weks.net/). ```ruby God::Contacts::Prowl.defaults do |d| ... end God.contact(:prowl) do |c| ... end ``` ``` apikey - The String API key. ``` Scout ~~~~~ Send a notice to Scout (http://scoutapp.com/). ```ruby God::Contacts::Scout.defaults do |d| ... end God.contact(:scout) do |c| ... end ``` ``` client_key - The String client key. plugin_id - The String plugin id. ``` Twitter ~~~~~~~ Send a notice to a Twitter account (http://twitter.com/). In order to use the Twitter notification, you will need to authorize God via OAuth and then get the OAuth token and secret for your account. The easiest way to do this is with a Ruby gem called `twurl`. Install it like so: ```terminal [sudo] gem install twurl ``` Then, run the following: ```terminal twurl auth --consumer-key gOhjax6s0L3mLeaTtBWPw \ --consumer-secret yz4gpAVXJHKxvsGK85tEyzQJ7o2FEy27H1KEWL75jfA ``` This will return a URL. Copy it to your clipboard. Make sure you are logged into Twitter with the account that will used for the notifications, and then paste the URL into a new browser window. At the end of the authentication process, you will be given a PIN. Copy this PIN and paste it back to the command line prompt. Once this is complete, you need to find your access token and secret: ```terminal cat ~/.twurlrc ``` This will output the contents of the config file from which you can grab your access token and secret: ``` --- profiles: mojombo: gOhjax6s0L3mLeaTtBWPw: [red]token: 17376380-KXA91nCrgaQ4HxUXMmZtM38gB56qS3hx1NYbjT6mQ consumer_key: gOhjax6s0L3mLeaTtBWPw username: mojombo consumer_secret: yz4gpAVXJHKxvsGK85tEyzQJ7o2FEy27H1KEWL75jfA [red]secret: EBWFQBCtuMwCDeU4OXlc3LwGyY8OdWAV0Jg5KVB0 configuration: default_profile: - mojombo - gOhjax6s0L3mLeaTtBWPw ``` The access token and secret (highlighted in red above) are what you need to use as parameters to the Twitter notification. ```ruby God::Contacts::Twitter.defaults do |d| ... end God.contact(:twitter) do |c| ... end ``` ``` consumer_token - The String OAuth consumer token (defaults to God's existing consumer token). consumer_secret - The String OAuth consumer secret (defaults to God's existing consumer secret). access_token - The String OAuth access token. access_secret - The String OAuth access secret. ``` Webhook ~~~~~~~ Send a notice to a webhook (http://www.webhooks.org/). ```ruby God::Contacts::Webhook.defaults do |d| ... end God.contact(:webhook) do |c| ... end ``` ``` url - The String webhook URL. format - The Symbol format [ :form | :json ] (default: :form). ``` Airbrake ~~~~~~~ Send a notice to airbrake (http://airbrake.io/). ```ruby God::Contacts::Airbrake.defaults do |d| ... end God.contact(:airbrake) do |c| ... end ``` ``` apikey - The String API key. ``` ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Advanced Configuration with Transitions and Events -------------------------------------------------- So far you've been introduced to a simple poll-based config file and seen how to run it. Poll-based monitoring works great for simple things, but falls short for highly critical tasks. God has native support for kqueue/netlink events on BSD/Darwin/Linux systems. For instance, instead of using the `process_running` condition to poll for the status of your process, you can use the `process_exits` condition that will be notified *immediately* upon the exit of your process. This means less load on your system and shorter downtime after a crash. While the configuration syntax you saw in the previous example is very simple, it lacks the power that we need to deal with event based monitoring. In fact, the `start_if` and `restart_if` methods are really just calling out to a lower-level API. If we use the low-level API directly, we can harness the full power of god's event based lifecycle system. Let's look at another example config file. ```ruby RAILS_ROOT = "/Users/tom/dev/gravatar2" God.watch do |w| w.name = "local-3000" w.start = "mongrel_rails start -c #{RAILS_ROOT} -P #{RAILS_ROOT}/log/mongrel.pid -p 3000 -d" 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 c.transition = :start end end # start if process is not running 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| c.interval = 20 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| c.to_state = [:start, :restart] c.times = 5 c.within = 5.minute c.transition = :unmonitored c.retry_in = 10.minutes c.retry_times = 5 c.retry_within = 2.hours end end end ``` 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. ```ruby # 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 ``` The first transition block tells god what to do when the Watch is in the `init` state (first argument). This is where I tell god how to determine if my task is already running. Since I'm monitoring a process, I can use the `process_running` condition to determine whether the process is running. If the process is running, it will return true, otherwise it will return false. Since I sent a hash as the second argument to `transition`, the return from `process_running` will determine which of the two states will be transitioned to. If the process is running, the return is true and god will put the Watch into the `up` state. If the process is not running, the return is false and god will put the Watch into the `start` state. ```ruby # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end ... end ``` If god has determined that my process isn't running, the Watch will be put into the `start` state. Upon entering this state, the `start` command that I specified on the Watch will be called. In addition, the above transition specifies a condition that should be enabled when in either the `start` or `restart` states. The condition is another `process_running`, however this time I'm only interested in moving to another state once it returns `true`. A `true` return from this condition means that the process is running and it's ok to transition to the `up` state (second argument to `transition`). ```ruby # determine when process has finished starting w.transition([:start, :restart], :up) do |on| ... # failsafe on.condition(:tries) do |c| c.times = 5 c.transition = :start end end ``` The other half of this transition uses the `tries` condition to ensure that god doesn't get stuck in this state. It's possible that the process could go down while the transition is being made, in which case god would end up polling forever to see if the process is up. Here I've specified that if this condition is called five times, god should override the normal transition destination and move to the `start` state instead. If you specify a `transition` attribute on any condition, that state will be transferred to instead of the normal transfer destination. ```ruby # start if process is not running w.transition(:up, :start) do |on| on.condition(:process_exits) end ``` This is where the event based system comes into play. Once in the `up` state, I want to be notified when my process exits. The `process_exits` condition registers a callback that will trigger a transition change when it is fired off. Event conditions (like this one) cannot be used in transitions that have a hash for the second argument (as they do not return true or false). ```ruby # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| c.interval = 20 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 ``` Notice that I can have multiple transitions with the same start state. In this case, I want to have the `memory_usage` and `cpu_usage` poll conditions going at the same time that I listen for the process exit event. In the case of runaway CPU or memory usage, however, I want to transition to the `restart` state. When a Watch enters the `restart` state it will either call the `restart` command that you specified, or if none has been set, call the `stop` and then `start` commands. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Extend God with your own Conditions ----------------------------------- God was designed from the start to allow you to easily write your own custom conditions, making it simple to add tests that are application specific. ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// Contribute ---------- If you'd like to hack on god itself or contribute fixes or new functionality, read this section. The codebase can be found at https://github.com/mojombo/god. To get started, fork god on GitHub into your own account and then pull that down to your local machine. This way you can easily submit changes via Pull Requests later on. ```terminal $ git clone git@github.com:yourusername/god ``` We recommend using link:https://github.com/sstephenson/rbenv[rbenv] and link:https://github.com/sstephenson/ruby-build[ruby-build] to manage multiple versions of Ruby and their separate gemsets. Any changes to god must work on both Ruby 1.8.7-p352 and 1.9.3-p0. God uses link:http://gembundler.com/[bundler] to deal with development dependencies. Once you have the code locally, you can pull in all the dependencies like so: ```terminal $ cd god $ bundle install ``` In order for process events to function during development you'll need to compile the C extensions: ```terminal $ cd ext/god $ ruby extconf.rb $ make $ cd ../.. ``` Now you're ready to run the tests and make sure everything is configured properly. On Linux you'll need to run the tests as root in order for the events system to load. On MacOS there is no need to run the tests as root. ```terminal $ [sudo] bundle exec rake ``` To run your development god to make sure config files and such still work properly, just run: ```terminal $ [sudo] bundle exec god -c myconfig.god -D ``` There are a bunch of example config files for various scenarios in `test/configs` that you can try out. For big new features, it's great to add a new test config showing off the usage of the feature. If you intend to contribute your changes back to god core, make sure you create a new branch and do your work there. Then, when your changes are ready to be shared with the world, push them to your fork and issue a Pull Request against mojombo/god. Make sure to describe your changes in detail and add relevant tests. Any feature additions or changes should be accompanied by corresponding updates to the documentation. It can be found in the `docs` directory. The documentation is done in link:http://github.com/github/gollum[Gollum] format and then converted into the public site at http://godrb.com. To see the generated site locally you'll first need to commit your changes to git and then issue the following: ```terminal $ bundle exec rake site ``` This will open the site in your browser so you can check for correctness. ruby-god-0.13.3/doc/intro.asciidoc000066400000000000000000000012701226052465500167350ustar00rootroot00000000000000God: The Ruby Framework for Process Management ============================================== Tom Preston-Werner 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 monitoring application available. Features -------- * Config file is written in Ruby * Easily write your own custom conditions in Ruby * Supports both poll and event based conditions * Different poll conditions can have different intervals * Integrated notification system (write your own too!) * Easily control non-daemonizing scripts ruby-god-0.13.3/ext/000077500000000000000000000000001226052465500141355ustar00rootroot00000000000000ruby-god-0.13.3/ext/god/000077500000000000000000000000001226052465500147065ustar00rootroot00000000000000ruby-god-0.13.3/ext/god/.gitignore000066400000000000000000000001411226052465500166720ustar00rootroot00000000000000Makefile kqueue_handler_ext.bundle kqueue_handler.o netlink_handler_ext.bundle netlink_handler.o ruby-god-0.13.3/ext/god/extconf.rb000066400000000000000000000027241226052465500167060ustar00rootroot00000000000000require 'mkmf' fail = false def create_dummy_makefile File.open("Makefile", 'w') do |f| f.puts "all:" f.puts "install:" end end case RUBY_PLATFORM when /bsd/i, /darwin/i unless have_header('sys/event.h') puts 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." create_dummy_makefile else create_makefile 'kqueue_handler_ext' end when /linux/i unless have_header('linux/netlink.h') puts puts "Missing 'linux/netlink.h' header(s)" 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)" puts "These are only available in Linux kernel 2.6.15 and later (run `uname -a` to find yours)" 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." create_dummy_makefile else create_makefile 'netlink_handler_ext' end else puts puts "Unsupported platform '#{RUBY_PLATFORM}'. Supported platforms are BSD, DARWIN, and LINUX." puts "Your god installation will not support event conditions." create_dummy_makefile end ruby-god-0.13.3/ext/god/kqueue_handler.c000066400000000000000000000060151226052465500200500ustar00rootroot00000000000000#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) #include #include #include #include static VALUE mGod; static VALUE cKQueueHandler; static VALUE cEventHandler; static ID proc_exit; static ID proc_fork; static ID m_call; static ID m_size; static ID m_deregister; static int kq; int num_events; #define NUM_EVENTS FIX2INT(rb_funcall(rb_cv_get(cEventHandler, "@@actions"), m_size, 0)) VALUE kqh_event_mask(VALUE klass, VALUE sym) { ID id = SYM2ID(sym); if (proc_exit == id) { return UINT2NUM(NOTE_EXIT); } else if (proc_fork == id) { return UINT2NUM(NOTE_FORK); } 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; (void)event; //!< Silence warning about unused var, should be removed? 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, "%s", strerror(errno)); } num_events = NUM_EVENTS; return Qnil; } VALUE 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); // 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, "%s", strerror(errno)); } nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL); if (-1 == nevents) { free(events); rb_raise(rb_eStandardError, "%s", strerror(errno)); } else { for (i = 0; i < nevents; i++) { if (events[i].fflags & NOTE_EXIT) { rb_funcall(cEventHandler, m_call, 2, INT2NUM(events[i].ident), ID2SYM(proc_exit)); } else if (events[i].fflags & NOTE_FORK) { rb_funcall(cEventHandler, m_call, 2, INT2NUM(events[i].ident), ID2SYM(proc_fork)); } } } free(events); return INT2FIX(nevents); } 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); rb_define_singleton_method(cKQueueHandler, "monitor_process", kqh_monitor_process, 2); rb_define_singleton_method(cKQueueHandler, "handle_events", kqh_handle_events, 0); rb_define_singleton_method(cKQueueHandler, "event_mask", kqh_event_mask, 1); } #endif ruby-god-0.13.3/ext/god/netlink_handler.c000066400000000000000000000115271226052465500202210ustar00rootroot00000000000000#ifdef __linux__ /* only build on linux */ #include #include #include #include #include #include #define _LINUX_TIME_H #include #include static VALUE mGod; static VALUE cNetlinkHandler; static VALUE cEventHandler; static ID proc_exit; static ID proc_fork; static ID m_call; static ID m_watching_pid; static int nl_sock; /* socket for netlink connection */ VALUE 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, "%s", 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 (-1 == recv(nl_sock, buff, sizeof(buff), 0)) { rb_raise(rb_eStandardError, "%s", strerror(errno)); } hdr = (struct nlmsghdr *)buff; if (NLMSG_ERROR == hdr->nlmsg_type) { rb_raise(rb_eStandardError, "%s", strerror(errno)); } 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)); rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_signal")), INT2FIX(event->event_data.exit.exit_signal)); rb_hash_aset(extra_data, ID2SYM(rb_intern("thread_group_id")), INT2FIX(event->event_data.exit.process_tgid)); 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, ID2SYM(rb_intern("parent_pid")), INT2FIX(event->event_data.fork.parent_pid)); rb_hash_aset(extra_data, ID2SYM(rb_intern("parent_thread_group_id")), INT2FIX(event->event_data.fork.parent_tgid)); rb_hash_aset(extra_data, ID2SYM(rb_intern("child_pid")), INT2FIX(event->event_data.fork.child_pid)); rb_hash_aset(extra_data, ID2SYM(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); default: break; } } return Qnil; } #define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \ sizeof(int)) void connect_to_netlink() { struct sockaddr_nl sa_nl; /* netlink interface info */ 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, "%s", 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, "%s", strerror(errno)); } /* Fill header */ hdr = (struct nlmsghdr *)buff; hdr->nlmsg_len = NL_MESSAGE_SIZE; hdr->nlmsg_type = NLMSG_DONE; 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 */ msg->id.val = CN_VAL_PROC; msg->seq = 0; msg->ack = 0; 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, "%s", strerror(errno)); } } 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(); } #endif ruby-god-0.13.3/god.gemspec000066400000000000000000000132201226052465500154510ustar00rootroot00000000000000Gem::Specification.new do |s| s.specification_version = 2 if s.respond_to? :specification_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.name = 'god' s.version = '0.13.3' s.date = '2013-09-25' s.summary = "Process monitoring framework." s.description = "An easy to configure, easy to extend monitoring framework written in Ruby." s.authors = ["Tom Preston-Werner", "Kevin Clark", "Eric Lindvall"] s.email = 'god-rb@googlegroups.com' s.homepage = 'http://god.rubyforge.org/' s.rubyforge_project = 'god' s.rubygems_version = '1.3.5' s.require_paths = %w[lib ext] s.executables = ["god"] s.extensions = %w[ext/god/extconf.rb] s.rdoc_options = ["--charset=UTF-8"] s.extra_rdoc_files = %w[README.md] s.add_development_dependency('json', '~> 1.6') s.add_development_dependency('rake') s.add_development_dependency('rdoc', '~> 3.10') s.add_development_dependency('twitter', '~> 4.0') s.add_development_dependency('prowly', '~> 0.3') s.add_development_dependency('xmpp4r', '~> 0.5') s.add_development_dependency('dike', '~> 0.0.3') s.add_development_dependency('rcov', '~> 0.9') s.add_development_dependency('daemons', '~> 1.1') s.add_development_dependency('mocha', '~> 0.10') s.add_development_dependency('gollum', '~> 1.3.1') s.add_development_dependency('airbrake', '~> 3.1.7') s.add_development_dependency('nokogiri', '~> 1.5.0') s.add_development_dependency('activesupport', [ '>= 2.3.10', '< 4.0.0' ]) # = MANIFEST = s.files = %w[ Announce.txt Gemfile History.txt LICENSE README.md Rakefile bin/god doc/god.asciidoc doc/intro.asciidoc ext/god/.gitignore ext/god/extconf.rb ext/god/kqueue_handler.c ext/god/netlink_handler.c god.gemspec lib/god.rb lib/god/behavior.rb lib/god/behaviors/clean_pid_file.rb lib/god/behaviors/clean_unix_socket.rb lib/god/behaviors/notify_when_flapping.rb lib/god/cli/command.rb lib/god/cli/run.rb lib/god/cli/version.rb lib/god/compat19.rb lib/god/condition.rb lib/god/conditions/always.rb lib/god/conditions/complex.rb lib/god/conditions/cpu_usage.rb lib/god/conditions/degrading_lambda.rb lib/god/conditions/disk_usage.rb lib/god/conditions/file_mtime.rb lib/god/conditions/file_touched.rb lib/god/conditions/flapping.rb lib/god/conditions/http_response_code.rb lib/god/conditions/lambda.rb lib/god/conditions/memory_usage.rb lib/god/conditions/process_exits.rb lib/god/conditions/process_running.rb lib/god/conditions/socket_responding.rb lib/god/conditions/tries.rb lib/god/configurable.rb lib/god/contact.rb lib/god/contacts/airbrake.rb lib/god/contacts/campfire.rb lib/god/contacts/email.rb lib/god/contacts/jabber.rb lib/god/contacts/prowl.rb lib/god/contacts/scout.rb lib/god/contacts/twitter.rb lib/god/contacts/webhook.rb lib/god/driver.rb lib/god/errors.rb lib/god/event_handler.rb lib/god/event_handlers/dummy_handler.rb lib/god/event_handlers/kqueue_handler.rb lib/god/event_handlers/netlink_handler.rb lib/god/logger.rb lib/god/metric.rb lib/god/process.rb lib/god/registry.rb lib/god/simple_logger.rb lib/god/socket.rb lib/god/sugar.rb lib/god/sys_logger.rb lib/god/system/portable_poller.rb lib/god/system/process.rb lib/god/system/slash_proc_poller.rb lib/god/task.rb lib/god/timeline.rb lib/god/trigger.rb lib/god/watch.rb test/configs/child_events/child_events.god test/configs/child_events/simple_server.rb test/configs/child_polls/child_polls.god test/configs/child_polls/simple_server.rb test/configs/complex/complex.god test/configs/complex/simple_server.rb test/configs/contact/contact.god test/configs/contact/simple_server.rb test/configs/daemon_events/daemon_events.god test/configs/daemon_events/simple_server.rb test/configs/daemon_events/simple_server_stop.rb test/configs/daemon_polls/daemon_polls.god test/configs/daemon_polls/simple_server.rb test/configs/degrading_lambda/degrading_lambda.god test/configs/degrading_lambda/tcp_server.rb test/configs/keepalive/keepalive.god test/configs/keepalive/keepalive.rb test/configs/lifecycle/lifecycle.god test/configs/matias/matias.god test/configs/real.rb test/configs/running_load/running_load.god test/configs/stop_options/simple_server.rb test/configs/stop_options/stop_options.god test/configs/stress/simple_server.rb test/configs/stress/stress.god test/configs/task/logs/.placeholder test/configs/task/task.god test/configs/test.rb test/helper.rb test/suite.rb test/test_airbrake.rb test/test_behavior.rb test/test_campfire.rb test/test_condition.rb test/test_conditions_disk_usage.rb test/test_conditions_http_response_code.rb test/test_conditions_process_running.rb test/test_conditions_socket_responding.rb test/test_conditions_tries.rb test/test_contact.rb test/test_driver.rb test/test_email.rb test/test_event_handler.rb test/test_god.rb test/test_handlers_kqueue_handler.rb test/test_jabber.rb test/test_logger.rb test/test_metric.rb test/test_process.rb test/test_prowl.rb test/test_registry.rb test/test_socket.rb test/test_sugar.rb test/test_system_portable_poller.rb test/test_system_process.rb test/test_task.rb test/test_timeline.rb test/test_trigger.rb test/test_watch.rb test/test_webhook.rb ] # = MANIFEST = s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ } end ruby-god-0.13.3/lib/000077500000000000000000000000001226052465500141035ustar00rootroot00000000000000ruby-god-0.13.3/lib/god.rb000066400000000000000000000501631226052465500152060ustar00rootroot00000000000000# Bail out before loading anything unless this flag is set. # # We are doing this to guard against bundler autoloading because there is # no value in loading god in most processes. if $load_god # core require 'stringio' require 'fileutils' begin require 'fastthread' rescue LoadError ensure require 'thread' end # stdlib # internal requires require 'god/errors' require 'god/simple_logger' require 'god/logger' require 'god/sugar' require 'god/system/process' require 'god/system/portable_poller' require 'god/system/slash_proc_poller' require 'god/timeline' require 'god/configurable' require 'god/task' require 'god/behavior' require 'god/behaviors/clean_pid_file' require 'god/behaviors/clean_unix_socket' require 'god/behaviors/notify_when_flapping' require 'god/condition' require 'god/conditions/process_running' require 'god/conditions/process_exits' require 'god/conditions/tries' require 'god/conditions/memory_usage' require 'god/conditions/cpu_usage' require 'god/conditions/always' require 'god/conditions/lambda' require 'god/conditions/degrading_lambda' require 'god/conditions/flapping' require 'god/conditions/http_response_code' require 'god/conditions/disk_usage' require 'god/conditions/complex' require 'god/conditions/file_mtime' require 'god/conditions/file_touched' require 'god/conditions/socket_responding' require 'god/socket' require 'god/driver' require 'god/metric' require 'god/watch' require 'god/trigger' require 'god/event_handler' require 'god/registry' require 'god/process' require 'god/cli/version' require 'god/cli/command' # ruby 1.8 specific configuration if RUBY_VERSION < '1.9' $KCODE = 'u' end CONTACT_DEPS = { } CONTACT_LOAD_SUCCESS = { } def load_contact(name) require "god/contacts/#{name}" CONTACT_LOAD_SUCCESS[name] = true rescue LoadError CONTACT_LOAD_SUCCESS[name] = false end require 'god/contact' load_contact(:campfire) load_contact(:email) load_contact(:jabber) load_contact(:prowl) load_contact(:scout) load_contact(:twitter) load_contact(:webhook) load_contact(:airbrake) $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god]) # App wide logging system LOG = God::Logger.new def applog(watch, level, text) LOG.log(watch, level, text) end # The $run global determines whether god should be started when the # program would normally end. This should be set to true if when god # should be started (e.g. `god -c `) and false otherwise # (e.g. `god status`) $run ||= nil GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) # Return the binding of god's root level def root_binding binding end 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) end end class Module def safe_attr_accessor(*args) args.each do |arg| define_method((arg.to_s + "=").intern) do |other| 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 end end end module God # The String version number for this package. VERSION = '0.13.3' # The Integer number of lines of backlog to keep for the logger. LOG_BUFFER_SIZE_DEFAULT = 100 # An Array of directory paths to be used as the default PID file directory. # This list will be searched in order and the first one that has write # permissions will be used. PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids'] # The default Integer port number for the DRb communcations channel. DRB_PORT_DEFAULT = 17165 # The default Array of String IPs that will allow DRb communication access. DRB_ALLOW_DEFAULT = ['127.0.0.1'] # The default Symbol log level. LOG_LEVEL_DEFAULT = :info # The default Integer number of seconds to wait for god to terminate when # issued the quit command. TERMINATE_TIMEOUT_DEFAULT = 10 # The default Integer number of seconds to wait for a process to terminate. STOP_TIMEOUT_DEFAULT = 10 # The default String signal to send for the stop command. STOP_SIGNAL_DEFAULT = 'TERM' class << self # user configurable safe_attr_accessor :pid, :host, :port, :allow, :log_buffer_size, :pid_file_directory, :log_file, :log_level, :use_events, :terminate_timeout, :socket_user, :socket_group, :socket_perms # internal attr_accessor :inited, :running, :pending_watches, :pending_watch_states, :server, :watches, :groups, :contacts, :contact_groups, :main end # Initialize class instance variables. self.pid = nil self.host = nil self.port = nil self.allow = nil self.log_buffer_size = nil self.pid_file_directory = nil self.log_level = nil self.terminate_timeout = nil self.socket_user = nil self.socket_group = nil self.socket_perms = 0755 # Initialize internal data. # # Returns nothing. def self.internal_init # Only do this once. return if self.inited # Variable init. self.watches = {} self.groups = {} self.pending_watches = [] 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 self.terminate_timeout ||= TERMINATE_TIMEOUT_DEFAULT # Additional setup. self.setup # Log level. log_level_map = {:debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :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. Aborts # on duplicate watch name, invalid watch, or conflicting group name. # # Returns nothing. 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. Aborts # on duplicate task name, invalid task, or conflicting group name. # # Returns nothing. def self.task(klass = Task) # Ensure internal init has run. 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] if self.running && existing_watch 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") elsif self.running applog(t, :info, "#{t.name} Loaded config") end end # Unmonitor and remove the given watch from god. # # watch - The Watch to remove. # # Returns nothing. 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. Aborts on invalid kind, # duplicate contact name, invalid contact, or conflicting group name. # # kind - The Symbol contact class specifier. # # Returns nothing. def self.contact(kind) # Ensure internal init has run. self.internal_init # Verify contact has been loaded. if CONTACT_LOAD_SUCCESS[kind] == false applog(nil, :error, "A required dependency for the #{kind} contact is unavailable.") applog(nil, :error, "Run the following commands to install the dependencies:") CONTACT_DEPS[kind].each do |d| applog(nil, :error, " [sudo] gem install #{d}") end abort end # 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 - The Contact to remove. # # Returns nothing. def self.uncontact(contact) self.contacts.delete(contact.name) if contact.group self.contact_groups[contact.group].delete(contact) end end def self.watches_by_name(name) case name when "", nil then self.watches.values.dup else Array(self.watches[name] || self.groups[name]).dup end end # Control the lifecycle of the given task(s). # # name - The String name of a task/group. If empty, invokes command for all tasks. # command - The String command to run. Valid commands are: # "start", "monitor", "restart", "stop", "unmonitor", "remove". # # Returns an Array of String task names affected by the command. def self.control(name, command) # Get the list of items. items = self.watches_by_name(name) jobs = [] # Do the command. case command when "start", "monitor" items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } } when "restart" items.each { |w| jobs << Thread.new { w.move(:restart) } } when "stop" items.each { |w| jobs << Thread.new { w.action(:stop); w.unmonitor if w.state != :unmonitored } } when "unmonitor" items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } } when "remove" items.each { |w| self.unwatch(w) } 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, false if all tasks could not be stopped within 10 # seconds def self.stop_all self.watches.sort.each do |name, w| Thread.new do w.unmonitor if w.state != :unmonitored w.action(:stop) if w.alive? end end terminate_timeout.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 # * Hard exit using exit! # # Never returns because the process will no longer exist! def self.terminate FileUtils.rm_f(self.pid) if self.pid self.server.stop if self.server exit!(0) end # Gather the status of each task. # # Examples # # God.status # # => { 'mongrel' => :up, 'nginx' => :up } # # Returns a Hash where the key is the String task name and the value is the # Symbol status. def self.status info = {} self.watches.map do |name, w| info[name] = {:state => w.state, :group => w.group} end info end # Send a signal to each task. # # name - The String name of the task or group. # signal - The String or integer signal to send. e.g. 'HUP', 9. # # Returns an Array of String names of the tasks affected. def self.signal(name, signal) items = watches_by_name(name) jobs = [] items.each { |w| jobs << Thread.new { w.signal(signal) } } jobs.each { |j| j.join } items.map { |x| x.name } end # Log lines for the given task since the specified time. # # watch_name - The String name of the task (may be abbreviated). # since - The Time since which to report log lines. # # Raises God::NoSuchWatchError if no tasks matched. # Returns the String of newline separated 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 - The String config file contents. # filename - The filename of the config file. # action - The optional String command specifying how to deal with # existing watches. Valid options are: 'stop', 'remove' or # 'leave' (default). # # Returns a three-tuple Array [loaded_names, errors, unloaded_names] where: # loaded_names - The Array of String task names that were loaded. # errors - The String of error messages produced during the # load phase. Will be a blank String if no errors # were encountered. # unloaded_names - The Array of String task names that were unloaded # from the system (if 'remove' or 'stop' was # specified as the action). def self.running_load(code, filename, action = nil) errors = "" loaded_watches = [] unloaded_watches = [] jobs = [] begin LOG.start_capture Gem.clear_paths eval(code, root_binding, filename) self.pending_watches.each do |w| if previous_state = self.pending_watch_states[w.name] w.monitor unless previous_state == :unmonitored else w.monitor if w.autostart? end end loaded_watches = self.pending_watches.map { |w| w.name } self.pending_watches.clear self.pending_watch_states.clear self.watches.each do |name, watch| next if loaded_watches.include?(name) case action when 'stop' jobs << Thread.new(watch) { |w| w.action(:stop); self.unwatch(w) } unloaded_watches << name when 'remove' jobs << Thread.new(watch) { |w| self.unwatch(w) } unloaded_watches << name when 'leave', '', nil # Do nothing else raise InvalidCommandError, "Unknown action: #{action}" end end # Make sure we quit capturing when we're done. LOG.finish_capture 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 jobs.each { |t| t.join } [loaded_watches, errors, unloaded_watches] end # Load the given file(s) according to the given glob. # # glob - The glob-enabled String path to load. # # Returns nothing. def self.load(glob) Dir[glob].each do |f| Kernel.load f end end # Setup pid file directory and log system. # # Returns nothing. def self.setup if self.pid_file_directory # Pid file dir was specified, ensure it is created and writable. unless File.exist?(self.pid_file_directory) begin FileUtils.mkdir_p(self.pid_file_directory) rescue Errno::EACCES => e abort "Failed to create pid file directory: #{e.message}" end end unless File.writable?(self.pid_file_directory) abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}" end else # No pid file dir specified, try defaults. PID_FILE_DIRECTORY_DEFAULTS.each do |idir| dir = File.expand_path(idir) begin FileUtils.mkdir_p(dir) if File.writable?(dir) self.pid_file_directory = dir break end 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 if God::Logger.syslog LOG.info("Syslog enabled.") else LOG.info("Syslog disabled.") 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, self.socket_user, self.socket_group, self.socket_perms) # 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 = Thread.new do loop do sleep 60 end end end # Prevent god from exiting. # # Returns nothing. def self.join self.main.join if self.main end # Returns the version String. def self.version God::VERSION end # To be called on program exit to start god. # # Returns nothing. def self.at_exit self.start self.join 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. # # pattern - The String containing the abbreviation. # list - The Array of Strings to match against. # # Examples # # list = %w{ foo bar bars } # pattern = 'br' # God.pattern_match(list, pattern) # # => ['bar', 'bars'] # # Returns the Array of matching name Strings. def self.pattern_match(pattern, list) regex = pattern.split('').join('.*') list.select do |item| item =~ Regexp.new(regex) end.sort_by { |x| x.size } end end # Runs immediately before the program exits. If $run is true, # start god, if $run is false, exit normally. # # Returns nothing. at_exit do God.at_exit if $run end end ruby-god-0.13.3/lib/god/000077500000000000000000000000001226052465500146545ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/behavior.rb000066400000000000000000000020571226052465500170040ustar00rootroot00000000000000module 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) def self.generate(kind, watch) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern b = God::Behaviors.const_get(sym).new b.watch = watch b 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' def friendly_name "Behavior " + super + " on Watch '#{self.watch.name}'" end end end ruby-god-0.13.3/lib/god/behaviors/000077500000000000000000000000001226052465500166365ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/behaviors/clean_pid_file.rb000066400000000000000000000006141226052465500221010ustar00rootroot00000000000000module 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 ruby-god-0.13.3/lib/god/behaviors/clean_unix_socket.rb000066400000000000000000000006361226052465500226650ustar00rootroot00000000000000module 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 File.delete(self.watch.unix_socket) "deleted unix socket" rescue "no unix socket to delete" end end end end ruby-god-0.13.3/lib/god/behaviors/notify_when_flapping.rb000066400000000000000000000027711226052465500234030ustar00rootroot00000000000000module God module Behaviors class NotifyWhenFlapping < Behavior 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 self.notifier.notify("#{self.watch.name} has called start/restart #{@startup_times.length} times in #{self.seconds} seconds") end end end end end ruby-god-0.13.3/lib/god/cli/000077500000000000000000000000001226052465500154235ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/cli/command.rb000066400000000000000000000155441226052465500173770ustar00rootroot00000000000000module 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 rescue DRb::DRbConnError puts "The server is not available (or you do not have permissions to access it)" abort end end def dispatch if %w{load status signal log quit terminate}.include?(@command) setup send("#{@command}_command") elsif %w{start stop restart monitor unmonitor remove}.include?(@command) setup lifecycle_command elsif @command == 'check' check_command else puts "Command '#{@command}' is not valid. Run 'god --help' for usage" abort end end def load_command file = @args[1] action = @args[2] || 'leave' unless ['stop', 'remove', 'leave', ''].include?(action) puts "Command '#{@command}' action must be either 'stop', 'remove' or 'leave'" exit(1) end puts "Sending '#{@command}' command with action '#{action}'" puts unless File.exist?(file) abort "File not found: #{file}" end affected, errors, removed = *@server.running_load(File.read(file), File.expand_path(file), action) # output response unless affected.empty? puts 'The following tasks were affected:' affected.each do |w| puts ' ' + w end end unless removed.empty? puts 'The following tasks were removed:' removed.each do |w| puts ' ' + w end end unless errors.empty? puts errors exit(1) end end def status_command exitcode = 0 statuses = @server.status groups = {} statuses.each do |name, status| g = status[:group] || '' groups[g] ||= {} groups[g][name] = status end if item = @args[1] if single = statuses[item] # specified task (0 -> up, 1 -> unmonitored, 2 -> other) state = single[:state] puts "#{item}: #{state}" exitcode = state == :up ? 0 : (state == :unmonitored ? 1 : 2) elsif groups[item] # specified group (0 -> up, N -> other) puts "#{item}:" groups[item].keys.sort.each do |name| state = groups[item][name][:state] print " " puts "#{name}: #{state}" exitcode += 1 unless state == :up end else puts "Task or Group '#{item}' not found." exit(1) end else # show all groups and watches groups.keys.sort.each do |group| puts "#{group}:" unless group.empty? groups[group].keys.sort.each do |name| state = groups[group][name][:state] print " " unless group.empty? puts "#{name}: #{state}" end end end exit(exitcode) end def signal_command # get the name of the watch/group name = @args[1] signal = @args[2] puts "Sending signal '#{signal}' to '#{name}'" t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } } watches = @server.signal(name, signal) # output response t.kill; STDOUT.puts unless watches.empty? puts 'The following watches were affected:' watches.each do |w| puts ' ' + w end else puts 'No matching task or group' 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 puts "Please wait..." t = Time.at(0) loop do print @server.running_log(name, t) t = Time.now sleep 0.25 end rescue God::NoSuchWatchError puts "No such watch" rescue DRb::DRbConnError puts "The server went away" end end def quit_command begin @server.terminate abort 'Could not stop god' rescue DRb::DRbConnError puts 'Stopped god' end end def terminate_command t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } } if @server.stop_all t.kill; STDOUT.puts puts 'Stopped all watches' else t.kill; STDOUT.puts puts "Could not stop all watches within #{@server.terminate_timeout} seconds" end begin @server.terminate abort 'Could not stop god' rescue DRb::DRbConnError 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 else 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) ::Process.waitpid(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? puts 'The following watches were affected:' watches.each do |w| puts ' ' + w end else puts 'No matching task or group' end end end # Command end end ruby-god-0.13.3/lib/god/cli/run.rb000066400000000000000000000101511226052465500165520ustar00rootroot00000000000000module God module CLI class Run def initialize(options) @options = options dispatch end def dispatch # have at_exit start god $run = true if @options[:syslog] require 'god/sys_logger' end # run if @options[:daemonize] run_daemonized else run_in_front end end def attach process = System::Process.new(@options[:attach]) Thread.new do loop do unless process.exists? applog(nil, :info, "Going down because attached process #{@options[:attach]} exited") exit! end sleep 5 end end end def default_run # make sure we have STDIN/STDOUT redirected immediately setup_logging # 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] if !@options[:config].include?('*') && !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 setup_logging end def run_in_front require 'god' default_run end def run_daemonized # trap and ignore SIGHUP Signal.trap('HUP') {} pid = fork do begin require 'god' # set pid if requested if @options[:pid] # and as deamon God.pid = @options[:pid] end default_run unless God::EventHandler.loaded? puts puts "***********************************************************************" puts "*" puts "* Event conditions are not available for your installation of god." puts "* You may still use and write custom conditions using the poll system" puts "*" 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 setup_logging log_file = God.log_file log_file = File.expand_path(@options[:log]) if @options[:log] log_file = "/dev/null" if !log_file && @options[:daemonize] if log_file puts "Sending output to log file: #{log_file}" unless @options[:daemonize] # reset file descriptors STDIN.reopen "/dev/null" STDOUT.reopen(log_file, "a") STDERR.reopen STDOUT STDOUT.sync = true end end def load_config(config) files = File.directory?(config) ? Dir['**/*.god'] : Dir[config] abort "No files could be found" if files.empty? files.each do |god_file| unless load_god_file(god_file) abort "File '#{god_file}' could not be loaded" end end end def load_god_file(god_file) applog(nil, :info, "Loading #{god_file}") load File.expand_path(god_file) true rescue Exception => e if e.instance_of?(SystemExit) raise else puts "There was an error in #{god_file}" puts "\t" + e.message puts "\t" + e.backtrace.join("\n\t") false end end end # Run end end ruby-god-0.13.3/lib/god/cli/version.rb000066400000000000000000000005701226052465500174370ustar00rootroot00000000000000module 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 ruby-god-0.13.3/lib/god/compat19.rb000066400000000000000000000014371226052465500166430ustar00rootroot00000000000000require 'monitor' # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/monitor.rb module MonitorMixin class ConditionVariable def wait(timeout = nil) @monitor.__send__(:mon_check_owner) count = @monitor.__send__(:mon_exit_for_cond) begin @cond.wait(@monitor.instance_variable_get("@mon_mutex"), timeout) return true ensure @monitor.__send__(:mon_enter_for_cond, count) end end end end # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/thread.rb class ConditionVariable def wait(mutex, timeout=nil) begin # TODO: mutex should not be used @waiters_mutex.synchronize do @waiters.push(Thread.current) end mutex.sleep timeout end self end end ruby-god-0.13.3/lib/god/condition.rb000066400000000000000000000054201226052465500171700ustar00rootroot00000000000000module 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" 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 begin Contact.normalize(condition.notify) rescue ArgumentError => e valid &= Configurable.complain("Attribute 'notify' " + e.message, condition) end end valid end # Construct the friendly name of this Condition, looks like: # # Condition FooBar on Watch 'baz' 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 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) # Return false otherwise 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 ruby-god-0.13.3/lib/god/conditions/000077500000000000000000000000001226052465500170255ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/conditions/always.rb000066400000000000000000000013521226052465500206530ustar00rootroot00000000000000module God module Conditions # Always trigger or never trigger. # # Examples # # # Always trigger. # on.condition(:always) do |c| # c.what = true # end # # # Never trigger. # on.condition(:always) do |c| # c.what = false # end class Always < PollCondition # The Boolean determining whether this condition will always trigger # (true) or never trigger (false). 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 ruby-god-0.13.3/lib/god/conditions/complex.rb000066400000000000000000000036151226052465500210260ustar00rootroot00000000000000module God module Conditions class Complex < PollCondition 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 # encourages easy-to-read conditions, being able to omit it # allows for more DRY code in some cases, so we deal with a # nil @this here by initially setting res to true or false, # depending on whether the first operator used is AND or OR # respectively. if 0 < @op_stack[0] & AND res = true else res = false end 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 ruby-god-0.13.3/lib/god/conditions/cpu_usage.rb000066400000000000000000000042351226052465500213310ustar00rootroot00000000000000module 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. # # Paramaters # 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 # may use #percent to clarify this amount (see examples). # # Examples # # Trigger if the process is using more than 25 percent of the cpu (from a Watch): # # on.condition(:cpu_usage) do |c| # c.above = 25.percent # end # # Non-Watch Tasks must specify a PID file: # # on.condition(:cpu_usage) do |c| # c.above = 25.percent # c.pid_file = "/var/run/mongrel.3000.pid" # 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) self.info = [] 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 else return false end end end end end ruby-god-0.13.3/lib/god/conditions/degrading_lambda.rb000066400000000000000000000021721226052465500226000ustar00rootroot00000000000000module 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 unless pass? if @tries == 2 self.info = "lambda condition was satisfied" return true end self.interval = self.interval / 2.0 @tries += 1 else @tries = 0 self.interval = @original_interval end self.info = "lambda condition was not satisfied" false end private def pass? begin Timeout::timeout(@interval) { self.lambda.call() } rescue Timeout::Error false end end end end end ruby-god-0.13.3/lib/god/conditions/disk_usage.rb000066400000000000000000000014121226052465500214660ustar00rootroot00000000000000module 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 self.info = [] usage = `df -P | grep -i " #{self.mount_point}$" | awk '{print $5}' | sed 's/%//'` if usage.to_i > self.above self.info = "disk space out of bounds" return true else return false end end end end end ruby-god-0.13.3/lib/god/conditions/file_mtime.rb000066400000000000000000000010471226052465500214660ustar00rootroot00000000000000module God module Conditions class FileMtime < PollCondition attr_accessor :path, :max_age def initialize super self.path = nil self.max_age = nil end def valid? valid = true valid &= complain("Attribute 'path' must be specified", self) if self.path.nil? valid &= complain("Attribute 'max_age' must be specified", self) if self.max_age.nil? valid end def test (Time.now - File.mtime(self.path)) > self.max_age end end end end ruby-god-0.13.3/lib/god/conditions/file_touched.rb000066400000000000000000000016021226052465500220030ustar00rootroot00000000000000module God module Conditions # Condition Symbol :file_touched # Type: Poll # # Trigger when a specified file is touched. # # Paramaters # Required # +path+ is the path to the file to watch. # # Examples # # Trigger if 'tmp/restart.txt' file is touched (from a Watch): # # on.condition(:file_touched) do |c| # c.path = 'tmp/restart.txt' # end # class FileTouched < PollCondition attr_accessor :path def initialize super self.path = nil end def valid? valid = true valid &= complain("Attribute 'path' must be specified", self) if self.path.nil? valid end def test if File.exists?(self.path) (Time.now - File.mtime(self.path)) <= self.interval else false end end end end end ruby-god-0.13.3/lib/god/conditions/flapping.rb000066400000000000000000000077371226052465500211700ustar00rootroot00000000000000module 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. # # Paramaters # Required # +times+ is the number of times that the Task must transition before # triggering. # +within+ is the number of seconds within which the Task must transition # the specified number of times before triggering. You may use # the sugar methods #seconds, #minutes, #hours, #days to clarify # your code (see examples). # --one or both of-- # +from_state+ is the state (as a Symbol) from which the transition must occur. # +to_state is the state (as a Symbol) to which the transition must occur. # # Optional: # +retry_in+ is the number of seconds after which to re-monitor the Task after # 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 # # Examples # # Trigger if class Flapping < TriggerCondition attr_accessor :times, :within, :from_state, :to_state, :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? valid &= complain("Attribute 'within' must be specified", self) if self.within.nil? 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 retry_mechanism end end end rescue => e puts e.message 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) end else # 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 end end end end end end end ruby-god-0.13.3/lib/god/conditions/http_response_code.rb000066400000000000000000000140631226052465500232450ustar00rootroot00000000000000require 'net/http' require 'net/https' module God module Conditions # Condition Symbol :http_response_code # Type: Poll # # Trigger based on the response from an HTTP request. # # Paramaters # Required # +host+ is the hostname to connect [required] # --one of code_is or code_is_not-- # +code_is+ trigger if the response code IS one of these # e.g. 500 or '500' or [404, 500] or %w{404 500} # +code_is_not+ trigger if the response code IS NOT one of these # e.g. 200 or '200' or [200, 302] or %w{200 302} # Optional # +port+ is the port to connect (default 80) # +path+ is the path to connect (default '/') # +headers+ is the hash of HTTP headers to send (default none) # +times+ is the number of times after which to trigger (default 1) # e.g. 3 (times in a row) or [3, 5] (three out of fives times) # +timeout+ is the time to wait for a connection (default 60.seconds) # +ssl+ should the connection use ssl (default false) # # Examples # # Trigger if the response code from www.example.com/foo/bar # is not a 200 (or if the connection is refused or times out: # # on.condition(:http_response_code) do |c| # c.host = 'www.example.com' # c.path = '/foo/bar' # c.code_is_not = 200 # end # # Trigger if the response code is a 404 or a 500 (will not # be triggered by a connection refusal or timeout): # # on.condition(:http_response_code) do |c| # c.host = 'www.example.com' # c.path = '/foo/bar' # c.code_is = [404, 500] # end # # Trigger if the response code is not a 200 five times in a row: # # on.condition(:http_response_code) do |c| # c.host = 'www.example.com' # c.path = '/foo/bar' # c.code_is_not = 200 # c.times = 5 # end # # Trigger if the response code is not a 200 or does not respond # within 10 seconds: # # on.condition(:http_response_code) do |c| # c.host = 'www.example.com' # c.path = '/foo/bar' # c.code_is_not = 200 # c.timeout = 10 # end class HttpResponseCode < PollCondition attr_accessor :code_is, # e.g. 500 or '500' or [404, 500] or %w{404 500} :code_is_not, # e.g. 200 or '200' or [200, 302] or %w{200 302} :times, # e.g. 3 or [3, 5] :host, # e.g. www.example.com :port, # e.g. 8080 :ssl, # e.g. true or false :ca_file, # e.g /path/to/pem_file for ssl verification (checkout http://curl.haxx.se/ca/cacert.pem) :timeout, # e.g. 60.seconds :path, # e.g. '/' :headers # e.g. {'Host' => 'myvirtual.mydomain.com'} def initialize super self.port = 80 self.path = '/' self.headers = {} self.times = [1, 1] self.timeout = 60.seconds self.ssl = false self.ca_file = nil 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? valid &= complain("One (and only one) of attributes 'code_is' and 'code_is_not' must be specified", self) if (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not) valid end def test response = nil connection = Net::HTTP.new(self.host, self.port) connection.use_ssl = self.port == 443 ? true : self.ssl connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if connection.use_ssl? if connection.use_ssl? && self.ca_file pem = File.read(self.ca_file) connection.ca_file = self.ca_file connection.verify_mode = OpenSSL::SSL::VERIFY_PEER end connection.start 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) elsif self.code_is_not && !self.code_is_not.include?(actual_response_code) pass(actual_response_code) else fail(actual_response_code) end rescue Errno::ECONNREFUSED self.code_is ? fail('Refused') : pass('Refused') rescue Errno::ECONNRESET self.code_is ? fail('Reset') : pass('Reset') rescue EOFError self.code_is ? fail('EOF') : pass('EOF') rescue Timeout::Error self.code_is ? fail('Timeout') : pass('Timeout') rescue Errno::ETIMEDOUT self.code_is ? fail('Timedout') : pass('Timedout') 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 self.info = "http response abnormal #{history(code, true)}" true else self.info = "http response nominal #{history(code, true)}" 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 ruby-god-0.13.3/lib/god/conditions/lambda.rb000066400000000000000000000007551226052465500206010ustar00rootroot00000000000000module God module Conditions class Lambda < PollCondition attr_accessor :lambda def valid? valid = true valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil? valid end def test if self.lambda.call() self.info = "lambda condition was satisfied" true else self.info = "lambda condition was not satisfied" false end end end end end ruby-god-0.13.3/lib/god/conditions/memory_usage.rb000066400000000000000000000043541226052465500220540ustar00rootroot00000000000000module God module Conditions # Condition Symbol :memory_usage # Type: Poll # # Trigger when the resident memory of a process is above a specified limit. # # Paramaters # Required # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. # +above+ is the amount of resident memory (in kilobytes) above which # the condition should trigger. You can also use the sugar # methods #kilobytes, #megabytes, and #gigabytes to clarify # this amount (see examples). # # Examples # # Trigger if the process is using more than 100 megabytes of resident # memory (from a Watch): # # on.condition(:memory_usage) do |c| # c.above = 100.megabytes # end # # Non-Watch Tasks must specify a PID file: # # on.condition(:memory_usage) do |c| # c.above = 100.megabytes # c.pid_file = "/var/run/mongrel.3000.pid" # 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) self.info = [] 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 else return false end end end end end ruby-god-0.13.3/lib/god/conditions/process_exits.rb000066400000000000000000000035531226052465500222520ustar00rootroot00000000000000module God module Conditions # Trigger when a process exits. # # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. # # Examples # # # Trigger if process exits (from a Watch). # on.condition(:process_exits) # # # Trigger if process exits (non-Watch). # on.condition(:process_exits) do |c| # c.pid_file = "/var/run/mongrel.3000.pid" # end class ProcessExits < EventCondition # The String PID file location of the process in question. Automatically # populated for Watches. 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 pid_file_location = self.pid_file || self.watch.pid_file applog(self.watch, :error, "#{self.watch.name} could not deregister: no cached PID or PID file #{pid_file_location} (#{self.base_name})") end end end end end ruby-god-0.13.3/lib/god/conditions/process_running.rb000066400000000000000000000034561226052465500226000ustar00rootroot00000000000000module God module Conditions # Trigger when a process is running or not running depending on attributes. # # Examples # # # Trigger if process IS NOT running. # on.condition(:process_running) do |c| # c.running = false # end # # # Trigger if process IS running. # on.condition(:process_running) do |c| # c.running = true # end # # # Non-Watch Tasks must specify a PID file. # on.condition(:process_running) do |c| # c.running = false # c.pid_file = "/var/run/mongrel.3000.pid" # end class ProcessRunning < PollCondition # Public: The Boolean specifying whether you want to trigger if the # process is running (true) or if it is not running (false). attr_accessor :running # Public: The String PID file location of the process in question. # Automatically populated for Watches. attr_accessor :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 elsif (!self.running && !active) self.info.concat(["process is not running"]) true else if self.running self.info.concat(["process is not running"]) end false end end end end end ruby-god-0.13.3/lib/god/conditions/socket_responding.rb000066400000000000000000000076371226052465500231070ustar00rootroot00000000000000require 'socket' include Socket::Constants module God module Conditions # Condition Symbol :socket_running # Type: Poll # # Trigger when a TCP or UNIX socket is running or not # # Parameters # Required # +family+ is the family of socket: either 'tcp' or 'unix' # --one of port or path-- # +port+ is the port (required if +family+ is 'tcp') # +path+ is the path (required if +family+ is 'unix') # # Optional # +responding+ is the boolean specifying whether you want to trigger if the socket is responding (true) # or if it is not responding (false) (default false) # # Examples # # Trigger if the TCP socket on port 80 is not responding or the connection is refused # # on.condition(:socket_responding) do |c| # c.family = 'tcp' # c.port = '80' # end # # Trigger if the socket is not responding or the connection is refused (use alternate compact +socket+ interface) # # on.condition(:socket_responding) do |c| # c.socket = 'tcp:80' # end # # Trigger if the socket is responding # # on.condition(:socket_responding) do |c| # c.socket = 'tcp:80' # c.responding = true # end # # Trigger if the socket is not responding or the connection is refused 5 times in a row # # on.condition(:socket_responding) do |c| # c.socket = 'tcp:80' # c.times = 5 # end # # Trigger if the Unix socket on path '/tmp/sock' is not responding or non-existent # # on.condition(:socket_responding) do |c| # c.family = 'unix' # c.port = '/tmp/sock' # end # class SocketResponding < PollCondition attr_accessor :family, :addr, :port, :path, :times, :responding def initialize super # default to tcp on the localhost self.family = 'tcp' self.addr = '127.0.0.1' # Set these to nil/0 values self.port = 0 self.path = nil self.responding = false 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]) @history = Timeline.new(self.times[1]) end def reset @timeline.clear @history.clear end def socket=(s) components = s.split(':') if components.size == 3 @family,@addr,@port = components @port = @port.to_i elsif components[0] =~ /^tcp$/ @family = components[0] @port = components[1].to_i elsif components[0] =~ /^unix$/ @family = components[0] @path = components[1] end end def valid? valid = true if self.family == 'tcp' and @port == 0 valid &= complain("Attribute 'port' must be specified for tcp sockets", self) end if self.family == 'unix' and self.path.nil? valid &= complain("Attribute 'path' must be specified for unix sockets", self) end valid = false unless %w{tcp unix}.member?(self.family) valid end def test self.info = [] if self.family == 'tcp' begin s = TCPSocket.new(self.addr, self.port) rescue SystemCallError end status = self.responding == !s.nil? elsif self.family == 'unix' begin s = UNIXSocket.new(self.path) rescue SystemCallError end status = self.responding == !s.nil? else status = false end @timeline.push(status) history = "[" + @timeline.map {|t| t ? '*' : ''}.join(',') + "]" if @timeline.select { |x| x }.size >= self.times.first self.info = "socket out of bounds #{history}" return true else return false end end end end end ruby-god-0.13.3/lib/god/conditions/tries.rb000066400000000000000000000017611226052465500205050ustar00rootroot00000000000000module 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 else self.info = "tries within bounds #{history}" return false end end end end end ruby-god-0.13.3/lib/god/configurable.rb000066400000000000000000000027061226052465500176460ustar00rootroot00000000000000module 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 # # A convenience method 'complain' is available that will print out a message and return false, # making it easy to report multiple validation errors: # # def valid? # valid = true # valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil? # valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil? # valid # end def valid? true end def base_name x = 1 # fix for MRI's local scope optimization bug DO NOT REMOVE! @base_name ||= self.class.name.split('::').last end def friendly_name base_name end def self.complain(text, c = nil) watch = c.watch rescue nil msg = "" msg += "#{watch.name}: " if watch msg += text msg += " for #{c.friendly_name}" if c applog(watch, :error, msg) false end def complain(text, c = nil) Configurable.complain(text, c) end end end ruby-god-0.13.3/lib/god/contact.rb000066400000000000000000000071101226052465500166330ustar00rootroot00000000000000module 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" 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 def self.defaults yield self end def arg(name) self.instance_variable_get("@#{name}") || self.class.instance_variable_get("@#{name}") end # Normalize the given notify specification into canonical form. # +spec+ is the notify spec as a String, Array of Strings, or Hash # # Canonical form looks like: # {:contacts => ['fred', 'john'], :priority => '1', :category => 'awesome'} # 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) case spec when String {:contacts => Array(spec)} when Array unless spec.select { |x| !x.instance_of?(String) }.empty? raise ArgumentError.new("contains non-String elements") end {:contacts => spec} when Hash copy = spec.dup # check :contacts if contacts = copy.delete(:contacts) case contacts when String # valid when Array unless contacts.select { |x| !x.instance_of?(String) }.empty? raise ArgumentError.new("has a :contacts key containing non-String elements") end # valid else raise ArgumentError.new("must have a :contacts key pointing to a String or Array of Strings") end 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 # +time+ is the Time at which the notification was made # +priority+ is the arbitrary priority String # +category+ is the arbitrary category String # +host+ is the hostname of the server 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 def friendly_name super + " Contact '#{self.name}'" end end end ruby-god-0.13.3/lib/god/contacts/000077500000000000000000000000001226052465500164725ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/contacts/airbrake.rb000066400000000000000000000022311226052465500205750ustar00rootroot00000000000000# Send a notice to Airbrake (http://airbrake.io/). # # apikey - The String API key. CONTACT_DEPS[:airbrake] = ['airbrake'] CONTACT_DEPS[:airbrake].each do |d| require d end module God module Contacts class Airbrake < Contact class << self attr_accessor :apikey end def valid? valid = true valid &= complain("Attribute 'apikey' must be specified", self) if self.apikey.nil? valid end attr_accessor :apikey def notify(message, time, priority, category, host) ::Airbrake.configure {} message = "God: #{message.to_s} at #{host}" message << " | #{[category, priority].join(" ")}" unless category.to_s.empty? or priority.to_s.empty? if ::Airbrake.notify nil, :error_message => message, :api_key => arg(:apikey) self.info = "sent airbrake notification to #{self.name}" else self.info = "failed to send airbrake notification to #{self.name}" end rescue Object => e applog(nil, :info, "failed to send airbrake notification: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/contacts/campfire.rb000066400000000000000000000066031226052465500206120ustar00rootroot00000000000000# Send a notice to a Campfire room (http://campfirenow.com). # # subdomain - The String subdomain of the Campfire account. If your URL is # "foo.campfirenow.com" then your subdomain is "foo". # token - The String token used for authentication. # room - The String room name to which the message should be sent. # ssl - A Boolean determining whether or not to use SSL # (default: false). require 'net/http' require 'net/https' CONTACT_DEPS[:campfire] = ['json'] CONTACT_DEPS[:campfire].each do |d| require d end module Marshmallow class Connection def initialize(options) raise "Required option :subdomain not set." unless options[:subdomain] raise "Required option :token not set." unless options[:token] @options = options end def base_url scheme = @options[:ssl] ? 'https' : 'http' subdomain = @options[:subdomain] "#{scheme}://#{subdomain}.campfirenow.com" end def find_room_id_by_name(room) url = URI.parse("#{base_url}/rooms.json") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true if @options[:ssl] req = Net::HTTP::Get.new(url.path) req.basic_auth(@options[:token], 'X') res = http.request(req) case res when Net::HTTPSuccess rooms = JSON.parse(res.body) room = rooms['rooms'].select { |x| x['name'] == room } rooms.empty? ? nil : room.first['id'] else raise res.error! end end def speak(room, message) room_id = find_room_id_by_name(room) raise "No such room: #{room}." unless room_id url = URI.parse("#{base_url}/room/#{room_id}/speak.json") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true if @options[:ssl] req = Net::HTTP::Post.new(url.path) req.basic_auth(@options[:token], 'X') req.set_content_type('application/json') req.body = { 'message' => { 'body' => message } }.to_json res = http.request(req) case res when Net::HTTPSuccess true else raise res.error! end end end end module God module Contacts class Campfire < Contact class << self attr_accessor :subdomain, :token, :room, :ssl attr_accessor :format end self.ssl = false self.format = lambda do |message, time, priority, category, host| "[#{time.strftime('%H:%M:%S')}] #{host} - #{message}" end attr_accessor :subdomain, :token, :room, :ssl def valid? valid = true valid &= complain("Attribute 'subdomain' must be specified", self) unless arg(:subdomain) valid &= complain("Attribute 'token' must be specified", self) unless arg(:token) valid &= complain("Attribute 'room' must be specified", self) unless arg(:room) valid end def notify(message, time, priority, category, host) body = Campfire.format.call(message, time, priority, category, host) conn = Marshmallow::Connection.new( :subdomain => arg(:subdomain), :token => arg(:token), :ssl => arg(:ssl) ) conn.speak(arg(:room), body) self.info = "notified campfire: #{arg(:subdomain)}" rescue Object => e applog(nil, :info, "failed to notify campfire: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/contacts/email.rb000066400000000000000000000120321226052465500201040ustar00rootroot00000000000000# Send a notice to an email address. # # to_email - The String email address to which the email will be sent. # to_name - The String name corresponding to the recipient. # from_email - The String email address from which the email will be sent. # from_name - The String name corresponding to the sender. # delivery_method - The Symbol delivery method. [ :smtp | :sendmail ] # (default: :smtp). # # === SMTP Options (when delivery_method = :smtp) === # server_host - The String hostname of the SMTP server (default: localhost). # server_port - The Integer port of the SMTP server (default: 25). # server_auth - The Symbol authentication method. Possible values: # [ nil | :plain | :login | :cram_md5 ] # The default is nil, which means no authentication. To # enable authentication, pass the appropriate symbol and # then pass the appropriate SMTP Auth Options (below). # # === SMTP Auth Options (when server_auth != nil) === # server_domain - The String domain. # server_user - The String username. # server_password - The String password. # # === Sendmail Options (when delivery_method = :sendmail) === # sendmail_path - The String path to the sendmail executable # (default: "/usr/sbin/sendmail"). # sendmail_args - The String args to send to sendmail (default "-i -t"). require 'time' require 'net/smtp' module God module Contacts class Email < Contact class << self attr_accessor :to_email, :to_name, :from_email, :from_name, :delivery_method, :server_host, :server_port, :server_auth, :server_domain, :server_user, :server_password, :sendmail_path, :sendmail_args attr_accessor :format end self.from_email = 'god@example.com' self.from_name = 'God Process Monitoring' self.delivery_method = :smtp self.server_auth = nil self.server_host = 'localhost' self.server_port = 25 self.sendmail_path = '/usr/sbin/sendmail' self.sendmail_args = '-i -t' self.format = lambda do |name, from_email, from_name, to_email, to_name, message, time, priority, category, host| <<-EOF From: #{from_name} <#{from_email}> To: #{to_name || name} <#{to_email}> Subject: [god] #{message} Date: #{time.httpdate} Message-Id: <#{rand(1000000000).to_s(36)}.#{$$}.#{from_email}> Message: #{message} Host: #{host} Priority: #{priority} Category: #{category} EOF end attr_accessor :to_email, :to_name, :from_email, :from_name, :delivery_method, :server_host, :server_port, :server_auth, :server_domain, :server_user, :server_password, :sendmail_path, :sendmail_args def valid? valid = true valid &= complain("Attribute 'to_email' must be specified", self) unless arg(:to_email) valid &= complain("Attribute 'delivery_method' must be one of [ :smtp | :sendmail ]", self) unless [:smtp, :sendmail].include?(arg(:delivery_method)) if arg(:delivery_method) == :smtp valid &= complain("Attribute 'server_host' must be specified", self) unless arg(:server_host) valid &= complain("Attribute 'server_port' must be specified", self) unless arg(:server_port) if arg(:server_auth) valid &= complain("Attribute 'server_domain' must be specified", self) unless arg(:server_domain) valid &= complain("Attribute 'server_user' must be specified", self) unless arg(:server_user) valid &= complain("Attribute 'server_password' must be specified", self) unless arg(:server_password) end end valid end def notify(message, time, priority, category, host) body = Email.format.call(self.name, arg(:from_email), arg(:from_name), arg(:to_email), arg(:to_name), message, time, priority, category, host) case arg(:delivery_method) when :smtp notify_smtp(body) when :sendmail notify_sendmail(body) end self.info = "sent email to #{arg(:to_email)} via #{arg(:delivery_method).to_s}" rescue Object => e applog(nil, :info, "failed to send email to #{arg(:to_email)} via #{arg(:delivery_method).to_s}: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end def notify_smtp(mail) args = [arg(:server_host), arg(:server_port)] if arg(:server_auth) args << arg(:server_domain) args << arg(:server_user) args << arg(:server_password) args << arg(:server_auth) end Net::SMTP.start(*args) do |smtp| smtp.send_message(mail, arg(:from_email), arg(:to_email)) end end def notify_sendmail(mail) IO.popen("#{arg(:sendmail_path)} #{arg(:sendmail_args)}","w+") do |sm| sm.print(mail.gsub(/\r/, '')) sm.flush end end end end end ruby-god-0.13.3/lib/god/contacts/jabber.rb000066400000000000000000000050331226052465500202450ustar00rootroot00000000000000# Send a notice to a Jabber address. # # host - The String hostname of the Jabber server. # port - The Integer port of the Jabber server (default: 5222). # from_jid - The String Jabber ID of the sender. # password - The String password of the sender. # to_jid - The String Jabber ID of the recipient. # subject - The String subject of the message (default: "God Notification"). CONTACT_DEPS[:jabber] = ['xmpp4r'] CONTACT_DEPS[:jabber].each do |d| require d end module God module Contacts class Jabber < Contact class << self attr_accessor :host, :port, :from_jid, :password, :to_jid, :subject attr_accessor :format end self.port = 5222 self.subject = 'God Notification' self.format = lambda do |message, time, priority, category, host| text = "Message: #{message}\n" text += "Host: #{host}\n" if host text += "Priority: #{priority}\n" if priority text += "Category: #{category}\n" if category text end attr_accessor :host, :port, :from_jid, :password, :to_jid, :subject def valid? valid = true valid &= complain("Attribute 'host' must be specified", self) unless arg(:host) valid &= complain("Attribute 'port' must be specified", self) unless arg(:port) valid &= complain("Attribute 'from_jid' must be specified", self) unless arg(:from_jid) valid &= complain("Attribute 'to_jid' must be specified", self) unless arg(:to_jid) valid &= complain("Attribute 'password' must be specified", self) unless arg(:password) valid end def notify(message, time, priority, category, host) body = Jabber.format.call(message, time, priority, category, host) message = ::Jabber::Message.new(arg(:to_jid), body) message.set_type(:normal) message.set_id('1') message.set_subject(arg(:subject)) jabber_id = ::Jabber::JID.new("#{arg(:from_jid)}/God") client = ::Jabber::Client.new(jabber_id) client.connect(arg(:host), arg(:port)) client.auth(arg(:password)) client.send(message) client.close self.info = "sent jabber message to #{self.to_jid}" rescue Object => e if e.respond_to?(:message) applog(nil, :info, "failed to send jabber message to #{arg(:to_jid)}: #{e.message}") else applog(nil, :info, "failed to send jabber message to #{arg(:to_jid)}: #{e.class}") end applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/contacts/prowl.rb000066400000000000000000000032161226052465500201640ustar00rootroot00000000000000# Send a notice to Prowl (http://prowl.weks.net/). # # apikey - The String API key. CONTACT_DEPS[:prowl] = ['prowly'] CONTACT_DEPS[:prowl].each do |d| require d end module God module Contacts class Prowl < Contact class << self attr_accessor :apikey end def valid? valid = true valid &= complain("Attribute 'apikey' must be specified", self) if self.apikey.nil? valid end attr_accessor :apikey def notify(message, time, priority, category, host) result = Prowly.notify do |n| n.apikey = arg(:apikey) n.priority = map_priority(priority.to_i) n.application = category || "God" n.event = "on " + host.to_s n.description = message.to_s + " at " + time.to_s end if result.succeeded? self.info = "sent prowl notification to #{self.name}" else self.info = "failed to send prowl notification to #{self.name}: #{result.message}" end rescue Object => e applog(nil, :info, "failed to send prowl notification to #{self.name}: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end def map_priority(priority) case priority when 1 then Prowly::Notification::Priority::EMERGENCY when 2 then Prowly::Notification::Priority::HIGH when 3 then Prowly::Notification::Priority::NORMAL when 4 then Prowly::Notification::Priority::MODERATE when 5 then Prowly::Notification::Priority::VERY_LOW else Prowly::Notification::Priority::NORMAL end end end end end ruby-god-0.13.3/lib/god/contacts/scout.rb000066400000000000000000000031001226052465500201460ustar00rootroot00000000000000# Send a notice to Scout (http://scoutapp.com/). # # client_key - The String client key. # plugin_id - The String plugin id. require 'net/http' require 'uri' module God module Contacts class Scout < Contact class << self attr_accessor :client_key, :plugin_id attr_accessor :format end self.format = lambda do |message, priority, category, host| text = "Message: #{message}\n" text += "Host: #{host}\n" if host text += "Priority: #{priority}\n" if priority text += "Category: #{category}\n" if category return text end attr_accessor :client_key, :plugin_id def valid? valid = true valid &= complain("Attribute 'client_key' must be specified", self) unless arg(:client_key) valid &= complain("Attribute 'plugin_id' must be specified", self) unless arg(:plugin_id) valid end def notify(message, time, priority, category, host) data = { :client_key => arg(:client_key), :plugin_id => arg(:plugin_id), :format => 'xml', 'alert[subject]' => message, 'alert[body]' => Scout.format.call(message, priority, category, host) } uri = URI.parse('http://scoutapp.com/alerts/create') Net::HTTP.post_form(uri, data) self.info = "sent scout alert to plugin ##{plugin_id}" rescue => e applog(nil, :info, "failed to send scout alert to plugin ##{plugin_id}: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/contacts/twitter.rb000066400000000000000000000035511226052465500205250ustar00rootroot00000000000000# Send a notice to a Twitter account (http://twitter.com/). # # consumer_token - The String OAuth consumer token (defaults to God's # existing consumer token). # consumer_secret - The String OAuth consumer secret (defaults to God's # existing consumer secret). # access_token - The String OAuth access token. # access_secret - The String OAuth access secret. CONTACT_DEPS[:twitter] = ['twitter'] CONTACT_DEPS[:twitter].each do |d| require d end module God module Contacts class Twitter < Contact class << self attr_accessor :consumer_token, :consumer_secret, :access_token, :access_secret end self.consumer_token = 'gOhjax6s0L3mLeaTtBWPw' self.consumer_secret = 'yz4gpAVXJHKxvsGK85tEyzQJ7o2FEy27H1KEWL75jfA' def valid? valid = true valid &= complain("Attribute 'consumer_token' must be specified", self) unless arg(:consumer_token) valid &= complain("Attribute 'consumer_secret' must be specified", self) unless arg(:consumer_secret) valid &= complain("Attribute 'access_token' must be specified", self) unless arg(:access_token) valid &= complain("Attribute 'access_secret' must be specified", self) unless arg(:access_secret) valid end attr_accessor :consumer_token, :consumer_secret, :access_token, :access_secret def notify(message, time, priority, category, host) oauth = ::Twitter::OAuth.new(arg(:consumer_token), arg(:consumer_secret)) oauth.authorize_from_access(arg(:access_token), arg(:access_secret)) ::Twitter::Base.new(oauth).update(message) self.info = "sent twitter update" rescue => e applog(nil, :info, "failed to send twitter update: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/contacts/webhook.rb000066400000000000000000000033341226052465500204600ustar00rootroot00000000000000# Send a notice to a webhook. # # url - The String webhook URL. # format - The Symbol format [ :form | :json ] (default: :form). require 'net/http' require 'uri' CONTACT_DEPS[:webhook] = ['json'] CONTACT_DEPS[:webhook].each do |d| require d end module God module Contacts class Webhook < Contact class << self attr_accessor :url, :format end self.format = :form def valid? valid = true valid &= complain("Attribute 'url' must be specified", self) unless arg(:url) valid &= complain("Attribute 'format' must be one of [ :form | :json ]", self) unless [:form, :json].include?(arg(:format)) valid end attr_accessor :url, :format def notify(message, time, priority, category, host) data = { :message => message, :time => time, :priority => priority, :category => category, :host => host } uri = URI.parse(arg(:url)) http = Net::HTTP.new(uri.host, uri.port) req = nil res = nil case arg(:format) when :form req = Net::HTTP::Post.new(uri.path) req.set_form_data(data) when :json req = Net::HTTP::Post.new(uri.path) req.body = data.to_json end res = http.request(req) case res when Net::HTTPSuccess self.info = "sent webhook to #{arg(:url)}" else self.info = "failed to send webhook to #{arg(:url)}: #{res.error!}" end rescue Object => e applog(nil, :info, "failed to send email to #{arg(:url)}: #{e.message}") applog(nil, :debug, e.backtrace.join("\n")) end end end end ruby-god-0.13.3/lib/god/driver.rb000066400000000000000000000141031226052465500164730ustar00rootroot00000000000000require 'monitor' # Ruby 1.9.1 specific fixes. if RUBY_VERSION.between?('1.9', '1.9.1') require 'god/compat19' end module God # The TimedEvent class represents an event in the future. This class is used # by the drivers to schedule upcoming conditional tests and other scheduled # events. class TimedEvent include Comparable # The Time at which this event is due. attr_accessor :at # Instantiate a new TimedEvent that will be triggered after the specified # delay. # # delay - The optional Numeric number of seconds from now at which to # trigger (default: 0). def initialize(delay = 0) self.at = Time.now + delay end # Is the current event due (current time >= event time)? # # Returns true if the event is due, false if not. def due? Time.now >= self.at end # Compare this event to another. # # other - The other TimedEvent. # # Returns -1 if this event is before the other, 0 if the two events are # due at the same time, 1 if the other event is later. def <=>(other) self.at <=> other.at end end # A DriverEvent is a TimedEvent with an associated Task and Condition. This # is the primary mechanism for poll conditions to be scheduled. class DriverEvent < TimedEvent # Initialize a new DriverEvent. # # delay - The Numeric delay for this event. # task - The Task associated with this event. # condition - The Condition associated with this event. def initialize(delay, task, condition) super(delay) @task = task @condition = condition end # Handle this event by invoking the underlying condition on the associated # task. # # Returns nothing. def handle_event @task.handle_poll(@condition) end end # A DriverOperation is a TimedEvent that is due as soon as possible. It is # used to execute an arbitrary method on the associated Task. class DriverOperation < TimedEvent # Initialize a new DriverOperation. # # task - The Task upon which to operate. # name - The Symbol name of the method to call. # args - The Array of arguments to send to the method. def initialize(task, name, args) super(0) @task = task @name = name @args = args end # Handle the operation that was issued asynchronously. # # Returns nothing. def handle_event @task.send(@name, *@args) end end # The DriverEventQueue is a simple queue that holds TimedEvent instances in # order to maintain the schedule of upcoming events. class DriverEventQueue # Initialize a DriverEventQueue. def initialize @shutdown = false @events = [] @monitor = Monitor.new @resource = @monitor.new_cond end # Wake any sleeping threads after setting the sentinel. # # Returns nothing. def shutdown @shutdown = true @monitor.synchronize do @resource.broadcast end end # Wait until the queue has something due, pop it off the queue, and return # it. # # Returns the popped event. def pop @monitor.synchronize do if @events.empty? raise ThreadError, "queue empty" if @shutdown @resource.wait else delay = @events.first.at - Time.now @resource.wait(delay) if delay > 0 end @events.shift end end # Add an event to the queue, wake any waiters if what we added needs to # happen sooner than the next pending event. # # Returns nothing. def push(event) @monitor.synchronize do @events << event @events.sort! # If we've sorted the events and found the one we're adding is at # the front, it will likely need to run before the next due date. @resource.signal if @events.first == event end end # Returns true if the queue is empty, false if not. def empty? @events.empty? end # Clear the queue. # # Returns nothing. def clear @events.clear end # Returns the Integer length of the queue. def length @events.length end alias size length end # The Driver class is responsible for scheduling all of the events for a # given Task. class Driver # The Thread running the driver loop. attr_reader :thread # Instantiate a new Driver and start the scheduler loop to handle events. # # task - The Task this Driver belongs to. def initialize(task) @task = task @events = God::DriverEventQueue.new @thread = Thread.new do loop do begin @events.pop.handle_event rescue ThreadError => e # queue is empty break rescue Object => e message = format("Unhandled exception in driver loop - (%s): %s\n%s", e.class, e.message, e.backtrace.join("\n")) applog(nil, :fatal, message) end end end end # Check if we're in the driver context. # # Returns true if in driver thread, false if not. def in_driver_context? Thread.current == @thread end # Clear all events for this Driver. # # Returns nothing. def clear_events @events.clear end # Shutdown the DriverEventQueue threads. # # Returns nothing. def shutdown @events.shutdown end # Queue an asynchronous message. # # name - The Symbol name of the operation. # args - An optional Array of arguments. # # Returns nothing. def message(name, args = []) @events.push(DriverOperation.new(@task, name, args)) end # Create and schedule a new DriverEvent. # # condition - The Condition. # delay - The Numeric number of seconds to delay (default: interval # defined in condition). # # 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 end ruby-god-0.13.3/lib/god/errors.rb000066400000000000000000000006101226052465500165120ustar00rootroot00000000000000module 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 ruby-god-0.13.3/lib/god/event_handler.rb000066400000000000000000000050501226052465500200170ustar00rootroot00000000000000module God 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 when /darwin/i, /bsd/i require 'god/event_handlers/kqueue_handler' @@handler = KQueueHandler when /linux/i require 'god/event_handlers/netlink_handler' @@handler = NetlinkHandler else raise NotImplementedError, "Platform not supported for EventHandler" end @@loaded = true rescue Exception require 'god/event_handlers/dummy_handler' @@handler = DummyHandler @@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) if watching_pid? pid running = ::Process.kill(0, pid.to_i) rescue false @@actions[pid].delete(event) @@handler.register_process(pid, @@actions[pid].keys) if running @@actions.delete(pid) if @@actions[pid].empty? 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 = Thread.new do loop do begin @@handler.handle_events rescue Exception => e message = format("Unhandled exception (%s): %s\n%s", e.class, e.message, e.backtrace.join("\n")) applog(nil, :fatal, message) end end end # do a real test to make sure events are working properly @@loaded = self.operational? end def self.stop @@thread.kill if @@thread 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) ::Process.waitpid(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 ruby-god-0.13.3/lib/god/event_handlers/000077500000000000000000000000001226052465500176555ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/event_handlers/dummy_handler.rb000066400000000000000000000003341226052465500230320ustar00rootroot00000000000000module God class DummyHandler EVENT_SYSTEM = "none" def self.register_process(pid, events) raise NotImplementedError end def self.handle_events raise NotImplementedError end end end ruby-god-0.13.3/lib/god/event_handlers/kqueue_handler.rb000066400000000000000000000005071226052465500232000ustar00rootroot00000000000000require 'kqueue_handler_ext' 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) end end end end ruby-god-0.13.3/lib/god/event_handlers/netlink_handler.rb000066400000000000000000000004741226052465500233500ustar00rootroot00000000000000require 'netlink_handler_ext' 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 # matches the list we're looking for -- Kev end end end ruby-god-0.13.3/lib/god/logger.rb000066400000000000000000000051601226052465500164620ustar00rootroot00000000000000module God class Logger < SimpleLogger attr_accessor :logs class << self attr_accessor :syslog end self.syslog = defined?(Syslog) # Instantiate a new Logger object def initialize(io = $stdout) super(io) self.logs = {} @mutex = Mutex.new @capture = nil @spool = Time.now - 10 @templogio = StringIO.new @templog = SimpleLogger.new(@templogio) @templog.level = Logger::INFO end def level=(lev) SysLogger.level = SimpleLogger::CONSTANT_TO_SYMBOL[lev] if Logger.syslog super(lev) 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] # +text+ is the String message # # Returns nothing 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 if @capture || (watch && (Time.now - @spool < 2)) @mutex.synchronize do @templogio.truncate(0) @templogio.rewind @templog.send(level, text) message = @templogio.string.dup if @capture @capture.puts(message) else self.logs[watch.name] << [Time.now, message] end end end # send to regular logger self.send(level, text) # send to syslog SysLogger.log(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 # # Returns String 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 @spool = Time.now self.logs[watch_name].select do |x| x.first > since end.map do |x| x[1] end.join end end # private # Enable capturing of log # # Returns nothing def start_capture @mutex.synchronize do @capture = StringIO.new end end # Disable capturing of log and return what was captured since # capturing was enabled with Logger#start_capture # # Returns String def finish_capture @mutex.synchronize do cap = @capture.string if @capture @capture = nil cap end end end end ruby-god-0.13.3/lib/god/metric.rb000066400000000000000000000046541226052465500164750ustar00rootroot00000000000000module God # Metrics are responsible for holding watch conditions. An instance of # Metric is yielded to blocks in the start_if, restart_if, stop_if, and # transition methods. class Metric # The Watch. attr_accessor :watch # The destination Hash in canonical hash form. Example: # { true => :up, false => :restart} attr_accessor :destination # The Array of Condition instances. attr_accessor :conditions # Initialize a new Metric. # # watch - The Watch. # destination - The optional destination Hash in canonical hash form. def initialize(watch, destination = nil) self.watch = watch self.destination = destination self.conditions = [] end # Public: Instantiate the given Condition and pass it into the optional # block. Attributes of the condition must be set in the config file. # # kind - The Symbol name of the condition. # # Returns nothing. def condition(kind) # Create the condition. begin c = Condition.generate(kind, self.watch) rescue NoSuchConditionError => e abort e.message end # Send to block so config can set attributes. yield(c) if block_given? # Prepare 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 c.interval = self.watch.interval else 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 # Add the condition to the list. self.conditions << c end # Enable all of this Metric's conditions. Poll conditions will be # scheduled and event/trigger conditions will be registered. # # Returns nothing. def enable self.conditions.each do |c| self.watch.attach(c) end end # Disable all of this Metric's conditions. Poll conditions will be # halted and event/trigger conditions will be deregistered. # # Returns nothing. def disable self.conditions.each do |c| self.watch.detach(c) end end end end ruby-god-0.13.3/lib/god/process.rb000066400000000000000000000237611226052465500166700ustar00rootroot00000000000000module God class Process WRITES_PID = [:start, :restart] attr_accessor :name, :uid, :gid, :log, :log_cmd, :err_log, :err_log_cmd, :start, :stop, :restart, :unix_socket, :chroot, :env, :dir, :stop_timeout, :stop_signal, :umask def initialize self.log = '/dev/null' @pid_file = nil @tracking_pid = true @user_log = false @pid = nil @unix_socket = nil @log_cmd = nil @stop_timeout = God::STOP_TIMEOUT_DEFAULT @stop_signal = God::STOP_SIGNAL_DEFAULT end def alive? if self.pid System::Process.new(self.pid).exists? else false end end def file_writable?(file) pid = fork do begin uid_num = Etc.getpwnam(self.uid).uid if self.uid gid_num = Etc.getgrnam(self.gid).gid if self.gid ::Dir.chroot(self.chroot) if self.chroot ::Process.groups = [gid_num] if self.gid ::Process::Sys.setgid(gid_num) if self.gid ::Process::Sys.setuid(uid_num) if self.uid rescue ArgumentError, Errno::EPERM, Errno::ENOENT exit(1) end 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 # uid must exist if specified if self.uid begin Etc.getpwnam(self.uid) rescue ArgumentError valid = false applog(self, :error, "UID for '#{self.uid}' does not exist") end end # gid must exist if specified if self.gid begin Etc.getgrnam(self.gid) rescue ArgumentError valid = false applog(self, :error, "GID for '#{self.gid}' does not exist") end end # dir must exist and be a directory if specified if self.dir if !File.exist?(self.dir) valid = false applog(self, :error, "Specified directory '#{self.dir}' does not exist") elsif !File.directory?(self.dir) valid = false applog(self, :error, "Specified directory '#{self.dir}' is not a directory") 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) valid = false applog(self, :error, "Log file '#{self.log}' exists but is not writable by #{self.uid || Etc.getlogin}") end else unless file_writable?(File.dirname(self.log)) valid = false applog(self, :error, "Log directory '#{File.dirname(self.log)}' is not writable by #{self.uid || Etc.getlogin}") end end # chroot directory must exist and have /dev/null in it if self.chroot if !File.directory?(self.chroot) valid = false applog(self, :error, "CHROOT directory '#{self.chroot}' does not exist") end if !File.exist?(File.join(self.chroot, '/dev/null')) valid = false applog(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) # if value is nil, do the right thing if value @tracking_pid = false 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. # # Returns Integer(pid) or nil 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 else @pid end end # Send the given signal to this process. # # Returns nothing def signal(sig) sig = sig.to_i if sig.to_i != 0 applog(self, :info, "#{self.name} sending signal '#{sig}' to pid #{self.pid}") ::Process.kill(sig, self.pid) rescue nil 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(@stop_signal, pid) rescue nil applog(self, :info, "#{self.name} sent SIG#{@stop_signal}") # Poll to see if it's dead @stop_timeout.times do begin ::Process.kill(0, pid) rescue Errno::ESRCH # It died. Good. applog(self, :info, "#{self.name} process stopped") return end sleep 1 end ::Process.kill('KILL', pid) rescue nil applog(self, :warn, "#{self.name} still alive after #{@stop_timeout}s; sent SIGKILL") end end if command.kind_of?(String) pid = nil if [:start, :restart].include?(action) && @tracking_pid # double fork god-daemonized processes # we don't want to wait for them to finish r, w = IO.pipe begin opid = fork do STDOUT.reopen(w) r.close pid = self.spawn(command) puts pid.to_s # send pid back to forker end ::Process.waitpid(opid, 0) w.close pid = r.gets.chomp ensure # make sure the file descriptors get closed no matter what r.close rescue nil w.close rescue nil end else # single fork self-daemonizing processes # we want to wait for them to finish 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 elsif command.kind_of?(Proc) # lambda command command.call else raise NotImplementedError end end # Fork/exec the given command, returns immediately # +command+ is the String containing the shell command # # Returns nothing def spawn(command) fork do File.umask self.umask if self.umask uid_num = Etc.getpwnam(self.uid).uid if self.uid gid_num = Etc.getgrnam(self.gid).gid if self.gid ::Dir.chroot(self.chroot) if self.chroot ::Process.setsid ::Process.groups = [gid_num] if self.gid ::Process::Sys.setgid(gid_num) if self.gid ::Process::Sys.setuid(uid_num) if self.uid self.dir ||= '/' Dir.chdir self.dir $0 = command STDIN.reopen "/dev/null" if self.log_cmd STDOUT.reopen IO.popen(self.log_cmd, "a") else STDOUT.reopen file_in_chroot(self.log), "a" end if err_log_cmd STDERR.reopen IO.popen(err_log_cmd, "a") elsif err_log && (log_cmd || err_log != log) STDERR.reopen file_in_chroot(err_log), "a" else STDERR.reopen STDOUT end # close any other file descriptors 3.upto(256){|fd| IO::new(fd).close rescue nil} if self.env && self.env.is_a?(Hash) self.env.each do |(key, value)| ENV[key] = value.to_s end end exec command unless command.empty? end end # Ensure that a stop command actually stops the process. Force kill # if necessary. # # Returns nothing def ensure_stop applog(self, :warn, "#{self.name} ensuring stop...") unless self.pid applog(self, :warn, "#{self.name} stop called but pid is uknown") return end # Poll to see if it's dead @stop_timeout.times do begin ::Process.kill(0, self.pid) rescue Errno::ESRCH # It died. Good. return end sleep 1 end # last resort ::Process.kill('KILL', self.pid) rescue nil applog(self, :warn, "#{self.name} still alive after #{@stop_timeout}s; sent SIGKILL") end private def file_in_chroot(file) return file unless self.chroot file.gsub(/^#{Regexp.escape(File.expand_path(self.chroot))}/, '') end end end ruby-god-0.13.3/lib/god/registry.rb000066400000000000000000000007061226052465500170540ustar00rootroot00000000000000module 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 end end ruby-god-0.13.3/lib/god/simple_logger.rb000066400000000000000000000022261226052465500200330ustar00rootroot00000000000000module 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'} CONSTANT_TO_SYMBOL = { 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 ruby-god-0.13.3/lib/god/socket.rb000066400000000000000000000052031226052465500164710ustar00rootroot00000000000000require 'drb' 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 # # Returns String (file location) def self.socket_file(port) "/tmp/god.#{port}.sock" end # The address of the socket for a given port # +port+ is the port number # # Returns String (drb address) 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, user = nil, group = nil, perm = nil) @port = port @user = user @group = group @perm = perm 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 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. # # Returns nothing def start begin @drb ||= DRb.start_service(self.socket, self) applog(nil, :info, "Started on #{DRb.uri}") rescue Errno::EADDRINUSE applog(nil, :info, "Socket already in use") DRb.start_service server = DRbObject.new(nil, self.socket) begin Timeout.timeout(5) do server.ping end abort "Socket #{self.socket} already in use by another instance of god" rescue StandardError, Timeout::Error applog(nil, :info, "Socket is stale, reopening") File.delete(self.socket_file) rescue nil @drb ||= DRb.start_service(self.socket, self) applog(nil, :info, "Started on #{DRb.uri}") end end if File.exists?(self.socket_file) uid = Etc.getpwnam(@user).uid if @user gid = Etc.getgrnam(@group).gid if @group File.chmod(Integer(@perm), socket_file) if @perm File.chown(uid, gid, socket_file) if uid or gid end end end end ruby-god-0.13.3/lib/god/sugar.rb000066400000000000000000000020461226052465500163240ustar00rootroot00000000000000class Numeric # Public: Units of seconds. def seconds self end # Public: Units of seconds. alias :second :seconds # Public: Units of minutes (60 seconds). def minutes self * 60 end # Public: Units of minutes (60 seconds). alias :minute :minutes # Public: Units of hours (3600 seconds). def hours self * 3600 end # Public: Units of hours (3600 seconds). alias :hour :hours # Public: Units of days (86400 seconds). def days self * 86400 end # Public: Units of days (86400 seconds). alias :day :days # Units of kilobytes. def kilobytes self end # Units of kilobytes. alias :kilobyte :kilobytes # Units of megabytes (1024 kilobytes). def megabytes self * 1024 end # Units of megabytes (1024 kilobytes). alias :megabyte :megabytes # Units of gigabytes (1,048,576 kilobytes). def gigabytes self * (1024 ** 2) end # Units of gigabytes (1,048,576 kilobytes). alias :gigabyte :gigabytes # Units of percent. e.g. 50.percent. def percent self end end ruby-god-0.13.3/lib/god/sys_logger.rb000066400000000000000000000023021226052465500173530ustar00rootroot00000000000000begin require 'syslog' # Ensure that Syslog is open begin Syslog.open('god') rescue RuntimeError Syslog.reopen('god') end Syslog.info("Syslog enabled.") module God class SysLogger SYMBOL_EQUIVALENTS = { :fatal => Syslog::LOG_CRIT, :error => Syslog::LOG_ERR, :warn => Syslog::LOG_WARNING, :info => Syslog::LOG_INFO, :debug => Syslog::LOG_DEBUG } # Set the log level # +level+ is the Symbol level to set as maximum. One of: # [:fatal | :error | :warn | :info | :debug ] # # Returns Nothing def self.level=(level) Syslog.mask = Syslog::LOG_UPTO(SYMBOL_EQUIVALENTS[level]) end # Log a message to syslog. # +level+ is the Symbol level of the message. One of: # [:fatal | :error | :warn | :info | :debug ] # +text+ is the String text of the message # # Returns Nothing def self.log(level, text) Syslog.log(SYMBOL_EQUIVALENTS[level], '%s', text) end end end rescue Object => e puts "Syslog could not be enabled: #{e.message}" end ruby-god-0.13.3/lib/god/system/000077500000000000000000000000001226052465500162005ustar00rootroot00000000000000ruby-god-0.13.3/lib/god/system/portable_poller.rb000066400000000000000000000015121226052465500217110ustar00rootroot00000000000000module God module System class PortablePoller def initialize(pid) @pid = pid end # Memory usage in kilobytes (resident set size) 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 end end end end ruby-god-0.13.3/lib/god/system/process.rb000066400000000000000000000016671226052465500202150ustar00rootroot00000000000000module God module System class Process def self.fetch_system_poller @@poller ||= if SlashProcPoller.usable? SlashProcPoller else PortablePoller end end def initialize(pid) @pid = pid.to_i @poller = self.class.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 else PortablePoller end end end end end ruby-god-0.13.3/lib/god/system/slash_proc_poller.rb000066400000000000000000000054521226052465500222450ustar00rootroot00000000000000module God module System 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. def self.usable? RequiredPaths.all? do |path| test(?r, path) && readable?(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 total_time = stats[:utime].to_i + stats[:stime].to_i # in jiffies seconds = uptime - stats[:starttime].to_i / @@hertz if seconds == 0 0 else ((total_time * 1000 / @@hertz) / seconds) / 10 end rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end private # Some systems (CentOS?) have a /proc, but they can hang when trying to # read from them. Try to use this sparingly as it is expensive. def self.readable?(path) begin timeout(1) { File.read(path) } rescue Timeout::Error false end end # 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], stats[:session], stats[:tty_nr], stats[:tpgid], stats[:flags], stats[:minflt], stats[:cminflt], stats[:majflt], stats[:cmajflt], stats[:utime], stats[:stime], stats[:cutime], stats[:cstime], stats[:priority], stats[:nice], _, stats[:itrealvalue], stats[:starttime], stats[:vsize], stats[:rss], stats[:rlim], stats[:startcode], stats[:endcode], stats[:startstack], stats[:kstkesp], stats[:kstkeip], stats[:signal], stats[:blocked], stats[:sigignore], stats[:sigcatch], stats[:wchan], stats[:nswap], stats[:cnswap], stats[:exit_signal], stats[:processor], stats[:rt_priority], stats[:policy] = File.read("/proc/#{@pid}/stat").scan(/\(.*?\)|\w+/) stats end end end end ruby-god-0.13.3/lib/god/task.rb000066400000000000000000000351061226052465500161500ustar00rootroot00000000000000module God class Task # Public: Gets/Sets the String name of the task. attr_accessor :name # Public: Gets/Sets the Numeric default interval to be used between poll # events. attr_accessor :interval # Public: Gets/Sets the String group name of the task. attr_accessor :group # Public: Gets/Sets the Array of Symbol valid states for the state machine. attr_accessor :valid_states # Public: Gets/Sets the Symbol initial state of the state machine. attr_accessor :initial_state # Gets/Sets the Driver for this task. attr_accessor :driver # Public: Sets whether the task should autostart when god starts. Defaults # to true (enabled). attr_writer :autostart # Returns true if autostart is enabled, false if not. 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 # Initialize the metrics to an empty state. # # Returns nothing. def prepare self.valid_states.each do |state| self.metrics[state] ||= [] end end # Verify that the minimum set of configuration requirements has been met. # # Returns true if valid, false if not. def valid? valid = true # A name must be specified. if self.name.nil? valid = false applog(self, :error, "No name String was specified.") end # Valid states must be specified. if self.valid_states.nil? valid = false applog(self, :error, "No valid_states Array or Symbols was specified.") end # An initial state must be specified. if self.initial_state.nil? valid = false applog(self, :error, "No initial_state Symbol was specified.") end valid end ########################################################################### # # Advanced mode # ########################################################################### # Convert the given input into canonical hash form which looks like: # # { true => :state } or { true => :state, false => :otherstate } # # to - The Symbol or Hash destination. # # Returns the canonical Hash. def canonical_hash_form(to) to.instance_of?(Symbol) ? {true => to} : to end # Public: Define a transition handler which consists of a set of conditions # # start_states - The Symbol or Array of Symbols start state(s). # end_states - The Symbol or Hash end states. # # Yields the Metric for this transition. # # Returns nothing. 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 task, 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) else # Add an :always condition if no block was given. m.condition(:always) do |c| 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 # Public: Define a lifecycle handler. Conditions that belong to a # lifecycle are active as long as the process is being monitored. # # Returns nothing. def lifecycle # Create a new metric to hold the task 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 given state. # # to_state - The Symbol representing the state to move to. # # Returns this Task. def move(to_state) if !self.driver.in_driver_context? # 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. # # condition - The Condition. # # Returns nothing. def trigger(condition) self.driver.message(:handle_event, [condition]) end def signal(sig) # noop 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 - The Symbol action. # c - The Condition. # # Returns this Task. def action(a, c = nil) if !self.driver.in_driver_context? # 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 end end end end ########################################################################### # # Events # ########################################################################### def attach(condition) case condition when PollCondition self.driver.schedule(condition, 0) when EventCondition, TriggerCondition condition.register end end def detach(condition) case condition when PollCondition condition.reset when EventCondition, TriggerCondition condition.deregister end end ########################################################################### # # Registration # ########################################################################### def register! # override if necessary end def unregister! driver.shutdown end ########################################################################### # # Handlers # ########################################################################### # Evaluate and handle the given poll condition. Handles logging # notifications, and moving to the new state if necessary. # # condition - The Condition to handle. # # Returns nothing. def handle_poll(condition) # Lookup metric. metric = self.directory[condition] # Run the test. begin result = condition.test rescue Object => e cname = condition.class.to_s.split('::').last message = format("Unhandled exception in %s condition - (%s): %s\n%s", cname, e.class, e.message, e.backtrace.join("\n")) applog(self, :error, message) result = false end # Log. messages = self.log_line(self, metric, condition, result) # Notify. if result && condition.notify self.notify(condition, messages.last) end # After-condition. condition.after # Get the destination. dest = if result && condition.transition # Condition override. condition.transition else # Regular. metric.destination && metric.destination[result] end # Transition or reschedule. if dest # Transition. begin self.move(dest) rescue EventRegistrationFailedError msg = self.name + ' Event registration failed, moving back to previous state' applog(self, :info, msg) dest = self.state retry end else # Reschedule. 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 - The Condition to handle. # # Returns nothing. def handle_event(condition) # Lookup metric. metric = self.directory[condition] # Log. messages = self.log_line(self, metric, condition, true) # Notify. if condition.notify self.notify(condition, messages.last) end # Get the destination. dest = if condition.transition # Condition override. condition.transition else # Regular. metric.destination && metric.destination[true] end if dest self.move(dest) end end # Determine whether a trigger happened. # # metric - The Metric. # result - The Boolean result from the condition's test. # # Returns Boolean def trigger?(metric, result) metric.destination && metric.destination[result] end # Log info about the condition and return the list of messages logged. # # watch - The Watch. # metric - The Metric. # condition - The Condition. # result - The Boolean result of the condition test evaluation. # # Returns the Array of String messages. def log_line(watch, metric, condition, result) 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| messages << "#{watch.name} #{status} #{condition_info} (#{condition.base_name})" applog(watch, :info, messages.last) end else messages << "#{watch.name} #{status} (#{condition.base_name})" 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 - The Metric. # condition - The Condition. # # Returns the formatted String. def dest_desc(metric, condition) if condition.transition {true => condition.transition}.inspect else if metric.destination metric.destination.inspect else 'none' end end end # Notify all recipients of the given condition with the specified message. # # condition - The Condition. # message - The String message to send. # # Returns nothing. def notify(condition, message) spec = Contact.normalize(condition.notify) unmatched = [] # Resolve contacts. resolved_contacts = spec[:contacts].inject([]) do |acc, contact_name_or_group| cons = Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group]) unmatched << contact_name_or_group if cons.empty? 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' begin 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 % []) rescue Exception => e applog(condition.watch, :error, "#{e.message} #{e.backtrace}") msg = "#{condition.watch.name} Failed to deliver notification for contact: #{c.name} (#{c.base_name})" applog(condition.watch, :error, msg % []) end end end end end ruby-god-0.13.3/lib/god/timeline.rb000066400000000000000000000007331226052465500170120ustar00rootroot00000000000000module God class Timeline < Array # Instantiate a new Timeline # +max_size+ is the maximum size to which the timeline should grow # # Returns Timeline def initialize(max_size) super() @max_size = max_size end # Push a value onto the Timeline # +val+ is the value to push # # Returns Timeline def push(val) self.concat([val]) shift if size > @max_size end alias_method :<<, :push end end ruby-god-0.13.3/lib/god/trigger.rb000066400000000000000000000016041226052465500166450ustar00rootroot00000000000000module 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 ruby-god-0.13.3/lib/god/watch.rb000066400000000000000000000254701226052465500163170ustar00rootroot00000000000000require 'etc' require 'forwardable' module God # The Watch class is a specialized Task that handles standard process # workflows. It has four states: init, up, start, and restart. class Watch < Task # The Array of Symbol valid task states. VALID_STATES = [:init, :up, :start, :restart] # The Sybmol initial state. INITIAL_STATE = :init # Public: The grace period for this process (seconds). attr_accessor :grace # Public: The start grace period (seconds). attr_accessor :start_grace # Public: The stop grace period (seconds). attr_accessor :stop_grace # Public: The restart grace period (seconds). attr_accessor :restart_grace # Public: God::Process delegators. See lib/god/process.rb for docs. extend Forwardable def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart, :dir, :name=, :uid=, :gid=, :start=, :stop=, :restart=, :dir=, :pid_file, :pid_file=, :log, :log=, :log_cmd, :log_cmd=, :err_log, :err_log=, :err_log_cmd, :err_log_cmd=, :alive?, :pid, :unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env=, :signal, :stop_timeout=, :stop_signal=, :umask, :umask= # Initialize a new Watch instance. def initialize super # This God::Process instance holds information specific to the process. @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 # Is this Watch valid? # # Returns true if the Watch is valid, false if not. def valid? super && @process.valid? end ########################################################################### # # Behavior # ########################################################################### # Public: Add a behavior to this Watch. See lib/god/behavior.rb. # # kind - The Symbol name of the Behavior to add. # # Yields the newly instantiated Behavior. # # Returns nothing. def behavior(kind) # Create the behavior. begin b = Behavior.generate(kind, self) 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 ########################################################################### # # Quickstart mode # ########################################################################### # Default Integer interval at which keepalive will runn poll checks. DEFAULT_KEEPALIVE_INTERVAL = 5.seconds # Default Integer or Array of Integers specification of how many times the # memory condition must fail before triggering. DEFAULT_KEEPALIVE_MEMORY_TIMES = [3, 5] # Default Integer or Array of Integers specification of how many times the # CPU condition must fail before triggering. DEFAULT_KEEPALIVE_CPU_TIMES = [3, 5] # Public: A set of conditions for easily getting started with simple watch # scenarios. Keepalive is intended for use by beginners or on processes # that do not need very sophisticated monitoring. # # If events are enabled, it will use the :process_exit event to determine # if a process fails. Otherwise it will use the :process_running poll. # # options - The option Hash. Possible values are: # :interval - The Integer number of seconds on which to poll # for process status. Affects CPU, memory, and # :process_running conditions (if used). # Default: 5.seconds. # :memory_max - The Integer memory max. A bare integer means # kilobytes. You may use Numeric.kilobytes, # Numeric#megabytes, and Numeric#gigabytes to # makes things more clear. # :memory_times - If :memory_max is set, :memory_times can be # set to either an Integer or a 2 element # Integer Array to specify the number of times # the memory condition must fail. Examples: # 3 (three times), [3, 5] (three out of any five # checks). Default: [3, 5]. # :cpu_max - The Integer CPU percentage max. Range is # 0 to 100. You may use the Numberic#percent # sugar to clarify e.g. 50.percent. # :cpu_times - If :cpu_max is set, :cpu_times can be # set to either an Integer or a 2 element # Integer Array to specify the number of times # the memory condition must fail. Examples: # 3 (three times), [3, 5] (three out of any five # checks). Default: [3, 5]. def keepalive(options = {}) if God::EventHandler.loaded? self.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.interval = options[:interval] || DEFAULT_KEEPALIVE_INTERVAL c.running = true end end self.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.interval = options[:interval] || DEFAULT_KEEPALIVE_INTERVAL c.running = true end end self.transition(:up, :start) do |on| on.condition(:process_exits) end else self.start_if do |start| start.condition(:process_running) do |c| c.interval = options[:interval] || DEFAULT_KEEPALIVE_INTERVAL c.running = false end end end self.restart_if do |restart| if options[:memory_max] restart.condition(:memory_usage) do |c| c.interval = options[:interval] || DEFAULT_KEEPALIVE_INTERVAL c.above = options[:memory_max] c.times = options[:memory_times] || DEFAULT_KEEPALIVE_MEMORY_TIMES end end if options[:cpu_max] restart.condition(:cpu_usage) do |c| c.interval = options[:interval] || DEFAULT_KEEPALIVE_INTERVAL c.above = options[:cpu_max] c.times = options[:cpu_times] || DEFAULT_KEEPALIVE_CPU_TIMES end end end end ########################################################################### # # Simple mode # ########################################################################### # Public: Start the process if any of the given conditions are triggered. # # Yields the Metric upon which conditions can be added. # # Returns nothing. def start_if self.transition(:up, :start) do |on| yield(on) end end # Public: Restart the process if any of the given conditions are triggered. # # Yields the Metric upon which conditions can be added. # # Returns nothing. def restart_if self.transition(:up, :restart) do |on| yield(on) end end # Public: Stop the process if any of the given conditions are triggered. # # Yields the Metric upon which conditions can be added. # # Returns nothing. def stop_if self.transition(:up, :stop) do |on| yield(on) end end ########################################################################### # # Lifecycle # ########################################################################### # Enable monitoring. Start at the first available of the init or up states. # # Returns nothing. def monitor if !self.metrics[:init].empty? self.move(:init) else self.move(:up) end end ########################################################################### # # Actions # ########################################################################### # Perform an action. # # a - The Symbol action to perform. One of :start, :restart, :stop. # c - The Condition. # # Returns this Watch. def action(a, c = nil) if !self.driver.in_driver_context? # 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) sleep(self.start_grace + self.grace) when :restart if self.restart call_action(c, :restart) else action(:stop, c) action(:start, c) end sleep(self.restart_grace + self.grace) when :stop call_action(c, :stop) sleep(self.stop_grace + self.grace) end end self end # Perform the specifics of the action. # # condition - The Condition. # action - The Symbol action. # # Returns nothing. def call_action(condition, action) # Before. before_items = self.behaviors before_items += [condition] if condition before_items.each do |b| info = b.send("before_#{action}") if info msg = "#{self.name} before_#{action}: #{info} (#{b.base_name})" 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 # Execute. @process.call_action(action) # After. after_items = self.behaviors after_items += [condition] if condition after_items.each do |b| info = b.send("after_#{action}") if info msg = "#{self.name} after_#{action}: #{info} (#{b.base_name})" applog(self, :info, msg) end end end ########################################################################### # # Registration # ########################################################################### # Register the Process in the global process registry. # # Returns nothing. def register! God.registry.add(@process) end # Unregister the Process in the global process registry. # # Returns nothing. def unregister! God.registry.remove(@process) super end end end ruby-god-0.13.3/metadata.yml000066400000000000000000000240231226052465500156410ustar00rootroot00000000000000--- !ruby/object:Gem::Specification name: god version: !ruby/object:Gem::Version prerelease: false segments: - 0 - 13 - 3 version: 0.13.3 platform: ruby authors: - Tom Preston-Werner - Kevin Clark - Eric Lindvall autorequire: bindir: bin cert_chain: [] date: 2013-09-25 00:00:00 -07:00 default_executable: dependencies: - !ruby/object:Gem::Dependency type: :development version_requirements: &id001 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 1 - 6 version: "1.6" name: json requirement: *id001 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id002 !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 0 version: "0" name: rake requirement: *id002 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id003 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 3 - 10 version: "3.10" name: rdoc requirement: *id003 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id004 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 4 - 0 version: "4.0" name: twitter requirement: *id004 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id005 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 0 - 3 version: "0.3" name: prowly requirement: *id005 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id006 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 0 - 5 version: "0.5" name: xmpp4r requirement: *id006 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id007 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 0 - 0 - 3 version: 0.0.3 name: dike requirement: *id007 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id008 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 0 - 9 version: "0.9" name: rcov requirement: *id008 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id009 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 1 - 1 version: "1.1" name: daemons requirement: *id009 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id010 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 0 - 10 version: "0.10" name: mocha requirement: *id010 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id011 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 1 - 3 - 1 version: 1.3.1 name: gollum requirement: *id011 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id012 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 3 - 1 - 7 version: 3.1.7 name: airbrake requirement: *id012 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id013 !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version segments: - 1 - 5 - 0 version: 1.5.0 name: nokogiri requirement: *id013 prerelease: false - !ruby/object:Gem::Dependency type: :development version_requirements: &id014 !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 2 - 3 - 10 version: 2.3.10 - - < - !ruby/object:Gem::Version segments: - 4 - 0 - 0 version: 4.0.0 name: activesupport requirement: *id014 prerelease: false description: An easy to configure, easy to extend monitoring framework written in Ruby. email: god-rb@googlegroups.com executables: - god extensions: - ext/god/extconf.rb extra_rdoc_files: - README.md files: - Announce.txt - Gemfile - History.txt - LICENSE - README.md - Rakefile - bin/god - doc/god.asciidoc - doc/intro.asciidoc - ext/god/.gitignore - ext/god/extconf.rb - ext/god/kqueue_handler.c - ext/god/netlink_handler.c - god.gemspec - lib/god.rb - lib/god/behavior.rb - lib/god/behaviors/clean_pid_file.rb - lib/god/behaviors/clean_unix_socket.rb - lib/god/behaviors/notify_when_flapping.rb - lib/god/cli/command.rb - lib/god/cli/run.rb - lib/god/cli/version.rb - lib/god/compat19.rb - lib/god/condition.rb - lib/god/conditions/always.rb - lib/god/conditions/complex.rb - lib/god/conditions/cpu_usage.rb - lib/god/conditions/degrading_lambda.rb - lib/god/conditions/disk_usage.rb - lib/god/conditions/file_mtime.rb - lib/god/conditions/file_touched.rb - lib/god/conditions/flapping.rb - lib/god/conditions/http_response_code.rb - lib/god/conditions/lambda.rb - lib/god/conditions/memory_usage.rb - lib/god/conditions/process_exits.rb - lib/god/conditions/process_running.rb - lib/god/conditions/socket_responding.rb - lib/god/conditions/tries.rb - lib/god/configurable.rb - lib/god/contact.rb - lib/god/contacts/airbrake.rb - lib/god/contacts/campfire.rb - lib/god/contacts/email.rb - lib/god/contacts/jabber.rb - lib/god/contacts/prowl.rb - lib/god/contacts/scout.rb - lib/god/contacts/twitter.rb - lib/god/contacts/webhook.rb - lib/god/driver.rb - lib/god/errors.rb - lib/god/event_handler.rb - lib/god/event_handlers/dummy_handler.rb - lib/god/event_handlers/kqueue_handler.rb - lib/god/event_handlers/netlink_handler.rb - lib/god/logger.rb - lib/god/metric.rb - lib/god/process.rb - lib/god/registry.rb - lib/god/simple_logger.rb - lib/god/socket.rb - lib/god/sugar.rb - lib/god/sys_logger.rb - lib/god/system/portable_poller.rb - lib/god/system/process.rb - lib/god/system/slash_proc_poller.rb - lib/god/task.rb - lib/god/timeline.rb - lib/god/trigger.rb - lib/god/watch.rb - test/configs/child_events/child_events.god - test/configs/child_events/simple_server.rb - test/configs/child_polls/child_polls.god - test/configs/child_polls/simple_server.rb - test/configs/complex/complex.god - test/configs/complex/simple_server.rb - test/configs/contact/contact.god - test/configs/contact/simple_server.rb - test/configs/daemon_events/daemon_events.god - test/configs/daemon_events/simple_server.rb - test/configs/daemon_events/simple_server_stop.rb - test/configs/daemon_polls/daemon_polls.god - test/configs/daemon_polls/simple_server.rb - test/configs/degrading_lambda/degrading_lambda.god - test/configs/degrading_lambda/tcp_server.rb - test/configs/keepalive/keepalive.god - test/configs/keepalive/keepalive.rb - test/configs/lifecycle/lifecycle.god - test/configs/matias/matias.god - test/configs/real.rb - test/configs/running_load/running_load.god - test/configs/stop_options/simple_server.rb - test/configs/stop_options/stop_options.god - test/configs/stress/simple_server.rb - test/configs/stress/stress.god - test/configs/task/logs/.placeholder - test/configs/task/task.god - test/configs/test.rb - test/helper.rb - test/suite.rb - test/test_airbrake.rb - test/test_behavior.rb - test/test_campfire.rb - test/test_condition.rb - test/test_conditions_disk_usage.rb - test/test_conditions_http_response_code.rb - test/test_conditions_process_running.rb - test/test_conditions_socket_responding.rb - test/test_conditions_tries.rb - test/test_contact.rb - test/test_driver.rb - test/test_email.rb - test/test_event_handler.rb - test/test_god.rb - test/test_handlers_kqueue_handler.rb - test/test_jabber.rb - test/test_logger.rb - test/test_metric.rb - test/test_process.rb - test/test_prowl.rb - test/test_registry.rb - test/test_socket.rb - test/test_sugar.rb - test/test_system_portable_poller.rb - test/test_system_process.rb - test/test_task.rb - test/test_timeline.rb - test/test_trigger.rb - test/test_watch.rb - test/test_webhook.rb has_rdoc: true homepage: http://god.rubyforge.org/ licenses: [] post_install_message: rdoc_options: - --charset=UTF-8 require_paths: - lib - ext required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 0 version: "0" required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 0 version: "0" requirements: [] rubyforge_project: god rubygems_version: 1.3.6 signing_key: specification_version: 2 summary: Process monitoring framework. test_files: - test/test_airbrake.rb - test/test_behavior.rb - test/test_campfire.rb - test/test_condition.rb - test/test_conditions_disk_usage.rb - test/test_conditions_http_response_code.rb - test/test_conditions_process_running.rb - test/test_conditions_socket_responding.rb - test/test_conditions_tries.rb - test/test_contact.rb - test/test_driver.rb - test/test_email.rb - test/test_event_handler.rb - test/test_god.rb - test/test_handlers_kqueue_handler.rb - test/test_jabber.rb - test/test_logger.rb - test/test_metric.rb - test/test_process.rb - test/test_prowl.rb - test/test_registry.rb - test/test_socket.rb - test/test_sugar.rb - test/test_system_portable_poller.rb - test/test_system_process.rb - test/test_task.rb - test/test_timeline.rb - test/test_trigger.rb - test/test_watch.rb - test/test_webhook.rb ruby-god-0.13.3/test/000077500000000000000000000000001226052465500143145ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/000077500000000000000000000000001226052465500157445ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/child_events/000077500000000000000000000000001226052465500204135ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/child_events/child_events.god000066400000000000000000000020761226052465500235620ustar00rootroot00000000000000God.watch do |w| w.name = "child-events" 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 c.transition = :start end end # start if process is not running w.transition(:up, :start) do |on| on.condition(:process_exits) end # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| c.to_state = [:start, :restart] c.times = 5 c.within = 20.seconds c.transition = :unmonitored c.retry_in = 10.seconds c.retry_times = 2 c.retry_within = 5.minutes end end end ruby-god-0.13.3/test/configs/child_events/simple_server.rb000077500000000000000000000000661226052465500236240ustar00rootroot00000000000000#! /usr/bin/env ruby loop { puts 'server'; sleep 1 } ruby-god-0.13.3/test/configs/child_polls/000077500000000000000000000000001226052465500202405ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/child_polls/child_polls.god000066400000000000000000000014331226052465500232300ustar00rootroot00000000000000God.watch do |w| w.name = 'child-polls' 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| c.to_state = [:start, :restart] c.times = 3 c.within = 60.seconds c.transition = :unmonitored c.retry_in = 10.seconds c.retry_times = 2 c.retry_within = 5.minutes end end end ruby-god-0.13.3/test/configs/child_polls/simple_server.rb000077500000000000000000000002021226052465500234410ustar00rootroot00000000000000#! /usr/bin/env ruby data = '' loop do STDOUT.puts('server'); STDOUT.flush; 100000.times { data << 'x' } sleep 10 end ruby-god-0.13.3/test/configs/complex/000077500000000000000000000000001226052465500174135ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/complex/complex.god000066400000000000000000000025541226052465500215630ustar00rootroot00000000000000God.watch do |w| w.name = "complex" 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 c.transition = :start end end # start if process is not running 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| cc.and(:cpu_usage) do |c| 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| c.to_state = [:start, :restart] c.times = 5 c.within = 20.seconds c.transition = :unmonitored c.retry_in = 10.seconds c.retry_times = 2 c.retry_within = 5.minutes end end end ruby-god-0.13.3/test/configs/complex/simple_server.rb000077500000000000000000000000661226052465500226240ustar00rootroot00000000000000#! /usr/bin/env ruby loop { puts 'server'; sleep 1 } ruby-god-0.13.3/test/configs/contact/000077500000000000000000000000001226052465500173775ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/contact/contact.god000066400000000000000000000051731226052465500215330ustar00rootroot00000000000000# God::Contacts::Campfire.defaults do |d| # d.subdomain = 'github' # d.token = '9fb768e421975cc1c6ff3f4f8306f890cb46e24f' # d.room = 'Notices' # d.ssl = true # end # # God.contact(:campfire) do |c| # c.name = 'tom4' # end # God.contact(:email) do |c| # c.name = 'tom' # c.group = 'developers' # c.to_email = 'tom@lepton.local' # c.from_email = 'god@github.com' # c.from_name = 'God' # c.delivery_method = :sendmail # end # God.contact(:email) do |c| # c.name = 'tom' # c.group = 'developers' # c.to_email = 'tom@mojombo.com' # c.from_email = 'god@github.com' # c.from_name = 'God' # c.server_host = 'smtp.rs.github.com' # end # God.contact(:prowl) do |c| # c.name = 'tom3' # c.apikey = 'f0fc8e1f3121672686337a631527eac2f1b6031c' # c.group = 'developers' # end # God.contact(:twitter) do |c| # c.name = 'tom6' # c.consumer_token = 'gOhjax6s0L3mLeaTtBWPw' # c.consumer_secret = 'yz4gpAVXJHKxvsGK85tEyzQJ7o2FEy27H1KEWL75jfA' # c.access_token = '17376380-qS391nCrgaP4HKXAmZtM38gB56xUXMhx1NYbjT6mQ' # c.access_secret = 'uMwCDeU4OXlEBWFQBc3KwGyY8OdWCtAV0Jg5KVB0' # end # God.contact(:scout) do |c| # c.name = 'tom5' # c.client_key = '583a51b5-acbc-2421-a830-b6f3f8e4b04e' # c.plugin_id = '230641' # end # God.contact(:webhook) do |c| # c.name = 'tom' # c.url = "http://www.postbin.org/wk7guh" # end # God.contact(:jabber) do |c| # c.name = 'tom' # c.host = 'talk.google.com' # c.to_jid = 'mojombo@jabber.org' # c.from_jid = 'mojombo@gmail.com' # c.password = 'secret' # end God.watch do |w| w.name = "contact" w.interval = 5.seconds w.start = "ruby " + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) 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 c.transition = :start end end # start if process is not running w.transition(:up, :start) do |on| on.condition(:process_exits) do |c| c.notify = {:contacts => ['tom'], :priority => 1, :category => 'product'} end end # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| c.to_state = [:start, :restart] c.times = 5 c.within = 20.seconds c.transition = :unmonitored c.retry_in = 10.seconds c.retry_times = 2 c.retry_within = 5.minutes end end end ruby-god-0.13.3/test/configs/contact/simple_server.rb000077500000000000000000000000661226052465500226100ustar00rootroot00000000000000#! /usr/bin/env ruby loop { puts 'server'; sleep 1 } ruby-god-0.13.3/test/configs/daemon_events/000077500000000000000000000000001226052465500205735ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/daemon_events/daemon_events.god000066400000000000000000000017341226052465500241220ustar00rootroot00000000000000God.watch do |w| w.name = "daemon-events" w.interval = 5.seconds w.start = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' start' w.stop = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server_stop.rb]) w.pid_file = '/var/run/daemon-events.pid' 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 c.transition = :start end end # start if process is not running w.transition(:up, :start) do |on| on.condition(:process_exits) end end ruby-god-0.13.3/test/configs/daemon_events/simple_server.rb000066400000000000000000000002451226052465500240000ustar00rootroot00000000000000require 'rubygems' require 'daemons' puts 'simple server ahoy!' Daemons.run_proc('daemon-events', {:dir_mode => :system}) do loop { puts 'server'; sleep 1 } end ruby-god-0.13.3/test/configs/daemon_events/simple_server_stop.rb000066400000000000000000000002641226052465500250460ustar00rootroot00000000000000# exit!(2) 3.times do puts 'waiting' sleep 1 end p ENV command = '/usr/local/bin/ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' stop' system(command) ruby-god-0.13.3/test/configs/daemon_polls/000077500000000000000000000000001226052465500204205ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/daemon_polls/daemon_polls.god000066400000000000000000000010061226052465500235640ustar00rootroot00000000000000God.watch do |w| w.name = "daemon-polls" w.interval = 5.seconds w.start = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' start' w.stop = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' stop' 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 end end end ruby-god-0.13.3/test/configs/daemon_polls/simple_server.rb000066400000000000000000000002361226052465500236250ustar00rootroot00000000000000require 'rubygems' require 'daemons' Daemons.run_proc('daemon-polls', {:dir_mode => :system}) do loop { STDOUT.puts('server'); STDOUT.flush; sleep 1 } end ruby-god-0.13.3/test/configs/degrading_lambda/000077500000000000000000000000001226052465500211705ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/degrading_lambda/degrading_lambda.god000066400000000000000000000012421226052465500251060ustar00rootroot00000000000000God.watch do |w| w.name = 'degrading-lambda' w.start = 'ruby ' + File.join(File.dirname(__FILE__), *%w[tcp_server.rb]) 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' c.lambda = lambda { begin sock = TCPSocket.open('127.0.0.1', 9090) sock.send "2\n", 0 retval = sock.gets puts "Retval is #{retval}" sock.close retval rescue false end } end end end ruby-god-0.13.3/test/configs/degrading_lambda/tcp_server.rb000066400000000000000000000005311226052465500236700ustar00rootroot00000000000000#! /usr/bin/env ruby require 'socket' server = TCPServer.new('127.0.0.1', 9090) while (session = server.accept) puts "Found a session" request = session.gets puts "Request: #{request}" time = request.to_i puts "Sleeping for #{time}" sleep time session.print "Slept for #{time} seconds" session.close puts "Session closed" end ruby-god-0.13.3/test/configs/keepalive/000077500000000000000000000000001226052465500177115ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/keepalive/keepalive.god000066400000000000000000000004671226052465500223600ustar00rootroot00000000000000God.watch do |w| w.name = 'keepalive' w.start = File.join(GOD_ROOT, *%w[test configs keepalive keepalive.rb]) w.log = File.join(GOD_ROOT, *%w[test configs keepalive keepalive.log]) w.keepalive(:interval => 5.seconds, :memory_max => 10.megabytes, :cpu_max => 30.percent) end ruby-god-0.13.3/test/configs/keepalive/keepalive.rb000077500000000000000000000002031226052465500222010ustar00rootroot00000000000000#! /usr/bin/env ruby data = '' loop do STDOUT.puts('server'); STDOUT.flush; 100000.times { data << 'x' } # sleep 10 end ruby-god-0.13.3/test/configs/lifecycle/000077500000000000000000000000001226052465500177035ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/lifecycle/lifecycle.god000066400000000000000000000010061226052465500223320ustar00rootroot00000000000000God::Contacts::Twitter.settings = { # this is for my 'mojombo2' twitter test account # feel free to use it for testing your conditions :username => 'mojombo@gmail.com', :password => 'gok9we3ot1av2e' } God.contact(:twitter) do |c| c.name = 'tom2' c.group = 'developers' end God.watch do |w| w.name = "lifecycle" w.interval = 5.seconds w.start = "/dev/null" # lifecycle w.lifecycle do |on| on.condition(:always) do |c| c.what = true c.notify = "tom2" end end end ruby-god-0.13.3/test/configs/matias/000077500000000000000000000000001226052465500172225ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/matias/matias.god000066400000000000000000000020721226052465500211740ustar00rootroot00000000000000$pid_file = "/tmp/matias.pid" God.task do |w| w.name = "watcher" 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| c.running = true c.pid_file = $pid_file end end # when process is up w.transition(:up, :down) do |on| # transition to 'start' if process goes down on.condition(:process_running) do |c| c.running = false c.pid_file = $pid_file end # send up info on.condition(:lambda) do |c| c.lambda = lambda do puts 'yay I am up' false end end end # when process is down w.transition(:down, :up) do |on| # transition to 'up' if process comes up on.condition(:process_running) do |c| c.running = true c.pid_file = $pid_file end # send down info on.condition(:lambda) do |c| c.lambda = lambda do puts 'boo I am down' false end end end end ruby-god-0.13.3/test/configs/real.rb000066400000000000000000000026411226052465500172170ustar00rootroot00000000000000if $0 == __FILE__ require File.join(File.dirname(__FILE__), *%w[.. .. lib god]) end RAILS_ROOT = "/Users/tom/dev/git/helloworld" God.watch do |w| w.name = "local-3000" w.interval = 5 # seconds 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| c.running = false 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| c.interval = 20 c.pid_file = pid_file c.above = (50 * 1024) # 50mb c.times = [3, 5] end restart.condition(:cpu_usage) do |c| c.interval = 10 c.pid_file = pid_file c.above = 10 # percent c.times = [3, 5] end end end # clear old session files # god.watch do |w| # w.name = "local-session-cleanup" # w.start = lambda do # Dir["#{RAILS_ROOT}/tmp/sessions/ruby_sess.*"].select do |f| # 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 # end ruby-god-0.13.3/test/configs/running_load/000077500000000000000000000000001226052465500204235ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/running_load/running_load.god000066400000000000000000000005061226052465500235760ustar00rootroot00000000000000God.watch do |w| w.name = 'running-load' w.start = '/Users/tom/dev/god/test/configs/child_polls/simple_server.rb' w.stop = '' w.interval = 5 w.grace = 2 w.uid = 'tom' w.gid = 'tom' w.group = 'test' w.start_if do |start| start.condition(:process_running) do |c| c.running = false end end end ruby-god-0.13.3/test/configs/stop_options/000077500000000000000000000000001226052465500205045ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/stop_options/simple_server.rb000077500000000000000000000001531226052465500237120ustar00rootroot00000000000000#! /usr/bin/env ruby trap :USR1 do end loop do STDOUT.puts('server'); STDOUT.flush; sleep 10 end ruby-god-0.13.3/test/configs/stop_options/stop_options.god000066400000000000000000000015131226052465500237370ustar00rootroot00000000000000God.watch do |w| w.name = 'stop-options' w.start = File.join(GOD_ROOT, *%w[test configs stop_options simple_server.rb]) w.stop_signal = 'USR1' w.stop_timeout = 5 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| c.to_state = [:start, :restart] c.times = 3 c.within = 60.seconds c.transition = :unmonitored c.retry_in = 10.seconds c.retry_times = 2 c.retry_within = 5.minutes end end end ruby-god-0.13.3/test/configs/stress/000077500000000000000000000000001226052465500172675ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/stress/simple_server.rb000066400000000000000000000000661226052465500224750ustar00rootroot00000000000000#! /usr/bin/env ruby loop { puts 'server'; sleep 1 } ruby-god-0.13.3/test/configs/stress/stress.god000066400000000000000000000005201226052465500213020ustar00rootroot00000000000000('01'..'08').each do |i| God.watch do |w| w.name = "stress-#{i}" w.start = "ruby " + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) w.interval = 0 w.grace = 2 w.group = 'test' w.start_if do |start| start.condition(:process_running) do |c| c.running = false end end end end ruby-god-0.13.3/test/configs/task/000077500000000000000000000000001226052465500167065ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/task/logs/000077500000000000000000000000001226052465500176525ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/task/logs/.placeholder000066400000000000000000000000001226052465500221230ustar00rootroot00000000000000ruby-god-0.13.3/test/configs/task/task.god000066400000000000000000000010261226052465500203420ustar00rootroot00000000000000LOG_DIR = File.join(File.dirname(__FILE__), *%w[logs]) God.task do |t| t.name = 'task' 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 Dir[File.join(LOG_DIR, '*.log')].size > 1 end end end end ruby-god-0.13.3/test/configs/test.rb000066400000000000000000000031321226052465500172470ustar00rootroot00000000000000ENV['GOD_TEST_RAILS_ROOT'] || abort("Set a rails root for testing in an environment variable called GOD_TEST_RAILS_ROOT") RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] port = 5000 God.watch do |w| w.name = "local-#{port}" w.interval = 5.seconds w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -p #{port} -d" w.restart = "mongrel_rails restart -P ./log/mongrel.pid -c #{RAILS_ROOT}" 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| c.running = true end end # start if process is not running 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| c.interval = 1 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 c.path = '/' c.code_is_not = 200 c.timeout = 10.seconds c.times = [3, 5] end end end ruby-god-0.13.3/test/helper.rb000066400000000000000000000064431226052465500161270ustar00rootroot00000000000000$:.unshift File.expand_path('../../lib', __FILE__) # For use/testing when no gem is installed # Use this flag to actually load all of the god infrastructure $load_god = true require File.join(File.dirname(__FILE__), *%w[.. lib god sys_logger]) require File.join(File.dirname(__FILE__), *%w[.. lib god]) God::EventHandler.load require 'test/unit' require 'set' include God if Process.uid != 0 and RbConfig::CONFIG['host_os'] == "linux" abort <<-EOF \n ********************************************************************* * * * You need to run these tests as root * * chroot and netlink (linux only) require it * * * ********************************************************************* EOF end begin require 'mocha/setup' rescue LoadError unless gems ||= false require 'rubygems' gems = true retry else abort "=> You need the Mocha gem to run these tests." end end module God module Conditions class FakeCondition < Condition def test true end end class FakePollCondition < PollCondition def test true end end class FakeEventCondition < EventCondition def register end def deregister end end end module Behaviors class FakeBehavior < Behavior def before_start 'foo' end def after_start 'bar' end end end module Contacts class FakeContact < Contact end class InvalidContact end end def self.reset self.watches = nil self.groups = nil self.server = nil self.inited = nil self.host = nil self.port = nil self.pid_file_directory = nil self.registry.reset end end def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil yield ensure $VERBOSE = old_verbose end LOG.instance_variable_set(:@io, StringIO.new()) def output_logs io = LOG.instance_variable_get(:@io) LOG.instance_variable_set(:@io, $stderr) yield ensure LOG.instance_variable_set(:@io, io) end # module Kernel # def abort(text) # raise SystemExit, text # end # def exit(code) # raise SystemExit, "Exit code: #{code}" # end # end module Test::Unit::Assertions def assert_abort assert_raise SystemExit do yield end end end # This allows you to be a good OOP citizen and honor encapsulation, but # still make calls to private methods (for testing) by doing # # obj.bypass.private_thingie(arg1, arg2) # # Which is easier on the eye than # # obj.send(:private_thingie, arg1, arg2) # class Object class Bypass instance_methods.each do |m| undef_method m unless m =~ /^(__|object_id)/ end def initialize(ref) @ref = ref end def method_missing(sym, *args) @ref.__send__(sym, *args) end end def bypass Bypass.new(self) end end # Make sure we return valid exit codes if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION >= "1.9" module Kernel alias :__at_exit :at_exit def at_exit(&block) __at_exit do exit_status = $!.status if $!.is_a?(SystemExit) block.call exit exit_status if exit_status end end end end ruby-god-0.13.3/test/suite.rb000066400000000000000000000001601226052465500157670ustar00rootroot00000000000000require 'test/unit' tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"] tests.each do |file| require file end ruby-god-0.13.3/test/test_airbrake.rb000066400000000000000000000006241226052465500174620ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + '/helper' class TestAirbrake < Test::Unit::TestCase def test_notify airbrake = God::Contacts::Airbrake.new airbrake.apikey = "put_your_apikey_here" airbrake.name = "Airbrake" Airbrake.expects(:notify).returns "123" airbrake.notify("Test message for airbrake", Time.now, "airbrake priority", "airbrake category", "") end end ruby-god-0.13.3/test/test_behavior.rb000066400000000000000000000010161226052465500174750ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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 SysLogger.expects(:log).with(:error, 'foo') assert !Behavior.allocate.bypass.complain('foo') end end ruby-god-0.13.3/test/test_campfire.rb000066400000000000000000000010261226052465500174650ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestCampfire < Test::Unit::TestCase def setup @campfire = God::Contacts::Campfire.new end def test_exists God::Contacts::Campfire end def test_notify @campfire.subdomain = 'github' @campfire.token = 'abc' @campfire.room = 'danger' time = Time.now body = "[#{time.strftime('%H:%M:%S')}] host - msg" Marshmallow::Connection.any_instance.expects(:speak).with('danger', body) @campfire.notify('msg', time, 'prio', 'cat', 'host') end end ruby-god-0.13.3/test/test_condition.rb000066400000000000000000000023421226052465500176670ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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 God::EventHandler.start Condition.generate(:process_exits, nil).class end ensure God::EventHandler.stop 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 end end ruby-god-0.13.3/test/test_conditions_disk_usage.rb000066400000000000000000000021211226052465500222430ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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') assert_equal false, c.valid? end def test_valid_should_return_false_if_no_mount_point_given c = Conditions::DiskUsage.new c.above = 90 c.watch = stub(:name => 'foo') assert_equal false, c.valid? 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 ruby-god-0.13.3/test/test_conditions_http_response_code.rb000066400000000000000000000063271226052465500240300ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestHttpResponseCode < Test::Unit::TestCase def valid_condition c = Conditions::HttpResponseCode.new() c.watch = stub(:name => 'foo') c.host = 'localhost' c.port = 8080 c.path = '/' c.timeout = 10 c.code_is = 200 c.times = 1 yield(c) if block_given? 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 assert !c.valid? end def test_valid_should_return_false_if_no_host_set c = valid_condition do |cc| cc.host = nil end 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.any_instance.expects(:start).yields(mock(:read_timeout= => nil, :get => mock(: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 cc.code_is_not = [200] end Net::HTTP.any_instance.expects(:start).yields(mock(:read_timeout= => nil, :get => mock(: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.any_instance.expects(:start).yields(mock(:read_timeout= => nil, :get => mock(: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 cc.code_is_not = [200] end Net::HTTP.any_instance.expects(:start).yields(mock(:read_timeout= => nil, :get => mock(: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.any_instance.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 cc.code_is_not = [200] end Net::HTTP.any_instance.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.any_instance.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 cc.code_is_not = [200] end Net::HTTP.any_instance.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] end Net::HTTP.any_instance.expects(:start).yields(stub(:read_timeout= => nil, :get => stub(:code => 200))).times(2) assert_equal false, c.test assert_equal true, c.test end end ruby-god-0.13.3/test/test_conditions_process_running.rb000066400000000000000000000020761226052465500233540ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestConditionsProcessRunning < Test::Unit::TestCase 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 => 99999999, :name => 'foo')) assert_equal !r, c.test 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 end ruby-god-0.13.3/test/test_conditions_socket_responding.rb000066400000000000000000000111341226052465500236510ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestConditionsSocketResponding < Test::Unit::TestCase # valid? def test_valid_should_return_false_if_no_options_set c = Conditions::SocketResponding.new c.watch = stub(:name => 'foo') assert_equal false, c.valid? end def test_valid_should_return_true_if_required_options_set_for_default c = Conditions::SocketResponding.new c.port = 443 assert_equal true, c.valid? end def test_valid_should_return_true_if_required_options_set_for_tcp c = Conditions::SocketResponding.new c.family = 'tcp' c.port = 443 assert_equal true, c.valid? end def test_valid_should_return_true_if_required_options_set_for_unix c = Conditions::SocketResponding.new c.path = 'some-path' c.family = 'unix' assert_equal true, c.valid? end def test_valid_should_return_true_if_family_is_tcp c = Conditions::SocketResponding.new c.port = 443 c.family = 'tcp' assert_equal true, c.valid? end def test_valid_should_return_true_if_family_is_unix c = Conditions::SocketResponding.new c.path = 'some-path' c.family = 'unix' c.watch = stub(:name => 'foo') assert_equal true, c.valid? end # socket method def test_socket_should_return_127_0_0_1_for_default_addr c = Conditions::SocketResponding.new c.socket = 'tcp:443' assert_equal c.addr, '127.0.0.1' end def test_socket_should_set_properties_for_tcp c = Conditions::SocketResponding.new c.socket = 'tcp:127.0.0.1:443' assert_equal c.family, 'tcp' assert_equal c.addr, '127.0.0.1' assert_equal c.port, 443 assert_equal c.responding, false # path should not be set for tcp sockets assert_equal c.path, nil end def test_socket_should_set_properties_for_unix c = Conditions::SocketResponding.new c.socket = 'unix:/tmp/process.sock' assert_equal c.family, 'unix' assert_equal c.path, '/tmp/process.sock' assert_equal c.responding, false # path should not be set for unix domain sockets assert_equal c.port, 0 end # test : responding = false def test_test_tcp_should_return_false_if_socket_is_listening c = Conditions::SocketResponding.new c.prepare TCPSocket.expects(:new).returns(0) assert_equal false, c.test end def test_test_tcp_should_return_true_if_no_socket_is_listening c = Conditions::SocketResponding.new c.prepare TCPSocket.expects(:new).returns(nil) assert_equal true, c.test end def test_test_unix_should_return_false_if_socket_is_listening c = Conditions::SocketResponding.new c.socket = 'unix:/some/path' c.prepare UNIXSocket.expects(:new).returns(0) assert_equal false, c.test end def test_test_unix_should_return_true_if_no_socket_is_listening c = Conditions::SocketResponding.new c.socket = 'unix:/some/path' c.prepare UNIXSocket.expects(:new).returns(nil) assert_equal true, c.test end def test_test_unix_should_return_true_if_socket_is_listening_2_times c = Conditions::SocketResponding.new c.socket = 'unix:/some/path' c.times = [2, 2] c.prepare UNIXSocket.expects(:new).returns(nil).times(2) assert_equal false, c.test assert_equal true, c.test end # test : responding = true def test_test_tcp_should_return_true_if_socket_is_listening_with_responding_true c = Conditions::SocketResponding.new c.responding = true c.prepare TCPSocket.expects(:new).returns(0) assert_equal true, c.test end def test_test_tcp_should_return_false_if_no_socket_is_listening_with_responding_true c = Conditions::SocketResponding.new c.responding = true c.prepare TCPSocket.expects(:new).returns(nil) assert_equal false, c.test end def test_test_unix_should_return_true_if_socket_is_listening_with_responding_true c = Conditions::SocketResponding.new c.responding = true c.socket = 'unix:/some/path' c.prepare UNIXSocket.expects(:new).returns(0) assert_equal true, c.test end def test_test_unix_should_return_false_if_no_socket_is_listening_with_responding_true c = Conditions::SocketResponding.new c.socket = 'unix:/some/path' c.responding = true c.prepare UNIXSocket.expects(:new).returns(nil) assert_equal false, c.test end def test_test_unix_should_return_false_if_socket_is_listening_2_times_with_responding_true c = Conditions::SocketResponding.new c.socket = 'unix:/some/path' c.responding = true c.times = [2, 2] c.prepare UNIXSocket.expects(:new).returns(nil).times(2) assert_equal false, c.test assert_equal false, c.test end end ruby-god-0.13.3/test/test_conditions_tries.rb000066400000000000000000000025021226052465500212560ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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') assert !c.valid? end end class TestConditionsTries < Test::Unit::TestCase def setup @c = Conditions::Tries.new @c.times = 3 @c.prepare end # prepare def test_prepare_should_create_timeline assert_equal 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 assert !@c.test end end class TestConditionsTriesWithin < Test::Unit::TestCase def setup @c = Conditions::Tries.new @c.times = 3 @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 assert sleep(1.1) assert !@c.test end end ruby-god-0.13.3/test/test_contact.rb000066400000000000000000000054041226052465500173360ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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) end end end ruby-god-0.13.3/test/test_driver.rb000066400000000000000000000011201226052465500171650ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestDriver < Test::Unit::TestCase def setup end def test_push_pop_wait eq = God::DriverEventQueue.new MonitorMixin::ConditionVariable.any_instance.expects(:wait).times(1) eq.push(God::TimedEvent.new(0)) eq.push(God::TimedEvent.new(0.1)) t = Thread.new do # This pop will see an event immediately available, so no wait. assert_equal TimedEvent, eq.pop.class # This pop will happen before the next event is due, so wait. assert_equal TimedEvent, eq.pop.class end t.join end end ruby-god-0.13.3/test/test_email.rb000066400000000000000000000021241226052465500167660ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestEmail < Test::Unit::TestCase def setup God::Contacts::Email.to_email = 'dev@example.com' God::Contacts::Email.from_email = 'god@example.com' @email = God::Contacts::Email.new end def test_validity_delivery @email.delivery_method = :brainwaves assert_equal false, @email.valid? end def test_smtp_delivery_method_for_notify @email.delivery_method = :smtp God::Contacts::Email.any_instance.expects(:notify_sendmail).never God::Contacts::Email.any_instance.expects(:notify_smtp).once.returns(nil) @email.notify('msg', Time.now, 'prio', 'cat', 'host') assert_equal "sent email to dev@example.com via smtp", @email.info end def test_sendmail_delivery_method_for_notify @email.delivery_method = :sendmail God::Contacts::Email.any_instance.expects(:notify_smtp).never God::Contacts::Email.any_instance.expects(:notify_sendmail).once.returns(nil) @email.notify('msg', Time.now, 'prio', 'cat', 'host') assert_equal "sent email to dev@example.com via sendmail", @email.info end end ruby-god-0.13.3/test/test_event_handler.rb000066400000000000000000000035501226052465500205210ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' module God class EventHandler def self.actions=(value) @@actions = value end def self.actions @@actions end def self.handler=(value) @@handler = value end end end 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() exit_block.expects(:call).times 1 @h.actions = {4445 => {:proc_exit => exit_block}} @h.call(4446, :proc_exit) # shouldn't call, bad pid @h.call(4445, :proc_fork) # shouldn't call, bad event @h.call(4445, :proc_exit) # should call end def teardown # Reset handler @h.actions = {} @h.load end end # This doesn't currently work: # # class TestEventHandlerOperational < Test::Unit::TestCase # def test_operational # God::EventHandler.start # assert God::EventHandler.loaded? # end # end ruby-god-0.13.3/test/test_god.rb000066400000000000000000000367721226052465500164700ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestGod < Test::Unit::TestCase def setup God::Socket.stubs(:new).returns(true) God.stubs(:setup).returns(true) God.stubs(:validater).returns(true) God.reset God.pid_file_directory = '/var/run/god' end def teardown God.main && God.main.kill if God.watches God.watches.each do |k, w| w.driver.thread.kill 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| w.name = 'foo' 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| w.name = 'foo' 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| w.name = 'foo' w.start = 'bar' 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 God.watch { |w| w.name = 'foo'; w.start = 'bar' } end end # unwatch def test_unwatch_should_unmonitor_watch God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] w.state = :up w.expects(:unmonitor) God.unwatch(w) end def test_unwatch_should_unregister_watch God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] w.expects(:unregister!) God.unwatch(w) end def test_unwatch_should_remove_same_name_watches God.watch { |w| w.name = 'bar'; w.start = 'bar' } w = God.watches['bar'] God.unwatch(w) assert_equal 0, God.watches.size end def test_unwatch_should_remove_from_group God.watch do |w| w.name = 'bar' w.start = 'baz' w.group = 'test' end w = God.watches['bar'] God.unwatch(w) 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_equal({"tom" => 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_equal 2, God.contacts.size assert_equal 1, God.contact_groups.size end def test_contact_should_abort_on_no_name assert_abort do God.contact(:fake_contact) { |c| } end end def test_contact_should_abort_on_duplicate_contact_name God.contact(:fake_contact) { |c| c.name = 'tom' } assert_nothing_raised do God.contact(:fake_contact) { |c| c.name = 'tom' } 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' } assert_nothing_raised do God.contact(:fake_contact) { |c| c.name = 'devs' } 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| c.name = 'tom' c.stubs(:valid?).returns(false) 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.watch do |w| w.name = 'bar1' w.start = 'go' w.group = 'foo' end God.watches['foo1'].expects(:monitor) God.watches['foo2'].expects(:monitor) God.watches['bar1'].expects(:monitor).never God.control('bar', 'start') end def test_control_should_operate_on_all_watches_on_nil God.watch do |w| w.name = 'foo1' w.start = 'go' w.group = 'foo' end God.watch do |w| w.name = 'foo2' w.start = 'go' w.group = 'foo' end God.watch do |w| w.name = 'bar1' w.start = 'go' w.group = 'bar' end God.watches['foo1'].expects(:monitor) God.watches['foo2'].expects(:monitor) God.watches['bar1'].expects(:monitor) God.control(nil, 'start') end def test_control_should_operate_on_all_watches_on_empty_string God.watch do |w| w.name = 'foo1' w.start = 'go' w.group = 'foo' end God.watch do |w| w.name = 'foo2' w.start = 'go' w.group = 'foo' end God.watch do |w| w.name = 'bar1' w.start = 'go' w.group = 'bar' end God.watches['foo1'].expects(:monitor) God.watches['foo2'].expects(:monitor) God.watches['bar1'].expects(:monitor) God.control('', '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) end 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| w.name = 'foo' w.start = 'go' end EOF God.running_load(code, '/foo/bar.god') assert_equal 1, God.watches.size end def test_running_load_should_monitor_new_watches code = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' end EOF Watch.any_instance.expects(:monitor) God.running_load(code, '/foo/bar.god') end def test_running_load_should_not_monitor_new_watches_with_autostart_false code = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' w.autostart = false end EOF Watch.any_instance.expects(:monitor).never God.running_load(code, '/foo/bar.god') end def test_running_load_should_return_array_of_affected_watches code = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' end EOF w = nil w, e = *God.running_load(code, '/foo/bar.god') 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| w.name = 'foo' w.start = 'go' end EOF God.running_load(code, '/foo/bar.god') assert_equal 0, God.pending_watches.size end def test_running_load_with_stop code_one = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' end EOF code_two = <<-EOF God.watch do |w| w.name = 'bar' w.start = 'go' end EOF a, e, r = *God.running_load(code_one, '/foo/one.god', 'stop') assert_equal 1, a.size assert_equal 0, r.size a, e, r = *God.running_load(code_two, '/foo/two.god', 'stop') assert_equal 1, a.size assert_equal 1, r.size end def test_running_load_with_remove code_one = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' end EOF code_two = <<-EOF God.watch do |w| w.name = 'bar' w.start = 'go' end EOF a, e, r = *God.running_load(code_one, '/foo/one.god', 'remove') assert_equal 1, a.size assert_equal 0, r.size a, e, r = *God.running_load(code_two, '/foo/two.god', 'remove') assert_equal 1, a.size assert_equal 1, r.size end def test_running_load_with_leave code_one = <<-EOF God.watch do |w| w.name = 'foo' w.start = 'go' end EOF code_two = <<-EOF God.watch do |w| w.name = 'bar' w.start = 'go' end EOF a, e, r = *God.running_load(code_one, '/foo/one.god', 'leave') assert_equal 1, a.size assert_equal 0, r.size a, e, r = *God.running_load(code_two, '/foo/two.god', 'leave') assert_equal 1, a.size assert_equal 0, r.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' } God.start 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 fuzed22 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 fuzed22}, God.pattern_match('fu', list) assert_equal %w{ mysql }, God.pattern_match('sql', list) end end # class TestGodOther < Test::Unit::TestCase # def setup # God::Socket.stubs(:new).returns(true) # 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 # God.validater # end # end # end ruby-god-0.13.3/test/test_handlers_kqueue_handler.rb000066400000000000000000000006541226052465500225610ustar00rootroot00000000000000require 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 ruby-god-0.13.3/test/test_jabber.rb000077500000000000000000000015761226052465500171410ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + '/helper' class TestJabber < Test::Unit::TestCase def setup @jabber = God::Contacts::Jabber.new end def test_notify @jabber.host = 'talk.google.com' @jabber.from_jid = 'god@jabber.org' @jabber.password = 'secret' @jabber.to_jid = 'dev@jabber.org' time = Time.now body = God::Contacts::Jabber.format.call('msg', time, 'prio', 'cat', 'host') assert_equal "Message: msg\nHost: host\nPriority: prio\nCategory: cat\n", body Jabber::Client.any_instance.expects(:connect).with('talk.google.com', 5222) Jabber::Client.any_instance.expects(:auth).with('secret') Jabber::Client.any_instance.expects(:send) Jabber::Client.any_instance.expects(:close) @jabber.notify('msg', Time.now, 'prio', 'cat', 'host') assert_equal "sent jabber message to dev@jabber.org", @jabber.info end end ruby-god-0.13.3/test/test_logger.rb000066400000000000000000000022731226052465500171630ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestLogger < Test::Unit::TestCase def setup @log = God::Logger.new(StringIO.new('/dev/null')) end # log def test_log_should_keep_logs_when_wanted @log.watch_log_since('foo', Time.now) @log.expects(:info).with("qux") @log.log(stub(:name => 'foo'), :info, "qux") 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 SysLogger.expects(:log).with(:fatal, 'foo') @log.log(stub(:name => 'foo'), :fatal, "foo") end # watch_log_since def test_watch_log_since t1 = Time.now @log.watch_log_since('foo', t1) @log.log(stub(:name => 'foo'), :info, "one") @log.log(stub(:name => 'foo'), :info, "two") assert_match(/one.*two/m, @log.watch_log_since('foo', t1)) t2 = Time.now @log.log(stub(:name => 'foo'), :info, "three") 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 @log.fatal('foo') assert_equal 0, @log.logs.size end end ruby-god-0.13.3/test/test_metric.rb000066400000000000000000000034221226052465500171640ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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 # This doesn't currently work: # # 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) { } end end end ruby-god-0.13.3/test/test_process.rb000066400000000000000000000133351226052465500173630ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' module God class Process # def fork # raise "You forgot to stub fork" # end def exec(*args) raise "You forgot to stub exec" end end end class TestProcessChild < Test::Unit::TestCase def setup God.internal_init @p = God::Process.new @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' assert !@p.valid? end def test_valid_should_return_true_if_gid_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.gid = Etc.getgrgid(::Process.gid).name ::Process.stubs(:groups=) 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' assert !@p.valid? end def test_valid_should_return_true_if_dir_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.dir = '/tmp' assert @p.valid? end def test_valid_should_return_false_if_dir_does_not_exists @p.start = 'qux' @p.log = '/tmp/foo.log' @p.dir = '/tmp/doesnotexist' assert !@p.valid? end def test_valid_should_return_false_if_dir_is_not_a_dir @p.start = 'qux' @p.log = '/tmp/foo.log' @p.dir = '/etc/passwd' assert !@p.valid? end def test_valid_should_return_false_with_bogus_chroot @p.chroot = '/bogusroot' assert !@p.valid? end def test_valid_should_return_true_with_chroot_and_valid_log @p.start = 'qux' @p.chroot = Dir.pwd @p.log = "#{@p.chroot}/foo.log" File.expects(:exist?).with(@p.chroot).returns(true) File.expects(:exist?).with(@p.log).returns(true) File.expects(:exist?).with("#{@p.chroot}/dev/null").returns(true) File.stubs(:writable?).with('/foo.log').returns(true) ::Dir.stubs(:chroot) assert @p.valid? end # call_action def test_call_action_should_write_pid # Only for start, restart [:start, :restart].each do |action| @p.stubs(:test).returns true IO.expects(:pipe).returns([StringIO.new('1234'), StringIO.new]) @p.expects(:fork) Process.expects(:waitpid) File.expects(:open).with(@p.default_pid_file, 'w') @p.send("#{action}=", "run") @p.call_action(action) end end end ############################################################################### # # Daemon # ############################################################################### class TestProcessDaemon < Test::Unit::TestCase def setup God.internal_init @p = God::Process.new @p.name = 'foo' @p.pid_file = 'blah.pid' @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' assert !@p.valid? 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 # umask def test_umask_should_return_umask_specified @p.umask = 002 assert_equal 002, @p.umask 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) @p.send("#{x}!") end end end ruby-god-0.13.3/test/test_prowl.rb000066400000000000000000000006611226052465500170460ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + '/helper' class TestProwl < Test::Unit::TestCase def test_live_notify prowl = God::Contacts::Prowl.new prowl.name = "Prowly" prowl.apikey = 'put_your_apikey_here' Prowly.expects(:notify).returns(mock(:succeeded? => true)) prowl.notify("Test", Time.now, "Test", "Test", "") assert_equal "sent prowl notification to #{prowl.name}", prowl.info end end ruby-god-0.13.3/test/test_registry.rb000066400000000000000000000004631226052465500175530ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestRegistry < Test::Unit::TestCase def setup God.registry.reset end def test_add foo = God::Process.new foo.name = 'foo' God.registry.add(foo) assert_equal 1, God.registry.size assert_equal foo, God.registry['foo'] end end ruby-god-0.13.3/test/test_socket.rb000066400000000000000000000014301226052465500171660ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestSocket < Test::Unit::TestCase def setup silence_warnings do Object.const_set(:DRb, stub_everything) end end def test_should_start_a_drb_server DRb.expects(:start_service) God::Socket.new end def test_should_use_supplied_port_and_host DRb.expects(:start_service).with { |uri, object| uri == "drbunix:///tmp/god.9999.sock" && object.is_a?(God::Socket) } server = God::Socket.new(9999) end def test_should_forward_foreign_method_calls_to_god server = nil server = God::Socket.new God.expects(:send).with(:something_random) server.something_random end # ping def test_ping_should_return_true server = nil server = God::Socket.new assert server.ping end end ruby-god-0.13.3/test/test_sugar.rb000066400000000000000000000014121226052465500170170ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestSugar < Test::Unit::TestCase 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 end ruby-god-0.13.3/test/test_system_portable_poller.rb000066400000000000000000000011121226052465500224640ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestSystemPortablePoller < Test::Unit::TestCase 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') assert_equal 27, @process.bypass.time_string_to_seconds('0:27:32') assert_equal 75, @process.bypass.time_string_to_seconds('1:15:13') assert_equal 735, @process.bypass.time_string_to_seconds('12:15:13') end end ruby-god-0.13.3/test/test_system_process.rb000066400000000000000000000012371226052465500207650ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestSystemProcess < Test::Unit::TestCase 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 end ruby-god-0.13.3/test/test_task.rb000066400000000000000000000130211226052465500166370ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestTask < Test::Unit::TestCase def setup God.internal_init @task = Task.new @task.name = 'foo' @task.valid_states = [:foo, :bar] @task.initial_state = :foo @task.interval = 5 @task.prepare end # valid? def test_valid_should_return_false_if_no_name @task.name = nil assert !@task.valid? end def test_valid_should_return_false_if_no_valid_states @task.valid_states = nil assert !@task.valid? end def test_valid_should_return_false_if_no_initial_state @task.initial_state = nil assert !@task.valid? end # transition def test_transition_should_be_always_if_no_block_was_given @task.transition(:foo, :bar) assert_equal 5, @task.metrics.size assert_equal Conditions::Always, @task.metrics[:foo].first.conditions.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' @task.driver.stubs(:in_driver_context?).returns(true) @task.expects(:system).with('foo') @task.action(:foo, nil) end def test_action_should_call_lambda_commands @task.foo = lambda { } @task.driver.stubs(:in_driver_context?).returns(true) @task.foo.expects(:call) @task.action(:foo, nil) end def test_action_should_raise_not_implemented_on_non_string_or_lambda_action @task.driver.stubs(:in_driver_context?).returns(true) assert_raise NotImplementedError do @task.foo = 7 @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) @task.handle_poll(c) 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) @task.handle_poll(c) 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) @task.handle_poll(c) 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) @task.handle_poll(c) 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 @task.handle_poll(c) end def test_handle_poll_unexpected_exception_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).raises(StandardError) @task.driver.expects(:schedule) @task.handle_poll(c) 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) @task.handle_event(c) 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) @task.handle_event(c) 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 @task.handle_event(c) end end ruby-god-0.13.3/test/test_timeline.rb000066400000000000000000000013061226052465500175060ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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 # end end ruby-god-0.13.3/test/test_trigger.rb000066400000000000000000000023241226052465500173440ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' 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 ruby-god-0.13.3/test/test_watch.rb000066400000000000000000000154311226052465500170120ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestWatch < Test::Unit::TestCase def setup God.internal_init @watch = Watch.new @watch.name = 'foo' @watch.start = lambda { } @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' @watch.start = 'start' @watch.stop = 'stop' @watch.restart = 'restart' @watch.interval = 30 @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| @watch.transition(state, :bar) { } 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 # keepalive def test_keepalive_should_place_metrics_on_up_state @watch.keepalive(:memory_max => 5.megabytes, :cpu_max => 50.percent) assert_equal 2, @watch.metrics[:up].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) { } end @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 @watch.driver.stubs(:in_driver_context?).returns(true) @watch.driver.expects(:message).never metric = nil @watch.instance_eval do transition(:init, :up) do |on| metric = on on.condition(:process_running) do |c| c.running = true c.interval = 10 end end end metric.expects(:disable).never @watch.move(:init) end def test_move_should_clean_up_from_state_if_not_nil @watch.driver.stubs(:in_driver_context?).returns(true) @watch.driver.expects(:message).never metric = nil @watch.instance_eval do transition(:init, :up) do |on| metric = on on.condition(:process_running) do |c| c.running = true c.interval = 10 end end end @watch.move(:init) metric.expects(:disable) @watch.move(:up) end def test_move_should_call_action @watch.driver.stubs(:in_driver_context?).returns(true) @watch.driver.expects(:message).never @watch.expects(:action).with(:start) @watch.move(:start) end def test_move_should_move_to_up_state_if_no_start_or_restart_metric @watch.driver.stubs(:in_driver_context?).returns(true) @watch.driver.expects(:message).never [:start, :restart].each do |state| @watch.expects(:action) @watch.move(state) assert_equal :up, @watch.state end end def test_move_should_enable_destination_metric @watch.driver.stubs(:in_driver_context?).returns(true) @watch.driver.expects(:message).never metric = nil @watch.instance_eval do transition(:init, :up) do |on| metric = on on.condition(:process_running) do |c| c.running = true c.interval = 10 end end end metric.expects(:enable) @watch.move(:init) end # action def test_action_should_pass_start_and_stop_actions_to_call_action @watch.driver.stubs(:in_driver_context?).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 @watch.driver.stubs(:in_driver_context?).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 @watch.driver.stubs(:in_driver_context?).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) @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 God::Process.any_instance.expects(:call_action).with(:start) Behaviors::FakeBehavior.any_instance.expects(:before_start) @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 God::Process.any_instance.expects(:call_action).with(:start) Behaviors::FakeBehavior.any_instance.expects(:after_start) @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 end ruby-god-0.13.3/test/test_webhook.rb000066400000000000000000000007001226052465500173330ustar00rootroot00000000000000require File.dirname(__FILE__) + '/helper' class TestWebhook < Test::Unit::TestCase def setup @webhook = God::Contacts::Webhook.new end def test_notify @webhook.url = 'http://example.com/switch' Net::HTTP.any_instance.expects(:request).returns(Net::HTTPSuccess.new('a', 'b', 'c')) @webhook.notify('msg', Time.now, 'prio', 'cat', 'host') assert_equal "sent webhook to http://example.com/switch", @webhook.info end end