From 437be1314b9acb362d5e4ad9a21d14e574685e09 Mon Sep 17 00:00:00 2001 From: Tom Werner Date: Fri, 3 Aug 2007 16:38:10 -0700 Subject: [PATCH] implemented cli control --- History.txt | 9 ++++++++ bin/god | 53 +++++++++++++++++++++++++++++++++++++++------ examples/events.god | 2 +- examples/gravatar.god | 2 +- lib/god.rb | 6 ++--- lib/god/hub.rb | 4 +--- lib/god/meddle.rb | 10 ++++----- lib/god/server.rb | 4 ++-- lib/god/watch.rb | 25 +++++++++++++++------ site/index.html | 16 +++++++++----- test/configs/test.rb | 9 ++++++-- test/helper.rb | 1 + test/test_event_handler.rb | 5 ++++- test/test_god.rb | 2 +- test/test_meddle.rb | 4 ++-- test/test_system_process.rb | 2 +- 16 files changed, 112 insertions(+), 42 deletions(-) diff --git a/History.txt b/History.txt index ef728c2..e1c4a16 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,12 @@ +== 0.3.0 / + +* 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 in base 32) +* Implement command line control to start/restart/stop/monitor/unmonitor watches by name + == 0.2.1 / * Fix netlink header problem on Ubuntu Edgy [Dan Sully] diff --git a/bin/god b/bin/god index 71179ed..1d35cd4 100755 --- a/bin/god +++ b/bin/god @@ -3,24 +3,63 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) require 'rubygems' -require 'daemons' require 'optparse' +require 'drb' + +require 'daemons' require 'god' -options = {} + +options = {:daemonize => true, :port => 17165} + OptionParser.new do |opts| - opts.banner = "Usage: god command [options]" + opts.banner = <<-EOF +Usage: god [command] [options] + + Commands: + start + stop + monitor + unmonitor + + Options: +EOF opts.on("-cCONFIG", "--config-file CONFIG", "Configuration file") do |x| options[:config] = x end + + opts.on("-pPORT", "--port PORT", "Communications port") do |x| + options[:port] = x + end + + opts.on("-D", "--no-daemonize", "Don't daemonize") do + options[:daemonize] = false + end end.parse! + +if command = ARGV[0] + name = ARGV[1] -options[:config] = File.expand_path(options[:config]) if options[:config] + DRb.start_service + server = DRbObject.new nil, "druby://localhost:#{options[:port]}" + + case command + when "start", "monitor" + server.meddle.watches[name].monitor + when "restart" + server.meddle.watches[name].move(:restart) + when "stop" + server.meddle.watches[name].unmonitor.action(:stop) + when "unmonitor" + server.meddle.watches[name].unmonitor + else + abort "Command '#{command}' is not valid. Run 'god --help' for usage" + end +else + options[:config] = File.expand_path(options[:config]) if options[:config] -# p options -# p ARGV + Daemons.daemonize if options[:daemonize] -Daemons.run_proc('god') do load options[:config] end diff --git a/examples/events.god b/examples/events.god index a48a2ff..c89775d 100644 --- a/examples/events.god +++ b/examples/events.god @@ -2,7 +2,7 @@ # and running on your Mac. # Run with: -# god start -c /path/to/events.god +# god -c /path/to/events.god RAILS_ROOT = "/Users/tom/dev/helloworld" diff --git a/examples/gravatar.god b/examples/gravatar.god index abe5aa5..b2da97a 100644 --- a/examples/gravatar.god +++ b/examples/gravatar.god @@ -1,4 +1,4 @@ -# run with: god start -c /path/to/gravatar.god +# run with: god -c /path/to/gravatar.god # # This is the actual config file used to keep the mongrels of # gravatar.com running. diff --git a/lib/god.rb b/lib/god.rb index a7a57c0..7716ec9 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -40,7 +40,7 @@ Syslog.open('god') God::EventHandler.load module God - VERSION = '0.2.1' + VERSION = '0.3.0' def self.meddle(options = {}) m = Meddle.new(options) @@ -54,10 +54,10 @@ module God # start the timer system Timer.get - # start monitoring each watch + # start monitoring m.monitor - # join the timer thread to we don't exit + # join the timer thread so we don't exit Timer.get.join end end diff --git a/lib/god/hub.rb b/lib/god/hub.rb index 6740897..f9feaee 100644 --- a/lib/god/hub.rb +++ b/lib/god/hub.rb @@ -48,14 +48,12 @@ module God watch.mutex.synchronize do result = condition.test - msg = watch.name + ' ' + condition.class.name + " [#{result}]" + msg = watch.name + ' ' + condition.class.name + " [#{result}] " + metric.destination.inspect Syslog.debug(msg) puts msg condition.after - p metric.destination - if dest = metric.destination[result] watch.move(dest) else diff --git a/lib/god/meddle.rb b/lib/god/meddle.rb index 0d94054..8cfe13a 100644 --- a/lib/god/meddle.rb +++ b/lib/god/meddle.rb @@ -9,7 +9,7 @@ module God # Create a new instance that is ready for use by a configuration file def initialize(options = {}) - self.watches = [] + self.watches = {} self.server = Server.new(self, options[:host], options[:port]) end @@ -21,17 +21,17 @@ module God yield(w) # ensure the new watch has a unique name - unless @watches.select { |x| x.name == w.name }.empty? + if @watches[w.name] abort "Duplicate Watch with name '#{w.name}'" end # add to list of watches - @watches << w + @watches[w.name] = w end - # Schedule all poll conditions and register all condition events + # Start monitoring any watches set to autostart def monitor - @watches.each { |w| w.monitor if w.autostart? } + @watches.values.each { |w| w.monitor if w.autostart? } end end diff --git a/lib/god/server.rb b/lib/god/server.rb index 38d972b..e0ba066 100644 --- a/lib/god/server.rb +++ b/lib/god/server.rb @@ -5,12 +5,12 @@ require 'drb' module God class Server - attr_reader :host, :port + attr_reader :meddle, :host, :port def initialize(meddle = nil, host = nil, port = nil) @meddle = meddle @host = host - @port = port || 7777 + @port = port || 17165 start end diff --git a/lib/god/watch.rb b/lib/god/watch.rb index 80a3233..1485339 100644 --- a/lib/god/watch.rb +++ b/lib/god/watch.rb @@ -6,7 +6,8 @@ module God VALID_STATES = [:init, :up, :start, :restart] # config - attr_accessor :name, :state, :start, :stop, :restart, :interval, :grace, + attr_accessor :name, :state, :start, :stop, :restart, :interval, + :grace, :start_grace, :stop_grace, :restart_grace, :user, :group attr_writer :autostart @@ -24,7 +25,7 @@ module God @meddle = meddle # no grace period by default - self.grace = 0 + self.grace = self.start_grace = self.stop_grace = self.restart_grace = 0 # the list of behaviors self.behaviors = [] @@ -110,7 +111,7 @@ module God # ########################################################################### - # Schedule all poll conditions and register all condition events + # Enable monitoring def monitor # start monitoring at the first available of the init or up states if !self.metrics[:init].empty? @@ -120,6 +121,11 @@ module God end end + # Disable monitoring + def unmonitor + self.move(nil) + end + # Move from one state to another def move(to_state) msg = "move '#{self.state}' to '#{to_state}'" @@ -135,10 +141,15 @@ module God self.action(to_state) # move to new state - self.metrics[to_state].each { |m| m.enable } + if to_state + self.metrics[to_state].each { |m| m.enable } + end # set state self.state = to_state + + # return self + self end def action(a, c = nil) @@ -147,7 +158,7 @@ module God Syslog.debug(self.start) puts self.start call_action(c, :start, self.start) - sleep(self.grace) + sleep(self.start_grace + self.grace) when :restart if self.restart Syslog.debug(self.restart) @@ -157,12 +168,12 @@ module God action(:stop, c) action(:start, c) end - sleep(self.grace) + sleep(self.restart_grace + self.grace) when :stop Syslog.debug(self.stop) puts self.stop call_action(c, :stop, self.stop) - sleep(self.grace) + sleep(self.stop_grace + self.grace) end end diff --git a/site/index.html b/site/index.html index 73772c3..d0def57 100644 --- a/site/index.html +++ b/site/index.html @@ -218,7 +218,7 @@ code { -

