god 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/History.txt +26 -2
  2. data/Manifest.txt +19 -5
  3. data/Rakefile +7 -2
  4. data/bin/god +131 -11
  5. data/examples/events.god +52 -0
  6. data/examples/gravatar.god +29 -35
  7. data/ext/god/extconf.rb +49 -2
  8. data/ext/god/kqueue_handler.c +2 -2
  9. data/ext/god/netlink_handler.c +17 -4
  10. data/lib/god.rb +127 -25
  11. data/lib/god/behavior.rb +8 -3
  12. data/lib/god/behaviors/clean_pid_file.rb +2 -8
  13. data/lib/god/condition.rb +6 -5
  14. data/lib/god/conditions/cpu_usage.rb +4 -4
  15. data/lib/god/conditions/lambda.rb +19 -0
  16. data/lib/god/conditions/memory_usage.rb +4 -4
  17. data/lib/god/conditions/process_exits.rb +5 -7
  18. data/lib/god/conditions/process_running.rb +4 -4
  19. data/lib/god/errors.rb +3 -0
  20. data/lib/god/event_handler.rb +28 -3
  21. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  22. data/lib/god/event_handlers/kqueue_handler.rb +2 -0
  23. data/lib/god/event_handlers/netlink_handler.rb +2 -0
  24. data/lib/god/hub.rb +40 -23
  25. data/lib/god/metric.rb +4 -4
  26. data/lib/god/process.rb +105 -0
  27. data/lib/god/registry.rb +28 -0
  28. data/lib/god/server.rb +3 -4
  29. data/lib/god/sugar.rb +47 -0
  30. data/lib/god/system/process.rb +1 -2
  31. data/lib/god/timer.rb +13 -6
  32. data/lib/god/watch.rb +61 -29
  33. data/test/configs/child_events/child_events.god +25 -0
  34. data/test/configs/child_events/simple_server.rb +3 -0
  35. data/test/configs/child_polls/child_polls.god +15 -0
  36. data/test/configs/child_polls/simple_server.rb +3 -0
  37. data/test/configs/daemon_events/daemon_events.god +30 -0
  38. data/test/configs/daemon_events/simple_server.rb +6 -0
  39. data/test/configs/real.rb +47 -49
  40. data/test/configs/test.rb +52 -62
  41. data/test/helper.rb +44 -14
  42. data/test/test_behavior.rb +10 -2
  43. data/test/test_condition.rb +19 -3
  44. data/test/test_conditions_process_running.rb +42 -0
  45. data/test/test_event_handler.rb +73 -0
  46. data/test/test_god.rb +206 -9
  47. data/test/test_handlers_kqueue_handler.rb +12 -0
  48. data/test/test_hub.rb +157 -0
  49. data/test/test_metric.rb +30 -2
  50. data/test/test_process.rb +84 -0
  51. data/test/test_registry.rb +14 -0
  52. data/test/test_server.rb +3 -2
  53. data/test/test_sugar.rb +42 -0
  54. data/test/test_system_process.rb +1 -1
  55. data/test/test_timer.rb +8 -1
  56. data/test/test_watch.rb +137 -2
  57. metadata +28 -17
  58. data/examples/local.god +0 -60
  59. data/ext/god/Makefile +0 -149
  60. data/lib/god/base.rb +0 -13
  61. data/lib/god/meddle.rb +0 -38
  62. data/test/test_meddle.rb +0 -46
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestHandlersKqueueHandler < Test::Unit::TestCase
4
+ def test_register_process
5
+ KQueueHandler.expects(:monitor_process).with(1234, 2147483648)
6
+ KQueueHandler.register_process(1234, [:proc_exit])
7
+ end
8
+
9
+ def test_events_mask
10
+ assert_equal 2147483648, KQueueHandler.events_mask([:proc_exit])
11
+ end
12
+ end
@@ -0,0 +1,157 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestHub < Test::Unit::TestCase
4
+ def setup
5
+ Server.stubs(:new).returns(true)
6
+ God.reset
7
+
8
+ God.watch do |w|
9
+ w.name = 'foo'
10
+ w.interval = 10
11
+ end
12
+
13
+ @watch = God.watches['foo']
14
+ end
15
+
16
+ # attach
17
+
18
+ def test_attach_should_store_condition_metric_association
19
+ c = Conditions::FakePollCondition.new
20
+ m = Metric.new(@watch, :foo)
21
+ Hub.attach(c, m)
22
+
23
+ assert_equal m, Hub.directory[c]
24
+ end
25
+
26
+ def test_attach_should_schedule_for_poll_condition
27
+ c = Conditions::FakePollCondition.new
28
+ m = Metric.new(@watch, :foo)
29
+
30
+ Timer.any_instance.expects(:schedule).with(c, 0)
31
+
32
+ Hub.attach(c, m)
33
+ end
34
+
35
+ def test_attach_should_regsiter_for_event_condition
36
+ c = Conditions::FakeEventCondition.new
37
+ m = Metric.new(@watch, :foo)
38
+
39
+ c.expects(:register)
40
+
41
+ Hub.attach(c, m)
42
+ end
43
+
44
+ # detach
45
+
46
+ def test_detach_should_remove_condition_metric_association
47
+ c = Conditions::FakePollCondition.new
48
+ m = Metric.new(@watch, :foo)
49
+
50
+ Hub.attach(c, m)
51
+ Hub.detach(c)
52
+
53
+ assert_nil Hub.directory[c]
54
+ end
55
+
56
+ def test_detach_should_unschedule_poll_conditions
57
+ c = Conditions::FakePollCondition.new
58
+ m = Metric.new(@watch, :foo)
59
+ Hub.attach(c, m)
60
+
61
+ Timer.any_instance.expects(:unschedule).with(c)
62
+ c.expects(:deregister).never
63
+
64
+ Hub.detach(c)
65
+ end
66
+
67
+ def test_detach_should_deregister_event_conditions
68
+ c = Conditions::FakeEventCondition.new
69
+ m = Metric.new(@watch, :foo)
70
+ Hub.attach(c, m)
71
+
72
+ c.expects(:deregister).once
73
+
74
+ Hub.detach(c)
75
+ end
76
+
77
+ # trigger
78
+
79
+ def test_trigger_should_handle_poll_for_poll_condition
80
+ c = Conditions::FakePollCondition.new
81
+ Hub.expects(:handle_poll).with(c)
82
+
83
+ Hub.trigger(c)
84
+ end
85
+
86
+ def test_trigger_should_handle_event_for_event_condition
87
+ c = Conditions::FakeEventCondition.new
88
+ Hub.expects(:handle_event).with(c)
89
+
90
+ Hub.trigger(c)
91
+ end
92
+
93
+ # handle_poll
94
+
95
+ def test_handle_poll_no_change_should_reschedule
96
+ c = Conditions::FakePollCondition.new
97
+ c.interval = 10
98
+
99
+ m = Metric.new(@watch, {true => :up})
100
+ Hub.attach(c, m)
101
+
102
+ c.expects(:test).returns(false)
103
+ Timer.any_instance.expects(:schedule)
104
+
105
+ no_stdout do
106
+ t = Hub.handle_poll(c)
107
+ t.join
108
+ end
109
+ end
110
+
111
+ def test_handle_poll_change_should_move
112
+ c = Conditions::FakePollCondition.new
113
+ c.interval = 10
114
+
115
+ m = Metric.new(@watch, {true => :up})
116
+ Hub.attach(c, m)
117
+
118
+ c.expects(:test).returns(true)
119
+ @watch.expects(:move).with(:up)
120
+
121
+ no_stdout do
122
+ t = Hub.handle_poll(c)
123
+ t.join
124
+ end
125
+ end
126
+
127
+ def test_handle_poll_should_abort_on_exception
128
+ c = Conditions::FakePollCondition.new
129
+ c.interval = 10
130
+
131
+ m = Metric.new(@watch, {true => :up})
132
+ Hub.attach(c, m)
133
+
134
+ c.expects(:test).raises(StandardError.new)
135
+
136
+ assert_abort do
137
+ t = Hub.handle_poll(c)
138
+ t.join
139
+ end
140
+ end
141
+
142
+ # handle_event
143
+
144
+ def test_handle_event_should_move
145
+ c = Conditions::FakeEventCondition.new
146
+
147
+ m = Metric.new(@watch, {true => :up})
148
+ Hub.attach(c, m)
149
+
150
+ @watch.expects(:move).with(:up)
151
+
152
+ no_stdout do
153
+ t = Hub.handle_event(c)
154
+ t.join
155
+ end
156
+ end
157
+ end
@@ -36,7 +36,7 @@ class TestMetric < Test::Unit::TestCase
36
36
  def test_poll_condition_should_abort_if_no_interval_and_no_watch_interval
