mojombo-god 0.7.7

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 (108) hide show
  1. data/History.txt +255 -0
  2. data/Manifest.txt +107 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +127 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god.rb +644 -0
  14. data/lib/god/behavior.rb +52 -0
  15. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  16. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  17. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  18. data/lib/god/cli/command.rb +206 -0
  19. data/lib/god/cli/run.rb +177 -0
  20. data/lib/god/cli/version.rb +23 -0
  21. data/lib/god/condition.rb +96 -0
  22. data/lib/god/conditions/always.rb +23 -0
  23. data/lib/god/conditions/complex.rb +86 -0
  24. data/lib/god/conditions/cpu_usage.rb +80 -0
  25. data/lib/god/conditions/degrading_lambda.rb +52 -0
  26. data/lib/god/conditions/disk_usage.rb +27 -0
  27. data/lib/god/conditions/flapping.rb +128 -0
  28. data/lib/god/conditions/http_response_code.rb +168 -0
  29. data/lib/god/conditions/lambda.rb +25 -0
  30. data/lib/god/conditions/memory_usage.rb +82 -0
  31. data/lib/god/conditions/process_exits.rb +72 -0
  32. data/lib/god/conditions/process_running.rb +74 -0
  33. data/lib/god/conditions/tries.rb +44 -0
  34. data/lib/god/configurable.rb +57 -0
  35. data/lib/god/contact.rb +106 -0
  36. data/lib/god/contacts/email.rb +95 -0
  37. data/lib/god/dependency_graph.rb +41 -0
  38. data/lib/god/diagnostics.rb +37 -0
  39. data/lib/god/driver.rb +108 -0
  40. data/lib/god/errors.rb +24 -0
  41. data/lib/god/event_handler.rb +111 -0
  42. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  43. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  44. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  45. data/lib/god/logger.rb +120 -0
  46. data/lib/god/metric.rb +59 -0
  47. data/lib/god/process.rb +325 -0
  48. data/lib/god/registry.rb +32 -0
  49. data/lib/god/simple_logger.rb +53 -0
  50. data/lib/god/socket.rb +96 -0
  51. data/lib/god/sugar.rb +47 -0
  52. data/lib/god/system/portable_poller.rb +42 -0
  53. data/lib/god/system/process.rb +42 -0
  54. data/lib/god/system/slash_proc_poller.rb +82 -0
  55. data/lib/god/task.rb +487 -0
  56. data/lib/god/timeline.rb +25 -0
  57. data/lib/god/trigger.rb +43 -0
  58. data/lib/god/watch.rb +183 -0
  59. data/test/configs/child_events/child_events.god +44 -0
  60. data/test/configs/child_events/simple_server.rb +3 -0
  61. data/test/configs/child_polls/child_polls.god +37 -0
  62. data/test/configs/child_polls/simple_server.rb +12 -0
  63. data/test/configs/complex/complex.god +59 -0
  64. data/test/configs/complex/simple_server.rb +3 -0
  65. data/test/configs/contact/contact.god +74 -0
  66. data/test/configs/contact/simple_server.rb +3 -0
  67. data/test/configs/daemon_events/daemon_events.god +37 -0
  68. data/test/configs/daemon_events/simple_server.rb +8 -0
  69. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  70. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  71. data/test/configs/daemon_polls/simple_server.rb +6 -0
  72. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  73. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  74. data/test/configs/matias/matias.god +50 -0
  75. data/test/configs/real.rb +59 -0
  76. data/test/configs/running_load/running_load.god +16 -0
  77. data/test/configs/stress/simple_server.rb +3 -0
  78. data/test/configs/stress/stress.god +15 -0
  79. data/test/configs/task/logs/.placeholder +0 -0
  80. data/test/configs/task/task.god +26 -0
  81. data/test/configs/test.rb +61 -0
  82. data/test/helper.rb +151 -0
  83. data/test/suite.rb +6 -0
  84. data/test/test_behavior.rb +21 -0
  85. data/test/test_condition.rb +50 -0
  86. data/test/test_conditions_disk_usage.rb +56 -0
  87. data/test/test_conditions_http_response_code.rb +109 -0
  88. data/test/test_conditions_process_running.rb +44 -0
  89. data/test/test_conditions_tries.rb +67 -0
  90. data/test/test_contact.rb +109 -0
  91. data/test/test_dependency_graph.rb +62 -0
  92. data/test/test_driver.rb +11 -0
  93. data/test/test_event_handler.rb +80 -0
  94. data/test/test_god.rb +598 -0
  95. data/test/test_handlers_kqueue_handler.rb +16 -0
  96. data/test/test_logger.rb +63 -0
  97. data/test/test_metric.rb +72 -0
  98. data/test/test_process.rb +246 -0
  99. data/test/test_registry.rb +15 -0
  100. data/test/test_socket.rb +42 -0
  101. data/test/test_sugar.rb +42 -0
  102. data/test/test_system_portable_poller.rb +17 -0
  103. data/test/test_system_process.rb +30 -0
  104. data/test/test_task.rb +262 -0
  105. data/test/test_timeline.rb +37 -0
  106. data/test/test_trigger.rb +59 -0
  107. data/test/test_watch.rb +279 -0
  108. metadata +186 -0
