god 0.7.8 → 0.7.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|