37
37
  metric = Metric.new(stub(:name => 'foo', :interval => nil), nil)
38
38
 
39
- assert_raise AbortCalledError do
39
+ assert_abort do
40
40
  metric.condition(:fake_poll_condition)
41
41
  end
42
42
  end
@@ -53,8 +53,36 @@ class TestMetric < Test::Unit::TestCase
53
53
  def test_condition_should_abort_if_not_subclass_of_poll_or_event
54
54
  metric = Metric.new(stub(:name => 'foo', :interval => 10), nil)
55
55
 
56
- assert_raise AbortCalledError do
56
+ assert_abort do
57
57
  metric.condition(:fake_condition) { |c| }
58
58
  end
59
59
  end
60
+
61
+ def test_condition_should_abort_on_invalid_condition
62
+ assert_abort do
63
+ @metric.condition(:fake_poll_condition) { |c| c.stubs(:valid?).returns(false) }
64
+ end
65
+ end
66
+
67
+ def test_condition_should_abort_on_no_such_condition
68
+ assert_abort do
69
+ @metric.condition(:invalid) { }
70
+ end
71
+ end
72
+
73
+ # enable
74
+
75
+ def test_enable_should_attach_conditions
76
+ @metric.condition(:fake_poll_condition)
77
+ Hub.expects(:attach).with(@metric.conditions.first, @metric)
78
+ @metric.enable
79
+ end
80
+
81
+ # disable
82
+
83
+ def test_disable_should_detach_conditions
84
+ @metric.condition(:fake_poll_condition)
85
+ Hub.expects(:detach).with(@metric.conditions.first)
86
+ @metric.disable
87
+ end
60
88
  end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ module God