@@ -0,0 +1,168 @@
1
+ require 'net/http'
2
+
3
+ module God
4
+ module Conditions
5
+
6
+ # Condition Symbol :http_response_code
7
+ # Type: Poll
8
+ #
9
+ # Trigger based on the response from an HTTP request.
10
+ #
11
+ # Paramaters
12
+ # Required
13
+ # +host+ is the hostname to connect [required]
14
+ # --one of code_is or code_is_not--
15
+ # +code_is+ trigger if the response code IS one of these
16
+ # e.g. 500 or '500' or [404, 500] or %w{404 500}
17
+ # +code_is_not+ trigger if the response code IS NOT one of these
18
+ # e.g. 200 or '200' or [200, 302] or %w{200 302}
19
+ # Optional
20
+ # +port+ is the port to connect (default 80)
21
+ # +path+ is the path to connect (default '/')
22
+ # +headers+ is the hash of HTTP headers to send (default none)
23
+ # +times+ is the number of times after which to trigger (default 1)
24
+ # e.g. 3 (times in a row) or [3, 5] (three out of fives times)
25
+ # +timeout+ is the time to wait for a connection (default 60.seconds)
26
+ #
27
+ # Examples
28
+ #
29
+ # Trigger if the response code from www.example.com/foo/bar
30
+ # is not a 200 (or if the connection is refused or times out:
31
+ #
32
+ # on.condition(:http_response_code) do |c|
33
+ # c.host = 'www.example.com'
34
+ # c.path = '/foo/bar'
35
+ # c.code_is_not = 200
36
+ # end
37
+ #
38
+ # Trigger if the response code is a 404 or a 500 (will not
39
+ # be triggered by a connection refusal or timeout):
40
+ #
41
+ # on.condition(:http_response_code) do |c|
42
+ # c.host = 'www.example.com'
43
+ # c.path = '/foo/bar'
44
+ # c.code_is = [404, 500]
45
+ # end
46
+ #
47
+ # Trigger if the response code is not a 200 five times in a row:
48
+ #
49
+ # on.condition(:http_response_code) do |c|
50
+ # c.host = 'www.example.com'
51
+ # c.path = '/foo/bar'
52
+ # c.code_is_not = 200
53
+ # c.times = 5
54
+ # end
55
+ #
56
+ # Trigger if the response code is not a 200 or does not respond
57
+ # within 10 seconds:
58
+ #
59
+ # on.condition(:http_response_code) do |c|
60
+ # c.host = 'www.example.com'
61
+ # c.path = '/foo/bar'
62
+ # c.code_is_not = 200
63
+ # c.timeout = 10
64
+ # end
65
+ class HttpResponseCode < PollCondition
66
+ attr_accessor :code_is, # e.g. 500 or '500' or [404, 500] or %w{404 500}
67
+ :code_is_not, # e.g. 200 or '200' or [200, 302] or %w{200 302}
68
+ :times, # e.g. 3 or [3, 5]
69
+ :host, # e.g. www.example.com
70
+ :port, # e.g. 8080
71
+ :timeout, # e.g. 60.seconds
72
+ :path, # e.g. '/'
73
+ :headers # e.g. {'Host' => 'myvirtual.mydomain.com'}
74
+
75
+ def initialize
76
+ super
77
+ self.port = 80
78
+ self.path = '/'
79
+ self.headers = {}
80
+ self.times = [1, 1]
81
+ self.timeout = 60.seconds
82
+ end
83
+
84
+ def prepare
85
+ self.code_is = Array(self.code_is).map { |x| x.to_i } if self.code_is
86
+ self.code_is_not = Array(self.code_is_not).map { |x| x.to_i } if self.code_is_not
87
+
88
+ if self.times.kind_of?(Integer)
89
+ self.times = [self.times, self.times]
90
+ end
91
+
92
+ @timeline = Timeline.new(self.times[1])
93
+ @history = Timeline.new(self.times[1])
94
+ end
95
+
96
+ def reset
97
+ @timeline.clear
98
+ @history.clear
99
+ end
100
+
101
+ def valid?
102
+ valid = true
103
+ valid &= complain("Attribute 'host' must be specified", self) if self.host.nil?
104
+ valid &= complain("One (and only one) of attributes 'code_is' and 'code_is_not' must be specified", self) if
105
+ (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not)
106
+ valid
107
+ end
108
+
109
+ def test
110
+ response = nil
111
+
112
+ Net::HTTP.start(self.host, self.port) do |http|
113
+ http.read_timeout = self.timeout
114
+ response = http.get(self.path, self.headers)
115
+ end
116
+
117
+ actual_response_code = response.code.to_i
118
+ if self.code_is && self.code_is.include?(actual_response_code)
119
+ pass(actual_response_code)
120
+ elsif self.code_is_not && !self.code_is_not.include?(actual_response_code)
121
+ pass(actual_response_code)
122
+ else
123
+ fail(actual_response_code)
124
+ end
125
+ rescue Errno::ECONNREFUSED
126
+ self.code_is ? fail('Refused') : pass('Refused')
127
+ rescue Errno::ECONNRESET
128
+ self.code_is ? fail('Reset') : pass('Reset')
129
+ rescue EOFError
130
+ self.code_is ? fail('EOF') : pass('EOF')
131
+ rescue Timeout::Error
132
+ self.code_is ? fail('Timeout') : pass('Timeout')
133
+ rescue Errno::ETIMEDOUT
134
+ self.code_is ? fail('Timedout') : pass('Timedout')
135
+ rescue Exception => failure
136
+ self.code_is ? fail(failure.class.name) : pass(failure.class.name)
137
+ end
138
+
139
+ private
140
+
141
+ def pass(code)
142
+ @timeline << true
143
+ if @timeline.select { |x| x }.size >= self.times.first
144
+ self.info = "http response abnormal #{history(code, true)}"
145
+ true
146
+ else
147
+ self.info = "http response nominal #{history(code, true)}"
148
+ false
149
+ end
150
+ end
151
+
152
+ def fail(code)
153
+ @timeline << false
154
+ self.info = "http response nominal #{history(code, false)}"
155
+ false
156
+ end
157
+
158
+ def history(code, passed)
159
+ entry = code.to_s.dup
160
+ entry = '*' + entry if passed
161
+ @history << entry
162
+ '[' + @history.join(", ") + ']'
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,25 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Lambda < PollCondition
5
+ attr_accessor :lambda
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil?
10
+ valid
11
+ end
12
+
13
+ def test
14
+ if self.lambda.call()
15
+ self.info = "lambda condition was satisfied"
16
+ true
17
+ else
18
+ self.info = "lambda condition was not satisfied"
19
+ false
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :memory_usage
5
+ # Type: Poll
6
+ #
7
+ # Trigger when the resident memory of a process is above a specified limit.
8
+ #
9
+ # Paramaters
10
+ # Required
11
+ # +pid_file+ is the pid file of the process in question. Automatically
12
+ # populated for Watches.
13
+ # +above+ is the amount of resident memory (in kilobytes) above which
14
+ # the condition should trigger. You can also use the sugar
15
+ # methods #kilobytes, #megabytes, and #gigabytes to clarify
16
+ # this amount (see examples).
17
+ #
18
+ # Examples
19
+ #
20
+ # Trigger if the process is using more than 100 megabytes of resident
21
+ # memory (from a Watch):
22
+ #
23
+ # on.condition(:memory_usage) do |c|
24
+ # c.above = 100.megabytes
25
+ # end
26
+ #
27
+ # Non-Watch Tasks must specify a PID file:
28
+ #
29
+ # on.condition(:memory_usage) do |c|
30
+ # c.above = 100.megabytes
31
+ # c.pid_file = "/var/run/mongrel.3000.pid"
32
+ # end
33
+ class MemoryUsage < PollCondition
34
+ attr_accessor :above, :times, :pid_file
35
+
36
+ def initialize
37
+ super
38
+ self.above = nil
39
+ self.times = [1, 1]
40
+ end
41
+
42
+ def prepare
43
+ if self.times.kind_of?(Integer)
44
+ self.times = [self.times, self.times]
45
+ end
46
+
47
+ @timeline = Timeline.new(self.times[1])
48
+ end
49
+
50
+ def reset
51
+ @timeline.clear
52
+ end
53
+
54
+ def pid
55
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
56
+ end
57
+
58
+ def valid?
59
+ valid = true
60
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil?
61
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
62
+ valid
63
+ end
64
+
65
+ def test
66
+ process = System::Process.new(self.pid)
67
+ @timeline.push(process.memory)
68
+
69
+ history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}kb" }.join(", ") + "]"
70
+
71
+ if @timeline.select { |x| x > self.above }.size >= self.times.first
72
+ self.info = "memory out of bounds #{history}"
73
+ return true
74
+ else
75
+ self.info = "memory within bounds #{history}"
76
+ return false
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,72 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :process_exits
5
+ # Type: Event
6
+ #
7
+ # Trigger when a process exits.
8
+ #
9
+ # Paramaters
10
+ # Required
11
+ # +pid_file+ is the pid file of the process in question. Automatically
12
+ # populated for Watches.
13
+ #
14
+ # Examples
15
+ #
16
+ # Trigger if process exits (from a Watch):
17
+ #
18
+ # on.condition(:process_exits)
19
+ #
20
+ # Trigger if process exits:
21
+ #
22
+ # on.condition(:process_exits) do |c|
23
+ # c.pid_file = "/var/run/mongrel.3000.pid"
24
+ # end
25
+ class ProcessExits < EventCondition
26
+ attr_accessor :pid_file
27
+
28
+ def initialize
29
+ self.info = "process exited"
30
+ end
31
+
32
+ def valid?
33
+ true
34
+ end
35
+
36
+ def pid
37
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
38
+ end
39
+
40
+ def register
41
+ pid = self.pid
42
+
43
+ begin
44
+ EventHandler.register(pid, :proc_exit) do |extra|
45
+ formatted_extra = extra.size > 0 ? " #{extra.inspect}" : ""
46
+ self.info = "process #{pid} exited#{formatted_extra}"
47
+ self.watch.trigger(self)
48
+ end
49
+
50
+ msg = "#{self.watch.name} registered 'proc_exit' event for pid #{pid}"
51
+ applog(self.watch, :info, msg)
52
+ rescue StandardError
53
+ raise EventRegistrationFailedError.new
54
+ end
55
+ end
56
+
57
+ def deregister
58
+ pid = self.pid
59
+ if pid
60
+ EventHandler.deregister(pid, :proc_exit)
61
+
62
+ msg = "#{self.watch.name} deregistered 'proc_exit' event for pid #{pid}"
63
+ applog(self.watch, :info, msg)
64
+ else
65
+ pid_file_location = self.pid_file || self.watch.pid_file
66
+ applog(self.watch, :error, "#{self.watch.name} could not deregister: no cached PID or PID file #{pid_file_location} (#{self.base_name})")
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,74 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :process_running
5
+ # Type: Poll
6
+ #
7
+ # Trigger when a process is running or not running depending on attributes.
8
+ #
9
+ # Paramaters
10
+ # Required
11
+ # +pid_file+ is the pid file of the process in question. Automatically
12
+ # populated for Watches.
13
+ # +running" specifies whether you want to trigger if the process is
14
+ # running (true) or whether it is not running (false)
15
+ #
16
+ # Examples
17
+ #
18
+ # Trigger if process IS NOT running (from a Watch):
19
+ #
20
+ # on.condition(:process_running) do |c|
21
+ # c.running = false
22
+ # end
23
+ #
24
+ # Trigger if process IS running (from a Watch):
25
+ #
26
+ # on.condition(:process_running) do |c|
27
+ # c.running = true
28
+ # end
29
+ #
30
+ # Non-Watch Tasks must specify a PID file:
31
+ #
32
+ # on.condition(:process_running) do |c|
33
+ # c.running = false
34
+ # c.pid_file = "/var/run/mongrel.3000.pid"
35
+ # end
36
+ class ProcessRunning < PollCondition
37
+ attr_accessor :running, :pid_file
38
+
39
+ def pid
40
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
41
+ end
42
+
43
+ def valid?
44
+ valid = true
45
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil?
46
+ valid &= complain("Attribute 'running' must be specified", self) if self.running.nil?
47
+ valid
48
+ end
49
+
50
+ def test
51
+ self.info = []
52
+
53
+ pid = self.pid
54
+ active = pid && System::Process.new(pid).exists?
55
+
56
+ if (self.running && active)
57
+ self.info.concat(["process is running"])
58
+ true
59
+ elsif (!self.running && !active)
60
+ self.info.concat(["process is not running"])
61
+ true
62
+ else
63
+ if self.running
64
+ self.info.concat(["process is not running"])
65
+ else
66
+ self.info.concat(["process is running"])
67
+ end
68
+ false
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Tries < PollCondition
5
+ attr_accessor :times, :within
6
+
7
+ def prepare
8
+ @timeline = Timeline.new(self.times)
9
+ end
10
+
11
+ def reset
12
+ @timeline.clear
13
+ end
14
+
15
+ def valid?
16
+ valid = true
17
+ valid &= complain("Attribute 'times' must be specified", self) if self.times.nil?
18
+ valid
19
+ end
20
+
21
+ def test
22
+ @timeline << Time.now
23
+
24
+ concensus = (@timeline.size == self.times)
25
+ duration = self.within.nil? || (@timeline.last - @timeline.first) < self.within
26
+
27
+ if within
28
+ history = "[#{@timeline.size}/#{self.times} within #{(@timeline.last - @timeline.first).to_i}s]"
29
+ else
30
+ history = "[#{@timeline.size}/#{self.times}]"
31
+ end
32
+
33
+ if concensus && duration
34
+ self.info = "tries exceeded #{history}"
35
+ return true
36
+ else
37
+ self.info = "tries within bounds #{history}"
38
+ return false
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end