god 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/History.txt +26 -0
  2. data/Manifest.txt +15 -1
  3. data/Rakefile +2 -7
  4. data/bin/god +104 -16
  5. data/lib/god.rb +169 -37
  6. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  7. data/lib/god/condition.rb +1 -0
  8. data/lib/god/conditions/degrading_lambda.rb +47 -0
  9. data/lib/god/conditions/process_exits.rb +6 -2
  10. data/lib/god/conditions/tries.rb +33 -0
  11. data/lib/god/dependency_graph.rb +41 -0
  12. data/lib/god/errors.rb +6 -0
  13. data/lib/god/hub.rb +43 -20
  14. data/lib/god/logger.rb +44 -0
  15. data/lib/god/process.rb +91 -19
  16. data/lib/god/registry.rb +4 -0
  17. data/lib/god/server.rb +12 -2
  18. data/lib/god/timeline.rb +36 -0
  19. data/lib/god/watch.rb +27 -8
  20. data/test/configs/child_events/child_events.god +7 -2
  21. data/test/configs/child_polls/child_polls.god +3 -1
  22. data/test/configs/child_polls/simple_server.rb +1 -1
  23. data/test/configs/daemon_events/daemon_events.god +7 -3
  24. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  25. data/test/configs/daemon_polls/simple_server.rb +6 -0
  26. data/test/configs/degrading_lambda/degrading_lambda.god +33 -0
  27. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  28. data/test/configs/real.rb +1 -1
  29. data/test/configs/running_load/running_load.god +16 -0
  30. data/test/configs/stress/simple_server.rb +3 -0
  31. data/test/configs/stress/stress.god +15 -0
  32. data/test/configs/test.rb +14 -2
  33. data/test/helper.rb +12 -2
  34. data/test/test_conditions_tries.rb +46 -0
  35. data/test/test_dependency_graph.rb +62 -0
  36. data/test/test_god.rb +289 -33
  37. data/test/test_handlers_kqueue_handler.rb +11 -7
  38. data/test/test_hub.rb +18 -0
  39. data/test/test_logger.rb +55 -0
  40. data/test/test_process.rb +135 -17
  41. data/test/test_registry.rb +2 -1
  42. data/test/test_server.rb +35 -4
  43. data/test/test_timeline.rb +14 -2
  44. data/test/test_watch.rb +7 -0
  45. metadata +21 -4
  46. data/lib/god/conditions/timeline.rb +0 -17
data/lib/god/registry.rb CHANGED
@@ -13,6 +13,10 @@ module God
13
13
  @storage[item.name] = item
14
14
  end
15
15
 
16
+ def remove(item)
17
+ @storage.delete(item.name)
18
+ end
19
+
16
20
  def size
17
21
  @storage.size
18
22
  end
data/lib/god/server.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'drb'
2
+ require 'drb/acl'
2
3
 
3
4
  # The God::Server oversees the DRb server which dishes out info on this God daemon.
4
5
 
@@ -7,12 +8,18 @@ module God
7
8
  class Server
8
9
  attr_reader :host, :port
9
10
 
10
- def initialize(host = nil, port = nil)
11
+ def initialize(host = nil, port = nil, allow = [])
11
12
  @host = host
12
- @port = port || 17165
13
+ @port = port
14
+ @acl = %w{deny all} + allow.inject([]) { |acc, a| acc + ['allow', a] }
15
+ puts "Starting on #{@host}:#{@port}"
13
16
  start
14
17
  end
15
18
 
19
+ def ping
20
+ true
21
+ end
22
+
16
23
  def method_missing(*args, &block)
17
24
  God.send(*args, &block)
18
25
  end
@@ -20,6 +27,9 @@ module God
20
27
  private
21
28
 
22
29
  def start
30
+ acl = ACL.new(@acl)
31
+ DRb.install_acl(acl)
32
+
23
33
  @drb ||= DRb.start_service("druby://#{@host}:#{@port}", self)
24
34
  end
25
35
  end
@@ -0,0 +1,36 @@
1
+ module God
2
+
3
+ class Timeline < Array
4
+ def initialize(max_size)
5
+ super()
6
+ @max_size = max_size
7
+ end
8
+
9
+ # Push a value onto the Timeline
10
+ #
11
+ # Implementation explanation:
12
+ # A performance optimization appears here to speed up the push time.
13
+ # In essence, the code does this:
14
+ #
15
+ # def push(val)
16
+ # super(val)
17
+ # shift if size > @max_size
18
+ # end
19
+ #
20
+ # But that's super slow due to the shift, so we resort to reverse! and pop
21
+ # which gives us a 2x speedup with 100 elements and a 6x speedup with 1000
22
+ def push(val)
23
+ if (size + 1) > @max_size
24
+ reverse!
25
+ pop
26
+ reverse!
27
+ end
28
+ super(val)
29
+ end
30
+
31
+ def <<(val)
32
+ push(val)
33
+ end
34
+ end
35
+
36
+ end
data/lib/god/watch.rb CHANGED
@@ -17,7 +17,7 @@ module God
17
17
  extend Forwardable