4
+ class Process
5
+ def fork
6
+ raise "You forgot to stub fork"
7
+ end
8
+
9
+ def exec(*args)
10
+ raise "You forgot to stub exec"
11
+ end
12
+ end
13
+ end
14
+
15
+ class TestProcess < Test::Unit::TestCase
16
+ def setup
17
+ @p = God::Process.new(:name => 'foo', :pid_file => 'blah.pid')
18
+ @p.stubs(:test).returns true # so we don't try to mkdir_p
19
+ Process.stubs(:detach) # because we stub fork
20
+ end
21
+
22
+ # These actually excercise call_action in the back at this point - Kev
23
+ def test_call_action_with_string_should_fork_exec
24
+ @p.start = "do something"
25
+ IO.expects(:pipe).returns([StringIO.new('1234'), StringIO.new])
26
+ @p.expects(:fork)
27
+ Process.expects(:waitpid)
28
+ @p.call_action(:start)
29
+ end
30
+
31
+ def test_call_action_with_lambda_should_call
32
+ cmd = lambda { puts "Hi" }
33
+ cmd.expects(:call)
34
+ @p.start = cmd
35
+ @p.call_action(:start)
36
+ end
37
+
38
+ def test_default_pid_file
39
+ assert_equal File.join(God.pid_file_directory, 'foo.pid'), @p.default_pid_file
40
+ end
41
+
42
+ def test_call_action_without_pid_should_write_pid
43
+ # Only for start, restart
44
+ [:start, :restart].each do |action|
45
+ @p = God::Process.new(:name => 'foo')
46
+ @p.stubs(:test).returns true
47
+ IO.expects(:pipe).returns([StringIO.new('1234'), StringIO.new])
48
+ @p.expects(:fork)
49
+ Process.expects(:waitpid)
50
+ File.expects(:open).with(@p.default_pid_file, 'w')
51
+ @p.send("#{action}=", "run")
52
+ @p.call_action(action)
53
+ end
54
+ end
55
+
56
+ def test_call_action_should_not_write_pid_for_stop
57
+ @p.pid_file = nil
58
+ IO.expects(:pipe).returns([StringIO.new('1234'), StringIO.new])
59
+ @p.expects(:fork)
60
+ Process.expects(:waitpid)
61
+ File.expects(:open).times(0)
62
+ @p.stop = "stopping"
63
+ @p.call_action(:stop)
64
+ end
65
+
66
+ def test_call_action_should_mkdir_p_if_pid_file_dir_existence_test_fails
67
+ @p.pid_file = nil
68
+ IO.expects(:pipe).returns([StringIO.new('1234'), StringIO.new])
69
+ @p.expects(:fork)
70
+ Process.expects(:waitpid)
71
+ @p.expects(:test).returns(false, true)
72
+ FileUtils.expects(:mkdir_p).with(God.pid_file_directory)
73
+ File.expects(:open)
74
+ @p.start = "starting"
75
+ @p.call_action(:start)
76
+ end
77
+
78
+ def test_start_stop_restart_bang
79
+ [:start, :stop, :restart].each do |x|
80
+ @p.expects(:call_action).with(x)
81
+ @p.send("#{x}!")
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestRegistry < Test::Unit::TestCase
4
+ def setup
5
+ God.registry.reset
6
+ end
7
+
8
+ def test_add
9
+ foo = God::Process.new(:name => 'foo')
10
+ God.registry.add(foo)
11
+ assert_equal 1, God.registry.size
12
+ assert_equal foo, God.registry['foo']
13
+ end
14
+ end
@@ -14,11 +14,12 @@ class TestServer < Test::Unit::TestCase
14
14
 
