god 0.7.8 → 0.7.10
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.
- data/History.txt +21 -0
- data/Manifest.txt +7 -0
- data/Rakefile +1 -1
- data/bin/god +1 -0
- data/init/god +0 -0
- data/lib/god.rb +24 -1
- data/lib/god/cli/command.rb +24 -1
- data/lib/god/cli/run.rb +16 -20
- data/lib/god/conditions/file_mtime.rb +28 -0
- data/lib/god/contacts/campfire.rb +82 -0
- data/lib/god/contacts/jabber.rb +65 -0
- data/lib/god/contacts/twitter.rb +39 -0
- data/lib/god/contacts/webhook.rb +47 -0
- data/lib/god/driver.rb +148 -50
- data/lib/god/process.rb +17 -3
- data/lib/god/system/slash_proc_poller.rb +11 -1
- data/lib/god/task.rb +4 -0
- data/lib/god/watch.rb +2 -2
- data/test/configs/child_events/simple_server.rb +0 -0
- data/test/configs/child_polls/simple_server.rb +0 -0
- data/test/configs/complex/simple_server.rb +0 -0
- data/test/configs/contact/contact.god +13 -3
- data/test/configs/contact/simple_server.rb +0 -0
- data/test/test_campfire.rb +41 -0
- data/test/test_conditions_process_running.rb +1 -1
- data/test/test_webhook.rb +17 -0
- metadata +65 -48
data/History.txt
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
== 0.7.10 / 2008-11-13
|
2
|
+
* Major Enhancements
|
3
|
+
* Enable sending of arbitrary signals to a task or group via `god signal`
|
4
|
+
* Bug Fixes
|
5
|
+
* setup logging *after* loading a given config file when daemonized.
|
6
|
+
enables logging to the 'God.log_file' specified in a config file. [github.com/jnewland]
|
7
|
+
* New Conditions
|
8
|
+
* FileMtime < PollCondition - trigger on file mtime durations [github.com/jwilkins]
|
9
|
+
* New Contacts
|
10
|
+
* Twitter - allow messages to twitter [github.com/jwilkins]
|
11
|
+
* Campfire - send messages to 37signals' Campfire [github.com/hellvinz]
|
12
|
+
* Minor Enhancements
|
13
|
+
* Add watch log_cmd that can be reopened with STDOUT instead of a log file [github.com/jberkel]
|
14
|
+
* Added webhook output support [Martyn Loughran]
|
15
|
+
|
16
|
+
== 0.7.9 / 2008-08-06
|
17
|
+
* Major Enhancements
|
18
|
+
* Use a psuedo-priority queue for more efficient driver loop [Darrell Kresge]
|
19
|
+
* Bug Fixes
|
20
|
+
* Fix file_writable? when using chroot [github.com/eric]
|
21
|
+
|
1
22
|
== 0.7.8 / 2008-07-09
|
2
23
|
* Bug Fixes
|
3
24
|
* Catch all Exceptions from HttpResponseCode condition [github.com/rliebling]
|
data/Manifest.txt
CHANGED
@@ -24,6 +24,7 @@ lib/god/conditions/complex.rb
|
|
24
24
|
lib/god/conditions/cpu_usage.rb
|
25
25
|
lib/god/conditions/degrading_lambda.rb
|
26
26
|
lib/god/conditions/disk_usage.rb
|
27
|
+
lib/god/conditions/file_mtime.rb
|
27
28
|
lib/god/conditions/flapping.rb
|
28
29
|
lib/god/conditions/http_response_code.rb
|
29
30
|
lib/god/conditions/lambda.rb
|
@@ -33,7 +34,11 @@ lib/god/conditions/process_running.rb
|
|
33
34
|
lib/god/conditions/tries.rb
|
34
35
|
lib/god/configurable.rb
|
35
36
|
lib/god/contact.rb
|
37
|
+
lib/god/contacts/campfire.rb
|
36
38
|
lib/god/contacts/email.rb
|
39
|
+
lib/god/contacts/jabber.rb
|
40
|
+
lib/god/contacts/twitter.rb
|
41
|
+
lib/god/contacts/webhook.rb
|
37
42
|
lib/god/dependency_graph.rb
|
38
43
|
lib/god/diagnostics.rb
|
39
44
|
lib/god/driver.rb
|
@@ -82,6 +87,7 @@ test/configs/test.rb
|
|
82
87
|
test/helper.rb
|
83
88
|
test/suite.rb
|
84
89
|
test/test_behavior.rb
|
90
|
+
test/test_campfire.rb
|
85
91
|
test/test_condition.rb
|
86
92
|
test/test_conditions_disk_usage.rb
|
87
93
|
test/test_conditions_http_response_code.rb
|
@@ -90,6 +96,7 @@ test/test_conditions_tries.rb
|
|
90
96
|
test/test_contact.rb
|
91
97
|
test/test_dependency_graph.rb
|
92
98
|
test/test_driver.rb
|
99
|
+
test/test_email.rb
|
93
100
|
test/test_event_handler.rb
|
94
101
|
test/test_god.rb
|
95
102
|
test/test_handlers_kqueue_handler.rb
|
data/Rakefile
CHANGED
data/bin/god
CHANGED
@@ -33,6 +33,7 @@ begin
|
|
33
33
|
load <file> load a config into a running god
|
34
34
|
log <task name> show realtime log for given task
|
35
35
|
status show status of each task
|
36
|
+
signal <task or group name> <sig> signal all matching tasks
|
36
37
|
quit stop god
|
37
38
|
terminate stop god and all tasks
|
38
39
|
check run self diagnostic
|
data/init/god
CHANGED
File without changes
|
data/lib/god.rb
CHANGED
@@ -49,13 +49,23 @@ require 'god/conditions/flapping'
|
|
49
49
|
require 'god/conditions/http_response_code'
|
50
50
|
require 'god/conditions/disk_usage'
|
51
51
|
require 'god/conditions/complex'
|
52
|
+
require 'god/conditions/file_mtime'
|
52
53
|
|
53
54
|
require 'god/contact'
|
54
55
|
require 'god/contacts/email'
|
56
|
+
require 'god/contacts/webhook'
|
57
|
+
begin
|
58
|
+
require 'god/contacts/twitter'
|
59
|
+
rescue LoadError
|
60
|
+
end
|
55
61
|
begin
|
56
62
|
require 'god/contacts/jabber'
|
57
63
|
rescue LoadError
|
58
64
|
end
|
65
|
+
begin
|
66
|
+
require 'god/contacts/campfire'
|
67
|
+
rescue LoadError
|
68
|
+
end
|
59
69
|
|
60
70
|
require 'god/socket'
|
61
71
|
require 'god/driver'
|
@@ -138,7 +148,7 @@ class Module
|
|
138
148
|
end
|
139
149
|
|
140
150
|
module God
|
141
|
-
VERSION = '0.7.
|
151
|
+
VERSION = '0.7.10'
|
142
152
|
|
143
153
|
LOG_BUFFER_SIZE_DEFAULT = 100
|
144
154
|
PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
|
@@ -471,6 +481,19 @@ module God
|
|
471
481
|
info
|
472
482
|
end
|
473
483
|
|
484
|
+
# Send a signal to each task.
|
485
|
+
# +name+ is the String name of the task or group
|
486
|
+
# +signal+ is the signal to send. e.g. HUP, 9
|
487
|
+
#
|
488
|
+
# Returns String[]:task_names
|
489
|
+
def self.signal(name, signal)
|
490
|
+
items = Array(self.watches[name] || self.groups[name]).dup
|
491
|
+
jobs = []
|
492
|
+
items.each { |w| jobs << Thread.new { w.signal(signal) } }
|
493
|
+
jobs.each { |j| j.join }
|
494
|
+
items.map { |x| x.name }
|
495
|
+
end
|
496
|
+
|
474
497
|
# Log lines for the given task since the specified time.
|
475
498
|
# +watch_name+ is the name of the task (may be abbreviated)
|
476
499
|
# +since+ is the Time since which to report log lines
|
data/lib/god/cli/command.rb
CHANGED
@@ -25,7 +25,7 @@ module God
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def dispatch
|
28
|
-
if %w{load status log quit terminate}.include?(@command)
|
28
|
+
if %w{load status signal log quit terminate}.include?(@command)
|
29
29
|
setup
|
30
30
|
send("#{@command}_command")
|
31
31
|
elsif %w{start stop restart monitor unmonitor remove}.include?(@command)
|
@@ -84,6 +84,29 @@ module God
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
def signal_command
|
88
|
+
# get the name of the watch/group
|
89
|
+
name = @args[1]
|
90
|
+
signal = @args[2]
|
91
|
+
|
92
|
+
puts "Sending signal '#{signal}' to '#{name}'"
|
93
|
+
|
94
|
+
t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }
|
95
|
+
|
96
|
+
watches = @server.signal(name, signal)
|
97
|
+
|
98
|
+
# output response
|
99
|
+
t.kill; STDOUT.puts
|
100
|
+
unless watches.empty?
|
101
|
+
puts 'The following watches were affected:'
|
102
|
+
watches.each do |w|
|
103
|
+
puts ' ' + w
|
104
|
+
end
|
105
|
+
else
|
106
|
+
puts 'No matching task or group'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
87
110
|
def log_command
|
88
111
|
begin
|
89
112
|
Signal.trap('INT') { exit }
|
data/lib/god/cli/run.rb
CHANGED
@@ -64,6 +64,7 @@ module God
|
|
64
64
|
|
65
65
|
load_config @options[:config]
|
66
66
|
end
|
67
|
+
setup_logging
|
67
68
|
end
|
68
69
|
|
69
70
|
def run_in_front
|
@@ -74,18 +75,6 @@ module God
|
|
74
75
|
end
|
75
76
|
|
76
77
|
default_run
|
77
|
-
|
78
|
-
log_file = God.log_file
|
79
|
-
log_file = File.expand_path(@options[:log]) if @options[:log]
|
80
|
-
if log_file
|
81
|
-
puts "Sending output to log file: #{log_file}"
|
82
|
-
|
83
|
-
# reset file descriptors
|
84
|
-
STDIN.reopen "/dev/null"
|
85
|
-
STDOUT.reopen(log_file, "a")
|
86
|
-
STDERR.reopen STDOUT
|
87
|
-
STDOUT.sync = true
|
88
|
-
end
|
89
78
|
end
|
90
79
|
|
91
80
|
def run_daemonized
|
@@ -96,14 +85,6 @@ module God
|
|
96
85
|
begin
|
97
86
|
require 'god'
|
98
87
|
|
99
|
-
log_file = @options[:log] || God.log_file || "/dev/null"
|
100
|
-
|
101
|
-
# reset file descriptors
|
102
|
-
STDIN.reopen "/dev/null"
|
103
|
-
STDOUT.reopen(log_file, "a")
|
104
|
-
STDERR.reopen STDOUT
|
105
|
-
STDOUT.sync = true
|
106
|
-
|
107
88
|
# set pid if requested
|
108
89
|
if @options[:pid] # and as deamon
|
109
90
|
God.pid = @options[:pid]
|
@@ -142,6 +123,21 @@ module God
|
|
142
123
|
exit
|
143
124
|
end
|
144
125
|
|
126
|
+
def setup_logging
|
127
|
+
log_file = God.log_file
|
128
|
+
log_file = File.expand_path(@options[:log]) if @options[:log]
|
129
|
+
log_file = "/dev/null" if !log_file && @options[:daemonize]
|
130
|
+
if log_file
|
131
|
+
puts "Sending output to log file: #{log_file}" unless @options[:daemonize]
|
132
|
+
|
133
|
+
# reset file descriptors
|
134
|
+
STDIN.reopen "/dev/null"
|
135
|
+
STDOUT.reopen(log_file, "a")
|
136
|
+
STDERR.reopen STDOUT
|
137
|
+
STDOUT.sync = true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
145
141
|
def load_config(config)
|
146
142
|
if File.directory? config
|
147
143
|
files_loaded = false
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module God
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
class FileMtime < PollCondition
|
5
|
+
attr_accessor :path, :max_age
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
self.path = nil
|
10
|
+
self.max_age = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
valid = true
|
15
|
+
valid &= complain("Attribute 'path' must be specified", self) if self.path.nil?
|
16
|
+
valid &= complain("Attribute 'max_age' must be specified", self) if self.max_age.nil?
|
17
|
+
valid
|
18
|
+
end
|
19
|
+
|
20
|
+
def test
|
21
|
+
(Time.now - File.mtime(self.path)) > self.max_age
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# notify campfire using tinder http://tinder.rubyforge.org
|
2
|
+
#
|
3
|
+
# Example: set up a new campfire notifier
|
4
|
+
#
|
5
|
+
# Credentials
|
6
|
+
#
|
7
|
+
# God::Contacts::Campfire.server_settings = {
|
8
|
+
# :subdomain => "yoursubdomain",
|
9
|
+
# :user_name => "youruser",
|
10
|
+
# :room => "yourroom",
|
11
|
+
# :password => "yourpassword"
|
12
|
+
# }
|
13
|
+
#
|
14
|
+
# Register a new notifier
|
15
|
+
#
|
16
|
+
# God.contact(:campfire) do |c|
|
17
|
+
# c.name = 'campfire'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Define a transition for the process running event
|
21
|
+
#
|
22
|
+
# w.transition(:up, :start) do |on|
|
23
|
+
# on.condition(:process_running) do |c|
|
24
|
+
# c.running = true
|
25
|
+
# c.notify = 'campfire'
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
|
29
|
+
require 'tinder'
|
30
|
+
|
31
|
+
module God
|
32
|
+
module Contacts
|
33
|
+
|
34
|
+
class Campfire < Contact
|
35
|
+
class << self
|
36
|
+
attr_accessor :server_settings, :format
|
37
|
+
end
|
38
|
+
|
39
|
+
self.server_settings = {:subdomain => '',
|
40
|
+
:user_name => '',
|
41
|
+
:password => '',
|
42
|
+
:room => ''}
|
43
|
+
|
44
|
+
self.format = lambda do |message, host|
|
45
|
+
<<-EOF
|
46
|
+
#{host} - #{message}
|
47
|
+
EOF
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@room = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def notify(message, time, priority, category, host)
|
55
|
+
begin
|
56
|
+
body = Campfire.format.call(message,host)
|
57
|
+
|
58
|
+
room.speak body
|
59
|
+
|
60
|
+
self.info = "notified campfire: #{Campfire.server_settings[:subdomain]}"
|
61
|
+
rescue => e
|
62
|
+
applog(nil, :info, "failed to notify campfire: #{e.message}")
|
63
|
+
applog(nil, :debug, e.backtrace.join("\n"))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def room
|
70
|
+
unless @room
|
71
|
+
applog(nil,:debug, "initializing campfire connection using credentials: #{Campfire.server_settings.inspect}")
|
72
|
+
|
73
|
+
campfire = Tinder::Campfire.new Campfire.server_settings[:subdomain]
|
74
|
+
campfire.login Campfire.server_settings[:user_name], Campfire.server_settings[:password]
|
75
|
+
@room = campfire.find_room_by_name(Campfire.server_settings[:room])
|
76
|
+
end
|
77
|
+
@room
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# To add Jabber notifications you must have xmpp4r gem installed.
|
2
|
+
# Configure your watches like this:
|
3
|
+
#
|
4
|
+
# God::Contacts::Jabber.settings = { :jabber_id => 'sender@example.com',
|
5
|
+
# :password => 'secret' }
|
6
|
+
# God.contact(:jabber) do |c|
|
7
|
+
# c.name = 'Tester'
|
8
|
+
# c.jabber_id = 'receiver@example.com'
|
9
|
+
# c.group = 'developers'
|
10
|
+
# end
|
11
|
+
|
12
|
+
module XMPP4R
|
13
|
+
require 'rubygems'
|
14
|
+
require 'xmpp4r'
|
15
|
+
include Jabber
|
16
|
+
end
|
17
|
+
|
18
|
+
module God
|
19
|
+
module Contacts
|
20
|
+
class Jabber < Contact
|
21
|
+
class << self
|
22
|
+
attr_accessor :settings, :format
|
23
|
+
end
|
24
|
+
|
25
|
+
self.format = lambda do |message, priority, category, host|
|
26
|
+
text = "Message: #{message}\n"
|
27
|
+
text += "Host: #{host}\n" if host
|
28
|
+
text += "Priority: #{priority}\n" if priority
|
29
|
+
text += "Category: #{category}\n" if category
|
30
|
+
return text
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :jabber_id
|
34
|
+
|
35
|
+
def valid?
|
36
|
+
valid = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify(message, time, priority, category, host)
|
40
|
+
begin
|
41
|
+
jabber_id = XMPP4R::JID::new "#{Jabber.settings[:jabber_id]}/God"
|
42
|
+
jabber_client = XMPP4R::Client::new jabber_id
|
43
|
+
jabber_client.connect
|
44
|
+
jabber_client.auth Jabber.settings[:password]
|
45
|
+
|
46
|
+
body = Jabber.format.call message, priority, category, host
|
47
|
+
|
48
|
+
message = XMPP4R::Message::new self.jabber_id, body
|
49
|
+
message.set_type :normal
|
50
|
+
message.set_id '1'
|
51
|
+
message.set_subject 'God'
|
52
|
+
jabber_client.send message
|
53
|
+
|
54
|
+
self.info = "sent jabber message to #{self.jabber_id}"
|
55
|
+
rescue => e
|
56
|
+
puts e.message
|
57
|
+
puts e.backtrace.join("\n")
|
58
|
+
|
59
|
+
self.info = "failed to send jabber message to #{self.jabber_id}: #{e.message}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# For Twitter updates you need the 'twitter' gem
|
2
|
+
# (gem install twitter)
|
3
|
+
#
|
4
|
+
# Configure your watches like this:
|
5
|
+
#
|
6
|
+
# God::Contacts::Twitter.settings = { :username => 'sender@example.com',
|
7
|
+
# :password => 'secret' }
|
8
|
+
# God.contact(:twitter) do |c|
|
9
|
+
# c.name = 'Tester'
|
10
|
+
# c.group = 'developers'
|
11
|
+
# end
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'twitter'
|
15
|
+
|
16
|
+
module God
|
17
|
+
module Contacts
|
18
|
+
class Twitter < Contact
|
19
|
+
class << self
|
20
|
+
attr_accessor :settings
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
valid = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def notify(message, time, priority, category, host)
|
28
|
+
begin
|
29
|
+
::Twitter::Base.new(Twitter.settings[:username],
|
30
|
+
Twitter.settings[:password]).update(message)
|
31
|
+
|
32
|
+
self.info = "sent twitter update as #{Twitter.settings[:username]}"
|
33
|
+
rescue => e
|
34
|
+
self.info = "failed to send twitter update from #{self.twitter_id}: #{e.message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Configure your watches like this:
|
2
|
+
#
|
3
|
+
# God.contact(:webhook) do |c|
|
4
|
+
# c.name = 'Tester'
|
5
|
+
# c.hook_url = 'http://hook/url'
|
6
|
+
# end
|
7
|
+
|
8
|
+
require 'net/http'
|
9
|
+
require 'uri'
|
10
|
+
|
11
|
+
module God
|
12
|
+
module Contacts
|
13
|
+
|
14
|
+
class Webhook < Contact
|
15
|
+
|
16
|
+
attr_accessor :hook_url
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
valid = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def notify(message, time, priority, category, host)
|
23
|
+
begin
|
24
|
+
data = {
|
25
|
+
:message => message,
|
26
|
+
:time => time,
|
27
|
+
:priority => priority,
|
28
|
+
:category => category,
|
29
|
+
:host => host
|
30
|
+
}
|
31
|
+
|
32
|
+
uri = URI.parse(self.hook_url)
|
33
|
+
Net::HTTP.post_form(uri, data)
|
34
|
+
|
35
|
+
self.info = "sent webhook to #{self.hook_url}"
|
36
|
+
rescue => e
|
37
|
+
puts e.message
|
38
|
+
puts e.backtrace.join("\n")
|
39
|
+
|
40
|
+
self.info = "failed to send webhook to #{self.hook_url}: #{e.message}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/god/driver.rb
CHANGED
@@ -1,47 +1,171 @@
|
|
1
1
|
module God
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
class TimedEvent
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_accessor :at
|
5
6
|
|
6
|
-
# Instantiate a new
|
7
|
-
# +condition+ is the Condition
|
7
|
+
# Instantiate a new TimedEvent that will be triggered after the specified delay
|
8
8
|
# +delay+ is the number of seconds from now at which to trigger
|
9
9
|
#
|
10
|
-
# Returns
|
11
|
-
def initialize(
|
12
|
-
self.condition = condition
|
10
|
+
# Returns TimedEvent
|
11
|
+
def initialize(delay = 0)
|
13
12
|
self.at = Time.now + delay
|
14
13
|
end
|
15
|
-
|
14
|
+
|
16
15
|
def due?
|
17
16
|
Time.now >= self.at
|
18
17
|
end
|
18
|
+
|
19
|
+
def <=>(other)
|
20
|
+
self.at <=> other.at
|
21
|
+
end
|
22
|
+
end # DriverEvent
|
23
|
+
|
24
|
+
class DriverEvent < TimedEvent
|
25
|
+
attr_accessor :condition, :task
|
26
|
+
|
27
|
+
def initialize(delay, task, condition)
|
28
|
+
super delay
|
29
|
+
self.task = task
|
30
|
+
self.condition = condition
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_event
|
34
|
+
@task.handle_poll(@condition)
|
35
|
+
end
|
19
36
|
end # DriverEvent
|
37
|
+
|
38
|
+
class DriverOperation < TimedEvent
|
39
|
+
attr_accessor :task, :name, :args
|
40
|
+
|
41
|
+
def initialize(task, name, args)
|
42
|
+
super(0)
|
43
|
+
self.task = task
|
44
|
+
self.name = name
|
45
|
+
self.args = args
|
46
|
+
end
|
47
|
+
|
48
|
+
# Handle the next queued operation that was issued asynchronously
|
49
|
+
#
|
50
|
+
# Returns nothing
|
51
|
+
def handle_event
|
52
|
+
@task.send(@name, *@args)
|
53
|
+
end
|
54
|
+
end
|
20
55
|
|
56
|
+
class DriverEventQueue
|
57
|
+
def initialize
|
58
|
+
@shutdown = false
|
59
|
+
@waiting = []
|
60
|
+
@events = []
|
61
|
+
@waiting.taint
|
62
|
+
@events.taint
|
63
|
+
self.taint
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Wake any sleeping threads after setting the sentinel
|
68
|
+
#
|
69
|
+
def shutdown
|
70
|
+
@shutdown = true
|
71
|
+
begin
|
72
|
+
Thread.critical = true
|
73
|
+
@waiting.each do |t|
|
74
|
+
t.run
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
Thread.critical = false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Sleep until the queue has something due
|
83
|
+
#
|
84
|
+
def pop
|
85
|
+
begin
|
86
|
+
while (Thread.critical = true; @events.empty? or !@events.first.due?)
|
87
|
+
@waiting.push Thread.current
|
88
|
+
if @events.empty?
|
89
|
+
raise ThreadError, "queue empty" if @shutdown
|
90
|
+
Thread.stop
|
91
|
+
else
|
92
|
+
Thread.critical = false
|
93
|
+
sleep @events.first.at - Time.now
|
94
|
+
Thread.critical = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@events.shift
|
98
|
+
ensure
|
99
|
+
Thread.critical = false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
alias shift pop
|
104
|
+
alias deq pop
|
105
|
+
|
106
|
+
#
|
107
|
+
# Add an event to the queue, wake any waiters if what we added needs to
|
108
|
+
# happen sooner than the next pending event
|
109
|
+
#
|
110
|
+
def push(event)
|
111
|
+
Thread.critical = true
|
112
|
+
@events << event
|
113
|
+
@events.sort!
|
114
|
+
begin
|
115
|
+
t = @waiting.shift if @events.first == event
|
116
|
+
t.wakeup if t
|
117
|
+
rescue ThreadError
|
118
|
+
retry
|
119
|
+
ensure
|
120
|
+
Thread.critical = false
|
121
|
+
end
|
122
|
+
begin
|
123
|
+
t.run if t
|
124
|
+
rescue ThreadError
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
alias << push
|
129
|
+
alias enq push
|
130
|
+
|
131
|
+
def empty?
|
132
|
+
@que.empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
def clear
|
136
|
+
@events.clear
|
137
|
+
end
|
138
|
+
|
139
|
+
def length
|
140
|
+
@events.length
|
141
|
+
end
|
142
|
+
|
143
|
+
alias size length
|
144
|
+
|
145
|
+
def num_waiting
|
146
|
+
@waiting.size
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
21
151
|
class Driver
|
22
152
|
attr_reader :thread
|
23
153
|
|
24
|
-
INTERVAL = 0.25
|
25
|
-
|
26
154
|
# Instantiate a new Driver and start the scheduler loop to handle events
|
27
155
|
# +task+ is the Task this Driver belongs to
|
28
156
|
#
|
29
157
|
# Returns Driver
|
30
158
|
def initialize(task)
|
31
159
|
@task = task
|
32
|
-
@events =
|
33
|
-
@ops = Queue.new
|
160
|
+
@events = God::DriverEventQueue.new
|
34
161
|
|
35
162
|
@thread = Thread.new do
|
36
163
|
loop do
|
37
164
|
begin
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
sleep INTERVAL
|
44
|
-
end
|
165
|
+
@events.pop.handle_event
|
166
|
+
rescue ThreadError => e
|
167
|
+
# queue is empty
|
168
|
+
break
|
45
169
|
rescue Exception => e
|
46
170
|
message = format("Unhandled exception in driver loop - (%s): %s\n%s",
|
47
171
|
e.class, e.message, e.backtrace.join("\n"))
|
@@ -51,31 +175,8 @@ module God
|
|
51
175
|
end
|
52
176
|
end
|
53
177
|
|
54
|
-
# Handle the next queued operation that was issued asynchronously
|
55
|
-
#
|
56
|
-
# Returns nothing
|
57
|
-
def handle_op
|
58
|
-
command = @ops.pop
|
59
|
-
@task.send(command[0], *command[1])
|
60
|
-
end
|
61
|
-
|
62
|
-
# Handle the next event (poll condition) that is due
|
63
|
-
#
|
64
|
-
# Returns nothing
|
65
|
-
def handle_event
|
66
|
-
if @events.first.due?
|
67
|
-
event = @events.shift
|
68
|
-
@task.handle_poll(event.condition)
|
69
|
-
end
|
70
|
-
|
71
|
-
# don't sleep if there is a pending event and it is due
|
72
|
-
unless @events.first && @events.first.due?
|
73
|
-
sleep INTERVAL
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
178
|
# Clear all events for this Driver
|
78
|
-
#
|
179
|
+
#
|
79
180
|
# Returns nothing
|
80
181
|
def clear_events
|
81
182
|
@events.clear
|
@@ -87,7 +188,7 @@ module God
|
|
87
188
|
#
|
88
189
|
# Returns nothing
|
89
190
|
def message(name, args = [])
|
90
|
-
@
|
191
|
+
@events.push(DriverOperation.new(@task, name, args))
|
91
192
|
end
|
92
193
|
|
93
194
|
# Create and schedule a new DriverEvent
|
@@ -98,11 +199,8 @@ module God
|
|
98
199
|
def schedule(condition, delay = condition.interval)
|
99
200
|
applog(nil, :debug, "driver schedule #{condition} in #{delay} seconds")
|
100
201
|
|
101
|
-
@events.
|
102
|
-
|
103
|
-
# sort events
|
104
|
-
@events.sort! { |x, y| x.at <=> y.at }
|
202
|
+
@events.push(DriverEvent.new(delay, @task, condition))
|
105
203
|
end
|
106
204
|
end # Driver
|
107
205
|
|
108
|
-
end # God
|
206
|
+
end # God
|
data/lib/god/process.rb
CHANGED
@@ -2,7 +2,7 @@ module God
|
|
2
2
|
class Process
|
3
3
|
WRITES_PID = [:start, :restart]
|
4
4
|
|
5
|
-
attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart, :unix_socket, :chroot, :env
|
5
|
+
attr_accessor :name, :uid, :gid, :log, :log_cmd, :start, :stop, :restart, :unix_socket, :chroot, :env
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
self.log = '/dev/null'
|
@@ -12,6 +12,7 @@ module God
|
|
12
12
|
@user_log = false
|
13
13
|
@pid = nil
|
14
14
|
@unix_socket = nil
|
15
|
+
@log_cmd = nil
|
15
16
|
end
|
16
17
|
|
17
18
|
def alive?
|
@@ -32,7 +33,7 @@ module God
|
|
32
33
|
::Process::Sys.setgid(gid_num) if self.gid
|
33
34
|
::Process::Sys.setuid(uid_num) if self.uid
|
34
35
|
|
35
|
-
File.writable?(file) ? exit(0) : exit(1)
|
36
|
+
File.writable?(file_in_chroot(file)) ? exit(0) : exit(1)
|
36
37
|
end
|
37
38
|
|
38
39
|
wpid, status = ::Process.waitpid2(pid)
|
@@ -158,6 +159,15 @@ module God
|
|
158
159
|
end
|
159
160
|
end
|
160
161
|
|
162
|
+
# Send the given signal to this process.
|
163
|
+
#
|
164
|
+
# Returns nothing
|
165
|
+
def signal(sig)
|
166
|
+
sig = sig.to_i if sig.to_i != 0
|
167
|
+
applog(self, :info, "#{self.name} sending signal '#{sig}' to pid #{self.pid}")
|
168
|
+
::Process.kill(sig, self.pid) rescue nil
|
169
|
+
end
|
170
|
+
|
161
171
|
def start!
|
162
172
|
call_action(:start)
|
163
173
|
end
|
@@ -274,7 +284,11 @@ module God
|
|
274
284
|
Dir.chdir "/"
|
275
285
|
$0 = command
|
276
286
|
STDIN.reopen "/dev/null"
|
277
|
-
|
287
|
+
if self.log_cmd
|
288
|
+
STDOUT.reopen IO.popen(self.log_cmd, "a")
|
289
|
+
else
|
290
|
+
STDOUT.reopen file_in_chroot(self.log), "a"
|
291
|
+
end
|
278
292
|
STDERR.reopen STDOUT
|
279
293
|
|
280
294
|
# close any other file descriptors
|
@@ -15,7 +15,7 @@ module God
|
|
15
15
|
# Returns true if +RequiredPaths+ are readable.
|
16
16
|
def self.usable?
|
17
17
|
RequiredPaths.all? do |path|
|
18
|
-
test(?r, path)
|
18
|
+
test(?r, path) && readable?(path)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -57,6 +57,16 @@ module God
|
|
57
57
|
|
58
58
|
private
|
59
59
|
|
60
|
+
# Some systems (CentOS?) have a /proc, but they can hang when trying to
|
61
|
+
# read from them. Try to use this sparingly as it is expensive.
|
62
|
+
def self.readable?(path)
|
63
|
+
begin
|
64
|
+
timeout(1) { File.read(path) }
|
65
|
+
rescue Timeout::Error
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
60
70
|
# in seconds
|
61
71
|
def uptime
|
62
72
|
File.read(UptimePath).split[0].to_f
|
data/lib/god/task.rb
CHANGED
data/lib/god/watch.rb
CHANGED
@@ -13,8 +13,8 @@ module God
|
|
13
13
|
extend Forwardable
|
14
14
|
def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart,
|
15
15
|
:name=, :uid=, :gid=, :start=, :stop=, :restart=,
|
16
|
-
:pid_file, :pid_file=, :log, :log=, :alive?, :pid,
|
17
|
-
:unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env
|
16
|
+
:pid_file, :pid_file=, :log, :log=, :log_cmd, :log_cmd=, :alive?, :pid,
|
17
|
+
:unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env=, :signal
|
18
18
|
#
|
19
19
|
def initialize
|
20
20
|
super
|
File without changes
|
File without changes
|
File without changes
|
@@ -6,6 +6,13 @@ God::Contacts::Email.server_settings = {
|
|
6
6
|
:domain => "powerset.com"
|
7
7
|
}
|
8
8
|
|
9
|
+
God::Contacts::Twitter.settings = {
|
10
|
+
# this is for my 'mojombo2' twitter test account
|
11
|
+
# feel free to use it for testing your conditions
|
12
|
+
:username => 'mojombo@gmail.com',
|
13
|
+
:password => 'gok9we3ot1av2e'
|
14
|
+
}
|
15
|
+
|
9
16
|
God.contact(:email) do |c|
|
10
17
|
c.name = 'tom'
|
11
18
|
c.email = 'tom@mojombo.com'
|
@@ -24,12 +31,15 @@ God.contact(:email) do |c|
|
|
24
31
|
c.group = 'platform'
|
25
32
|
end
|
26
33
|
|
34
|
+
God.contact(:twitter) do |c|
|
35
|
+
c.name = 'tom2'
|
36
|
+
c.group = 'developers'
|
37
|
+
end
|
38
|
+
|
27
39
|
God.watch do |w|
|
28
40
|
w.name = "contact"
|
29
41
|
w.interval = 5.seconds
|
30
42
|
w.start = "ruby " + File.join(File.dirname(__FILE__), *%w[simple_server.rb])
|
31
|
-
w.uid = 'tom'
|
32
|
-
w.gid = 'tom'
|
33
43
|
w.log = "/Users/tom/contact.log"
|
34
44
|
|
35
45
|
# determine the state on startup
|
@@ -55,7 +65,7 @@ God.watch do |w|
|
|
55
65
|
# start if process is not running
|
56
66
|
w.transition(:up, :start) do |on|
|
57
67
|
on.condition(:process_exits) do |c|
|
58
|
-
c.notify = {:contacts => ['
|
68
|
+
c.notify = {:contacts => ['tom2', 'foobar'], :priority => 1, :category => 'product'}
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
File without changes
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require 'tinder'
|
3
|
+
|
4
|
+
class TestCampfire < Test::Unit::TestCase
|
5
|
+
def test_exists
|
6
|
+
God::Contacts::Campfire
|
7
|
+
end
|
8
|
+
|
9
|
+
# should notify
|
10
|
+
def test_campfire_delivery_method_for_notify
|
11
|
+
assert_nothing_raised do
|
12
|
+
|
13
|
+
room = mock()
|
14
|
+
room.expects(:speak).returns(nil)
|
15
|
+
|
16
|
+
g = God::Contacts::Campfire.new
|
17
|
+
God::Contacts::Campfire.format.expects(:call).with(:a,:e)
|
18
|
+
g.expects(:room).returns(room)
|
19
|
+
g.notify(:a, :b, :c, :d, :e)
|
20
|
+
assert_equal "notified campfire: ", g.info
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# should not establish a new connection because the older is alive
|
25
|
+
def test_campfire_room_method
|
26
|
+
assert_nothing_raised do
|
27
|
+
room = mock()
|
28
|
+
g = God::Contacts::Campfire.new
|
29
|
+
g.instance_variable_set(:@room,room)
|
30
|
+
assert_equal g.send(:room), room
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# should raise because the connections parameters have not been set
|
35
|
+
def test_campfire_delivery_method_for_notify_without_campfire_params
|
36
|
+
LOG.expects(:log).times(3) # 3 calls: 2 debug (credentials, backtrace) + 1 info (failed message)
|
37
|
+
g = God::Contacts::Campfire.new
|
38
|
+
g.notify(:a, :b, :c, :d, :e)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -6,7 +6,7 @@ class TestConditionsProcessRunning < Test::Unit::TestCase
|
|
6
6
|
c = Conditions::ProcessRunning.new
|
7
7
|
c.running = r
|
8
8
|
|
9
|
-
c.stubs(:watch).returns(stub(:pid =>
|
9
|
+
c.stubs(:watch).returns(stub(:pid => 99999999, :name => 'foo'))
|
10
10
|
|
11
11
|
# no_stdout do
|
12
12
|
assert_equal !r, c.test
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class TestWebhook < Test::Unit::TestCase
|
4
|
+
def test_exists
|
5
|
+
God::Contacts::Webhook
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_notify
|
9
|
+
assert_nothing_raised do
|
10
|
+
g = God::Contacts::Webhook.new
|
11
|
+
g.hook_url = 'http://test/switch'
|
12
|
+
Net::HTTP.expects(:post_form)
|
13
|
+
g.notify(:a, :b, :c, :d, :e)
|
14
|
+
assert_equal "sent webhook to http://test/switch", g.info
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,34 +1,37 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: god
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.7.
|
7
|
-
date: 2008-07-09 00:00:00 -07:00
|
8
|
-
summary: Like monit, only awesome
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
- ext
|
12
|
-
email: tom@rubyisawesome.com
|
13
|
-
homepage: http://god.rubyforge.org/
|
14
|
-
rubyforge_project: god
|
15
|
-
description: God is an easy to configure, easy to extend monitoring framework written in Ruby.
|
16
|
-
autorequire:
|
17
|
-
default_executable:
|
18
|
-
bindir: bin
|
19
|
-
has_rdoc: true
|
20
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
-
requirements:
|
22
|
-
- - ">"
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version: 0.0.0
|
25
|
-
version:
|
4
|
+
version: 0.7.10
|
26
5
|
platform: ruby
|
27
|
-
signing_key:
|
28
|
-
cert_chain:
|
29
|
-
post_install_message:
|
30
6
|
authors:
|
31
7
|
- Tom Preston-Werner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-13 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.0
|
24
|
+
version:
|
25
|
+
description: God is an easy to configure, easy to extend monitoring framework written in Ruby.
|
26
|
+
email: tom@rubyisawesome.com
|
27
|
+
executables:
|
28
|
+
- god
|
29
|
+
extensions:
|
30
|
+
- ext/god/extconf.rb
|
31
|
+
extra_rdoc_files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
32
35
|
files:
|
33
36
|
- History.txt
|
34
37
|
- Manifest.txt
|
@@ -56,6 +59,7 @@ files:
|
|
56
59
|
- lib/god/conditions/cpu_usage.rb
|
57
60
|
- lib/god/conditions/degrading_lambda.rb
|
58
61
|
- lib/god/conditions/disk_usage.rb
|
62
|
+
- lib/god/conditions/file_mtime.rb
|
59
63
|
- lib/god/conditions/flapping.rb
|
60
64
|
- lib/god/conditions/http_response_code.rb
|
61
65
|
- lib/god/conditions/lambda.rb
|
@@ -65,7 +69,11 @@ files:
|
|
65
69
|
- lib/god/conditions/tries.rb
|
66
70
|
- lib/god/configurable.rb
|
67
71
|
- lib/god/contact.rb
|
72
|
+
- lib/god/contacts/campfire.rb
|
68
73
|
- lib/god/contacts/email.rb
|
74
|
+
- lib/god/contacts/jabber.rb
|
75
|
+
- lib/god/contacts/twitter.rb
|
76
|
+
- lib/god/contacts/webhook.rb
|
69
77
|
- lib/god/dependency_graph.rb
|
70
78
|
- lib/god/diagnostics.rb
|
71
79
|
- lib/god/driver.rb
|
@@ -114,6 +122,7 @@ files:
|
|
114
122
|
- test/helper.rb
|
115
123
|
- test/suite.rb
|
116
124
|
- test/test_behavior.rb
|
125
|
+
- test/test_campfire.rb
|
117
126
|
- test/test_condition.rb
|
118
127
|
- test/test_conditions_disk_usage.rb
|
119
128
|
- test/test_conditions_http_response_code.rb
|
@@ -122,6 +131,7 @@ files:
|
|
122
131
|
- test/test_contact.rb
|
123
132
|
- test/test_dependency_graph.rb
|
124
133
|
- test/test_driver.rb
|
134
|
+
- test/test_email.rb
|
125
135
|
- test/test_event_handler.rb
|
126
136
|
- test/test_god.rb
|
127
137
|
- test/test_handlers_kqueue_handler.rb
|
@@ -137,8 +147,37 @@ files:
|
|
137
147
|
- test/test_timeline.rb
|
138
148
|
- test/test_trigger.rb
|
139
149
|
- test/test_watch.rb
|
150
|
+
has_rdoc: true
|
151
|
+
homepage: http://god.rubyforge.org/
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options:
|
154
|
+
- --main
|
155
|
+
- README.txt
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
- ext
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: "0"
|
164
|
+
version:
|
165
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: "0"
|
170
|
+
version:
|
171
|
+
requirements: []
|
172
|
+
|
173
|
+
rubyforge_project: god
|
174
|
+
rubygems_version: 1.3.0
|
175
|
+
signing_key:
|
176
|
+
specification_version: 2
|
177
|
+
summary: Like monit, only awesome
|
140
178
|
test_files:
|
141
179
|
- test/test_behavior.rb
|
180
|
+
- test/test_campfire.rb
|
142
181
|
- test/test_condition.rb
|
143
182
|
- test/test_conditions_disk_usage.rb
|
144
183
|
- test/test_conditions_http_response_code.rb
|
@@ -163,26 +202,4 @@ test_files:
|
|
163
202
|
- test/test_timeline.rb
|
164
203
|
- test/test_trigger.rb
|
165
204
|
- test/test_watch.rb
|
166
|
-
|
167
|
-
- --main
|
168
|
-
- README.txt
|
169
|
-
extra_rdoc_files:
|
170
|
-
- History.txt
|
171
|
-
- Manifest.txt
|
172
|
-
- README.txt
|
173
|
-
executables:
|
174
|
-
- god
|
175
|
-
extensions:
|
176
|
-
- ext/god/extconf.rb
|
177
|
-
requirements: []
|
178
|
-
|
179
|
-
dependencies:
|
180
|
-
- !ruby/object:Gem::Dependency
|
181
|
-
name: hoe
|
182
|
-
version_requirement:
|
183
|
-
version_requirements: !ruby/object:Gem::Version::Requirement
|
184
|
-
requirements:
|
185
|
-
- - ">="
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: 1.4.0
|
188
|
-
version:
|
205
|
+
- test/test_webhook.rb
|