18
18
  def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart,
19
19
  :name=, :uid=, :gid=, :start=, :stop=, :restart=,
20
- :pid_file, :pid_file=
20
+ :pid_file, :pid_file=, :log, :log=, :alive?
21
21
 
22
22
  # api
23
23
  attr_accessor :behaviors, :metrics
@@ -46,6 +46,16 @@ module God
46
46
  self.mutex = Mutex.new
47
47
  end
48
48
 
49
+ def valid?
50
+ @process.valid?
51
+ end
52
+
53
+ ###########################################################################
54
+ #
55
+ # Behavior
56
+ #
57
+ ###########################################################################
58
+
49
59
  def behavior(kind)
50
60
  # create the behavior
51
61
  begin
@@ -136,7 +146,7 @@ module God
136
146
  def move(to_state)
137
147
  msg = "#{self.name} move '#{self.state}' to '#{to_state}'"
138
148
  Syslog.debug(msg)
139
- puts msg
149
+ LOG.log(self, :info, msg)
140
150
 
141
151
  # cleanup from current state
142
152
  from_state = self.state
@@ -167,14 +177,16 @@ module God
167
177
  def action(a, c = nil)
168
178
  case a
169
179
  when :start
170
- Syslog.debug(self.start.to_s)
171
- puts self.start.to_s
180
+ msg = "#{self.name} start: #{self.start.to_s}"
181
+ Syslog.debug(msg)
182
+ LOG.log(self, :info, msg)
172
183
  call_action(c, :start)
173
184
  sleep(self.start_grace + self.grace)
174
185
  when :restart
175
186
  if self.restart
176
- Syslog.debug(self.restart.to_s)
177
- puts self.restart
187
+ msg = "#{self.name} restart: #{self.restart.to_s}"
188
+ Syslog.debug(msg)
189
+ LOG.log(self, :info, msg)
178
190
  call_action(c, :restart)
179
191
  else
180
192
  action(:stop, c)
@@ -182,8 +194,11 @@ module God
182
194
  end
183
195
  sleep(self.restart_grace + self.grace)
184
196
  when :stop
185
- Syslog.debug(self.stop.to_s)
186
- puts self.stop.to_s
197
+ if self.stop
198
+ msg = "#{self.name} stop: #{self.stop.to_s}"
199
+ Syslog.debug(msg)
200
+ LOG.log(self, :info, msg)
201
+ end
187
202
  call_action(c, :stop)
188
203
  sleep(self.stop_grace + self.grace)
189
204
  end
@@ -210,6 +225,10 @@ module God
210
225
  def register!
211
226
  God.registry.add(@process)
212
227
  end
228
+
229
+ def unregister!
230
+ God.registry.remove(@process)
231
+ end
213
232
  end
214
233
 
215
234
  end
@@ -2,7 +2,6 @@ God.watch do |w|
2
2
  w.name = "child-events"
3
3
  w.interval = 5.seconds
4
4
  w.start = File.join(File.dirname(__FILE__), *%w[simple_server.rb])
5
- w.stop = ""
6
5
 
7
6
  # determine the state on startup
8
7
  w.transition(:init, { true => :up, false => :start }) do |on|
@@ -12,10 +11,16 @@ God.watch do |w|
12
11
  end
13
12
 
14
13
  # determine when process has finished starting
15
- w.transition(:start, :up) do |on|
14
+ w.transition([:start, :restart], :up) do |on|
16
15
  on.condition(:process_running) do |c|
17
16
  c.running = true
18
17
  end
18
+
19
+ # failsafe
20
+ on.condition(:tries) do |c|
21
+ c.times = 2
22
+ c.transition = :start
23
+ end
19
24
  end
20
25
 
21
26
  # start if process is not running
@@ -1,11 +1,13 @@
1
1
  God.watch do |w|
2
2
  w.name = 'child-polls'
3
3
  w.start = File.join(File.dirname(__FILE__), *%w[simple_server.rb])
4
- w.stop = ''
4
+ # w.stop = ''
5
5
  w.interval = 5