15
15
  def test_should_use_supplied_port_and_host
16
16
  DRb.expects(:start_service).with { |uri, object| uri == "druby://host:port" && object.is_a?(Server) }
17
- server = Server.new(nil, 'host', 'port')
17
+ server = Server.new('host', 'port')
18
18
  end
19
19
 
20
20
  def test_should_forward_foreign_method_calls_to_meddle
21
- server = Server.new(mock(:something_random => true))
21
+ server = Server.new
22
+ God.expects(:send).with(:something_random)
22
23
  server.something_random
23
24
  end
24
25
  end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestSugar < Test::Unit::TestCase
4
+ def test_seconds
5
+ assert_equal 1, 1.seconds
6
+ assert_equal 1, 1.second
7
+ end
8
+
9
+ def test_minutes
10
+ assert_equal 60, 1.minutes
11
+ assert_equal 60, 1.minute
12
+ end
13
+
14
+ def test_hours
15
+ assert_equal 3600, 1.hours
16
+ assert_equal 3600, 1.hour
17
+ end
18
+
19
+ def test_days
20
+ assert_equal 86400, 1.days
21
+ assert_equal 86400, 1.day
22
+ end
23
+
24
+ def test_kilobytes
25
+ assert_equal 1, 1.kilobytes
26
+ assert_equal 1, 1.kilobyte
27
+ end
28
+
29
+ def test_megabytes
30
+ assert_equal 1024, 1.megabytes
31
+ assert_equal 1024, 1.megabyte
32
+ end
33
+
34
+ def test_gigabytes
35
+ assert_equal 1024 ** 2, 1.gigabytes
36
+ assert_equal 1024 ** 2, 1.gigabyte
37
+ end
38
+
39
+ def test_percent
40
+ assert_equal 1, 1.percent
41
+ end
42
+ end
@@ -11,7 +11,7 @@ class TestSystemProcess < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def test_exists_should_return_false_for_non_existant_process
14
- assert_equal false, System::Process.new(0).exists?
14
+ assert_equal false, System::Process.new(9999999).exists?
15
15
  end
16
16
 
17
17
  def test_memory
@@ -13,7 +13,7 @@ class TestTimer < Test::Unit::TestCase
13
13
  def test_schedule_should_queue_event
14
14
  Time.stubs(:now).returns(0)
15
15
 
16
- w = Watch.new(nil)
16
+ w = Watch.new
17
17
  @t.schedule(stub(:interval => 20))
18
18
 
19
19
  assert_equal 1, @t.events.size
@@ -50,4 +50,11 @@ class TestTimer < Test::Unit::TestCase
50
50
  @t.unschedule(a)
51
51
  assert_equal 1, @t.events.size
52
52
  end
53
+
54
+ # join
55
+
56
+ def test_join_should_join
57
+ Thread.any_instance.expects(:join)
58
+ @t.join
59
+ end
53
60
  end
@@ -2,7 +2,10 @@ require File.dirname(__FILE__) + '/helper'
2
2
 
3
3
  class TestWatch < Test::Unit::TestCase
4
4
  def setup
5
- @watch = Watch.new(nil)
5
+ @watch = Watch.new
6
+ @watch.name = 'foo'
7
+ @watch.start = lambda { }
8
+ @watch.stop = lambda { }
6
9
  end
7
10
 
8
11
  # new
@@ -47,10 +50,16 @@ class TestWatch < Test::Unit::TestCase
47
50
  assert_equal beh, @watch.behaviors.first
48
51
  end
49
52
 
53
+ def test_invalid_behavior_should_abort
54
+ assert_abort do
55
+ @watch.behavior(:invalid)
56
+ end
57
+ end
58
+
50
59
  # transition
51
60
 
