samhendley-god 0.7.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/History.txt +293 -0
  2. data/Manifest.txt +114 -0
  3. data/README.txt +60 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +128 -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 +667 -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 +229 -0
  19. data/lib/god/cli/run.rb +176 -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/file_mtime.rb +28 -0
  28. data/lib/god/conditions/flapping.rb +128 -0
  29. data/lib/god/conditions/http_response_code.rb +168 -0
  30. data/lib/god/conditions/lambda.rb +25 -0
  31. data/lib/god/conditions/memory_usage.rb +82 -0
  32. data/lib/god/conditions/process_exits.rb +72 -0
  33. data/lib/god/conditions/process_running.rb +74 -0
  34. data/lib/god/conditions/tries.rb +44 -0
  35. data/lib/god/configurable.rb +57 -0
  36. data/lib/god/contact.rb +106 -0
  37. data/lib/god/contacts/campfire.rb +82 -0
  38. data/lib/god/contacts/email.rb +95 -0
  39. data/lib/god/contacts/jabber.rb +65 -0
  40. data/lib/god/contacts/twitter.rb +39 -0
  41. data/lib/god/contacts/webhook.rb +47 -0
  42. data/lib/god/dependency_graph.rb +41 -0
  43. data/lib/god/diagnostics.rb +37 -0
  44. data/lib/god/driver.rb +206 -0
  45. data/lib/god/errors.rb +24 -0
  46. data/lib/god/event_handler.rb +111 -0
  47. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  48. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  49. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  50. data/lib/god/logger.rb +120 -0
  51. data/lib/god/metric.rb +59 -0
  52. data/lib/god/process.rb +342 -0
  53. data/lib/god/registry.rb +32 -0
  54. data/lib/god/simple_logger.rb +53 -0
  55. data/lib/god/socket.rb +96 -0
  56. data/lib/god/sugar.rb +47 -0
  57. data/lib/god/system/portable_poller.rb +42 -0
  58. data/lib/god/system/process.rb +42 -0
  59. data/lib/god/system/slash_proc_poller.rb +92 -0
  60. data/lib/god/task.rb +491 -0
  61. data/lib/god/timeline.rb +25 -0
  62. data/lib/god/trigger.rb +43 -0
  63. data/lib/god/watch.rb +184 -0
  64. data/test/configs/child_events/child_events.god +44 -0
  65. data/test/configs/child_events/simple_server.rb +3 -0
  66. data/test/configs/child_polls/child_polls.god +37 -0
  67. data/test/configs/child_polls/simple_server.rb +12 -0
  68. data/test/configs/complex/complex.god +59 -0
  69. data/test/configs/complex/simple_server.rb +3 -0
  70. data/test/configs/contact/contact.god +84 -0
  71. data/test/configs/contact/simple_server.rb +3 -0
  72. data/test/configs/daemon_events/daemon_events.god +37 -0
  73. data/test/configs/daemon_events/simple_server.rb +8 -0
  74. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  75. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  76. data/test/configs/daemon_polls/simple_server.rb +6 -0
  77. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  78. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  79. data/test/configs/matias/matias.god +50 -0
  80. data/test/configs/real.rb +59 -0
  81. data/test/configs/running_load/running_load.god +16 -0
  82. data/test/configs/stress/simple_server.rb +3 -0
  83. data/test/configs/stress/stress.god +15 -0
  84. data/test/configs/task/logs/.placeholder +0 -0
  85. data/test/configs/task/task.god +26 -0
  86. data/test/configs/test.rb +61 -0
  87. data/test/helper.rb +151 -0
  88. data/test/suite.rb +6 -0
  89. data/test/test_behavior.rb +21 -0
  90. data/test/test_campfire.rb +41 -0
  91. data/test/test_condition.rb +50 -0
  92. data/test/test_conditions_disk_usage.rb +56 -0
  93. data/test/test_conditions_http_response_code.rb +109 -0
  94. data/test/test_conditions_process_running.rb +44 -0
  95. data/test/test_conditions_tries.rb +67 -0
  96. data/test/test_contact.rb +109 -0
  97. data/test/test_dependency_graph.rb +62 -0
  98. data/test/test_driver.rb +11 -0
  99. data/test/test_email.rb +45 -0
  100. data/test/test_event_handler.rb +80 -0
  101. data/test/test_god.rb +598 -0
  102. data/test/test_handlers_kqueue_handler.rb +16 -0
  103. data/test/test_logger.rb +63 -0
  104. data/test/test_metric.rb +72 -0
  105. data/test/test_process.rb +246 -0
  106. data/test/test_registry.rb +15 -0
  107. data/test/test_socket.rb +42 -0
  108. data/test/test_sugar.rb +42 -0
  109. data/test/test_system_portable_poller.rb +17 -0
  110. data/test/test_system_process.rb +30 -0
  111. data/test/test_task.rb +262 -0
  112. data/test/test_timeline.rb +37 -0
  113. data/test/test_trigger.rb +59 -0
  114. data/test/test_watch.rb +279 -0
  115. metadata +193 -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