6
6
  w.grace = 2
7
7
  w.uid = 'tom'
8
8
  w.gid = 'tom'
9
+ w.group = 'test'
10
+ w.log = File.join(File.dirname(__FILE__), *%w[out.log])
9
11
 
10
12
  w.start_if do |start|
11
13
  start.condition(:process_running) do |c|
@@ -1,3 +1,3 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
- loop { puts 'server'; sleep 1 }
3
+ loop { STDOUT.puts('server'); STDOUT.flush; sleep 1 }
@@ -3,8 +3,6 @@ God.watch do |w|
3
3
  w.interval = 5.seconds
4
4
  w.start = '/usr/local/bin/ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' start'
5
5
  w.stop = '/usr/local/bin/ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' stop'
6
- w.uid = 'tom'
7
- w.gid = 'tom'
8
6
  w.pid_file = '/var/run/daemon-events.pid'
9
7
 
10
8
  w.behavior(:clean_pid_file)
@@ -17,10 +15,16 @@ God.watch do |w|
17
15
  end
18
16
 
19
17
  # determine when process has finished starting
20
- w.transition(:start, :up) do |on|
18
+ w.transition([:start, :restart], :up) do |on|
21
19
  on.condition(:process_running) do |c|
22
20
  c.running = true
23
21
  end
22
+
23
+ # failsafe
24
+ on.condition(:tries) do |c|
25
+ c.times = 2
26
+ c.transition = :start
27
+ end
24
28
  end
25
29
 
26
30
  # start if process is not running