52
61
  def test_transition_should_abort_on_invalid_start_state
53
- assert_raise AbortCalledError do
62
+ assert_abort do
54
63
  @watch.transition(:foo, :bar)
55
64
  end
56
65
  end
@@ -82,6 +91,132 @@ class TestWatch < Test::Unit::TestCase
82
91
  assert_equal 1, @watch.metrics[:up].size
83
92
  end
84
93
 
94
+ # monitor
95
+
96
+ def test_monitor_should_move_to_init_if_available
97
+ @watch.instance_eval do
98
+ transition(:init, :up) { }
99
+ end
100
+ @watch.expects(:move).with(:init)
101
+ @watch.monitor
102
+ end
103
+
104
+ def test_monitor_should_move_to_up_if_no_init_available
105
+ @watch.expects(:move).with(:up)
106
+ @watch.monitor
107
+ end
108
+
109
+ # unmonitor
110
+
111
+ def test_unmonitor_should_move_to_nil
112
+ @watch.expects(:move).with(nil)
113
+ @watch.unmonitor
114
+ end
115
+
116
+ # move
117
+
118
+ def test_move_should_not_clean_up_if_from_state_is_nil
119
+ metric = nil
120
+
121
+ @watch.instance_eval do
122
+ transition(:init, :up) do |on|
123
+ metric = on
124
+ on.condition(:process_running) do |c|
125
+ c.running = true
126
+ c.interval = 10
127
+ end
128
+ end
129
+ end
130
+
131
+ metric.expects(:disable).never
132
+
133
+ no_stdout { @watch.move(:init) }
134
+ end
135
+
136
+ def test_move_should_clean_up_from_state_if_not_nil
137
+ metric = nil
138
+
139
+ @watch.instance_eval do
140
+ transition(:init, :up) do |on|
141
+ metric = on
142
+ on.condition(:process_running) do |c|
143
+ c.running = true
144
+ c.interval = 10
145
+ end
146
+ end
147
+ end
148
+
149
+ no_stdout { @watch.move(:init) }
150
+
151
+ metric.expects(:disable)
152
+
153
+ no_stdout { @watch.move(:up) }
154
+ end
155
+
156
+ def test_move_should_call_action
157
+ @watch.expects(:action).with(:start)
158
+
159
+ no_stdout { @watch.move(:start) }
160
+ end
161
+
162
+ def test_move_should_move_to_up_state_if_no_start_or_restart_metric
163
+ [:start, :restart].each do |state|
164
+ @watch.expects(:action)
165
+ no_stdout { @watch.move(state) }
166
+ assert_equal :up, @watch.state
167
+ end
168
+ end
169
+
170
+ def test_move_should_enable_destination_metric
171
+ metric = nil
172
+
173
+ @watch.instance_eval do
174
+ transition(:init, :up) do |on|
175
+ metric = on
176
+ on.condition(:process_running) do |c|
177
+ c.running = true
178
+ c.interval = 10
179
+ end
180
+ end
181
+ end
182
+
183
+ metric.expects(:enable)
184
+
185
+ no_stdout { @watch.move(:init) }
186
+ end
187
+
188
+ # action
189
+
190
+ def test_action_should_pass_start_and_stop_actions_to_call_action
191
+ c = Conditions::FakePollCondition.new
192
+ [:start, :stop].each do |cmd|
193
+ @watch.expects(:call_action).with(c, cmd)
194
+ no_stdout { @watch.action(cmd, c) }
195
+ end
196
+ end
197
+
198
+ def test_action_should_do_stop_then_start_if_no_restart_command
199
+ c = Conditions::FakePollCondition.new
200
+ @watch.expects(:call_action).with(c, :stop)
201
+ @watch.expects(:call_action).with(c, :start)
202
+ no_stdout { @watch.action(:restart, c) }
203
+ end
204
+
205
+ def test_action_should_restart_to_call_action_if_present
206
+ @watch.restart = lambda { }
207
+ c = Conditions::FakePollCondition.new
208
+ @watch.expects(:call_action).with(c, :restart)
209
+ no_stdout { @watch.action(:restart, c) }
210
+ end
211
+
212
+ # call_action
213
+
214
+ def test_call_action
215
+ c = Conditions::FakePollCondition.new
216
+ God::Process.any_instance.expects(:call_action).with(:start)
217
+ @watch.call_action(c, :start)
218
+ end
219
+
85
220
  # canonical_hash_form
86
221
 
87
222
  def test_canonical_hash_form_should_convert_symbol_to_hash