Installation (v 0.2.0)

+

Installation (v 0.3.0)

The best way to get god is via rubygems:

$ sudo gem install god

You can also peruse or clone the code from http://repo.or.cz/w/god.git

@@ -331,7 +331,7 @@ end

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. All we have to do is specify the location of the PID file.

      w.start_if do |start|
-        start.condition(:process_not_running) do |c|
+        start.condition(:process_running) do |c|
           c.interval = 5 # seconds
           c.running = false
           c.pid_file = pid_file
@@ -365,15 +365,19 @@ 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.

-

Starting God

+

Starting and Controlling God

-

To start the god monitoring process simply run the god executable passing in the path to the config file:

+

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):

-
$ god start -c /path/to/config.god
+
$ 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:

-
$ god run -c /path/to/config.god
+
$ sudo god -c /path/to/config.god -D
+ +

You can start/restart/stop/monitor/unmonitor your Watches with the same utility like so:

+ +
$ sudo god stop 'gravatar2-mongrel-8200'

Advanced Configuration with Transitions and Events

diff --git a/test/configs/test.rb b/test/configs/test.rb index 3dba67e..2a0c364 100644 --- a/test/configs/test.rb +++ b/test/configs/test.rb @@ -2,15 +2,20 @@ if $0 == __FILE__ require File.join(File.dirname(__FILE__), *%w[.. .. lib god]) end -RAILS_ROOT = "/Users/kev/code/tak" +ENV['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'] God.meddle do |god| god.watch do |w| w.name = "local-3000" w.interval = 5 # seconds w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -p 3001 -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.autostart = false + w.restart_grace = 5 # seconds + w.stop_grace = 5 # seconds + w.autostart = false # w.user = "kev" # w.group = "kev" diff --git a/test/helper.rb b/test/helper.rb index b983859..7ebe925 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,6 +1,7 @@ require File.join(File.dirname(__FILE__), *%w[.. lib god]) require 'test/unit' +require 'set' begin require 'mocha' diff --git a/test/test_event_handler.rb b/test/test_event_handler.rb index 7a3096c..8eb3ddc 100644 --- a/test/test_event_handler.rb +++ b/test/test_event_handler.rb @@ -43,7 +43,10 @@ class TestEventHandler < Test::Unit::TestCase @h.actions = {pid => {:proc_exit => exit_block}} mock_handler = mock() - mock_handler.expects(:register_process).with(pid, [:proc_exit, :proc_fork]) + 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" } diff --git a/test/test_god.rb b/test/test_god.rb index d61692a..8fdcf4c 100644 --- a/test/test_god.rb +++ b/test/test_god.rb @@ -11,6 +11,6 @@ class TestGod < Test::Unit::TestCase def test_should_start_monitoring Meddle.any_instance.expects(:monitor) Timer.expects(:get).returns(stub(:join => nil)).times(2) - God.meddle {} + God.meddle(:port => 9999) {} end end diff --git a/test/test_meddle.rb b/test/test_meddle.rb index 1fb8a4b..bea63d7 100644 --- a/test/test_meddle.rb +++ b/test/test_meddle.rb @@ -7,7 +7,7 @@ class TestMeddle < Test::Unit::TestCase end def test_should_initialize_watches_to_empty_array - assert_equal [], @meddle.watches + assert_equal Hash.new, @meddle.watches end def test_watches_should_get_stored @@ -15,7 +15,7 @@ class TestMeddle < Test::Unit::TestCase @meddle.watch { |w| watch = w } assert_equal 1, @meddle.watches.size - assert_equal watch, @meddle.watches.first + assert_equal watch, @meddle.watches.values.first end def test_should_kick_off_a_server_instance diff --git a/test/test_system_process.rb b/test/test_system_process.rb index e9a537b..b62786a 100644 --- a/test/test_system_process.rb +++ b/test/test_system_process.rb @@ -11,7 +11,7 @@ class TestSystemProcess < Test::Unit::TestCase end def test_exists_should_return_false_for_non_existant_process - assert_equal false, System::Process.new(0).exists? + assert_equal false, System::Process.new(9999999).exists? end def test_memory -- 2.11.4.GIT