@@ -0,0 +1,17 @@
1
+ God.watch do |w|
2
+ w.name = "daemon-polls"
3
+ w.interval = 5.seconds
4
+ w.start = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' start'
5
+ w.stop = 'ruby ' + File.join(File.dirname(__FILE__), *%w[simple_server.rb]) + ' stop'
6
+ w.pid_file = '/var/run/daemon-polls.pid'
7
+ w.start_grace = 2.seconds
8
+ w.log = File.join(File.dirname(__FILE__), *%w[out.log])
9
+
10
+ w.behavior(:clean_pid_file)
11
+
12
+ w.start_if do |start|
13
+ start.condition(:process_running) do |c|
14
+ c.running = false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'daemons'
3
+
4
+ Daemons.run_proc('daemon-polls', {:dir_mode => :system}) do
5
+ loop { STDOUT.puts('server'); STDOUT.flush; sleep 1 }
6
+ end
@@ -0,0 +1,33 @@
1
+ God.watch do |w|
2
+ w.name = 'degrading-lambda'
3
+ w.start = File.join(File.dirname(__FILE__), *%w[tcp_server.rb])
4
+ w.interval = 5
5
+ w.grace = 2
6
+ w.uid = 'kev'
7
+ w.gid = 'kev'
8
+ w.group = 'test'
9
+
10
+ w.start_if do |start|
11
+ start.condition(:process_running) do |c|
12
+ c.running = false
13
+ end
14
+ end
15
+
16
+ w.restart_if do |restart|
17
+ restart.condition(:degrading_lambda) do |c|
18
+ require 'socket'
19
+ c.lambda = lambda {
20
+ begin
21
+ sock = TCPSocket.open('127.0.0.1', 9090)
22
+ sock.send "2\n", 0
23
+ retval = sock.gets
24
+ puts "Retval is #{retval}"
25
+ sock.close
26
+ retval
27
+ rescue
28
+ false
29
+ end
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ server = TCPServer.new('127.0.0.1', 9090)
5
+ while (session = server.accept)
6
+ puts "Found a session"
7
+ request = session.gets
8
+ puts "Request: #{request}"
9
+ time = request.to_i
10
+ puts "Sleeping for #{time}"
11
+ sleep time
12
+ session.print "Slept for #{time} seconds"
13
+ session.close
14
+ puts "Session closed"
15
+ end
data/test/configs/real.rb CHANGED
@@ -17,7 +17,7 @@ God.watch do |w|
17
17
  w.behavior(:clean_pid_file) do |b|
18
18
  b.pid_file = pid_file
19
19
  end
20
-
20
+
21
21
  # start if process is not running
22
22
  w.start_if do |start|
23
23
  start.condition(:process_running) do |c|
@@ -0,0 +1,16 @@
1
+ God.watch do |w|
2
+ w.name = 'running-load'
3
+ w.start = '/Users/tom/dev/god/test/configs/child_polls/simple_server.rb'
4
+ w.stop = ''
5
+ w.interval = 5
6
+ w.grace = 2
7
+ w.uid = 'tom'
8
+ w.gid = 'tom'
9
+ w.group = 'test'
10
+
11
+ w.start_if do |start|
12
+ start.condition(:process_running) do |c|
13
+ c.running = false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ loop { puts 'server'; sleep 1 }
@@ -0,0 +1,15 @@
1
+ ('01'..'20').each do |i|
2
+ God.watch do |w|
3
+ w.name = "stress-#{i}"
4
+ w.start = "ruby " + File.join(File.dirname(__FILE__), *%w[simple_server.rb])
5
+ w.interval = 1
6
+ w.grace = 2
7
+ w.group = 'test'
8
+
9
+ w.start_if do |start|
10
+ start.condition(:process_running) do |c|
11
+ c.running = false
12
+ end
13
+ end
14
+ end
15
+ end
data/test/configs/test.rb CHANGED
@@ -12,6 +12,12 @@ God.init do |g|
12
12
  # g.pid_file_directory =
13
13
  end
14
14
 
15
+ class SimpleNotifier
16
+ def self.notify(str)
17
+ puts "Notifying: #{str}"
18
+ end
19
+ end
20
+
15
21
  God.watch do |w|
16
22
  w.name = "local-3000"
17
23
  w.interval = 5.seconds
@@ -21,14 +27,20 @@ God.watch do |w|
21
27
  w.restart_grace = 5.seconds
22
28
  w.stop_grace = 5.seconds
23
29
  w.autostart = true
24
- w.uid = 'tom'
25
- w.gid = 'tom'
30
+ w.uid = 'kev'
31
+ w.gid = 'kev'
26
32
  w.group = 'mongrels'
27
33
  w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid")
28
34
 
29
35
  # clean pid files before start if necessary
30
36
  w.behavior(:clean_pid_file)
31
37
 
38
+ w.behavior(:notify_when_flapping) do |b|
39
+ b.failures = 5
40
+ b.seconds = 60.seconds
41
+ b.notifier = SimpleNotifier
42
+ end
43
+
32
44
  # determine the state on startup
33
45
  w.transition(:init, { true => :up, false => :start }) do |on|
34
46
  on.condition(:process_running) do |c|
data/test/helper.rb CHANGED
@@ -3,6 +3,18 @@ require File.join(File.dirname(__FILE__), *%w[.. lib god])
3
3
  require 'test/unit'
4
4
  require 'set'
5
5
 
6
+ include God
7
+
8
+ if RUBY_PLATFORM =~ /linux/i && Process.uid != 0
9
+ abort <<-EOF
10
+ *********************************************************************
11
+ * *
12
+ * You need to run these tests as root (netlink requires it) *
13
+ * *
14
+ *********************************************************************
15
+ EOF
16
+ end
17
+
6
18
  begin
7
19
  require 'mocha'
8
20
  rescue LoadError
@@ -15,8 +27,6 @@ rescue LoadError
15
27
  end
16
28
  end
17
29
 
18
- include God
19
-
20
30
  module God
21
31
  module Conditions
22
32
  class FakeCondition < Condition
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestConditionsTries < Test::Unit::TestCase
4
+ def setup
5
+ @c = Conditions::Tries.new
6
+ @c.times = 3
7
+ @c.prepare
8
+ end
9
+
10
+ def test_prepare_should_create_timeline
11
+ assert 3, @c.instance_variable_get(:@timeline).instance_variable_get(:@max_size)
12
+ end
13
+
14
+ def test_test_should_return_true_if_called_three_times_within_one_second
15
+ assert !@c.test
16
+ assert !@c.test
17
+ assert @c.test
18
+ end
19
+
20
+ def test_test_should_return_false_on_fourth_call_if_called_three_times_within_one_second
21
+ 3.times { @c.test }
22
+ assert !@c.test
23
+ end
24
+ end
25
+
26
+ class TestConditionsTriesWithin < Test::Unit::TestCase
27
+ def setup
28
+ @c = Conditions::Tries.new
29
+ @c.times = 3
30
+ @c.within = 1.seconds
31
+ @c.prepare
32
+ end
33
+
34
+ def test_test_should_return_true_if_called_three_times_within_one_second
35
+ assert !@c.test
36
+ assert !@c.test
37
+ assert @c.test
38
+ end
39
+
40
+ def test_test_should_return_false_if_called_three_times_within_two_seconds
41
+ assert !@c.test
42
+ assert !@c.test
43
+ assert sleep(1.1)
44
+ assert !@c.test
45
+ end
46
+ end