mojombo-god 0.7.7 → 0.7.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +20 -1
- data/Manifest.txt +7 -0
- data/Rakefile +1 -1
- data/lib/god.rb +11 -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 +10 -3
- data/lib/god/system/slash_proc_poller.rb +11 -1
- data/lib/god/watch.rb +1 -1
- data/test/configs/contact/contact.god +13 -3
- data/test/test_campfire.rb +41 -0
- data/test/test_conditions_process_running.rb +1 -1
- data/test/test_email.rb +45 -0
- metadata +8 -1
data/History.txt
CHANGED
@@ -1,4 +1,23 @@
|
|
1
|
-
==
|
1
|
+
== 0.7.10 /
|
2
|
+
* Bug Fixes
|
3
|
+
* setup logging *after* loading a given config file when daemonized.
|
4
|
+
enables logging to the 'God.log_file' specified in a config file. [github.com/jnewland]
|
5
|
+
* New Conditions
|
6
|
+
* FileMtime < PollCondition - trigger on file mtime durations [github.com/jwilkins]
|
7
|
+
* New Contacts
|
8
|
+
* Twitter - allow messages to twitter [github.com/jwilkins]
|
9
|
+
* Campfire - send messages to 37signals' Campfire [github.com/hellvinz]
|
10
|
+
* Minor Enhancements
|
11
|
+
* Add watch log_cmd that can be reopened with STDOUT instead of a log file [github.com/jberkel]
|
12
|
+
* Added webhook output support [Martyn Loughran]
|
13
|
+
|
14
|
+
== 0.7.9 / 2008-08-06
|
15
|
+
* Major Enhancements
|
16
|
+
* Use a psuedo-priority queue for more efficient driver loop [Darrell Kresge]
|
17
|
+
* Bug Fixes
|
18
|
+
* Fix file_writable? when using chroot [github.com/eric]
|
19
|
+
|
20
|
+
== 0.7.8 / 2008-07-09
|
2
21
|
* Bug Fixes
|
3
22
|
* Catch all Exceptions from HttpResponseCode condition [github.com/rliebling]
|
4
23
|
* Don't error out if the process went away in SlashProcPoller [Kevin Clark]
|
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/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.9'
|
142
152
|
|
143
153
|
LOG_BUFFER_SIZE_DEFAULT = 100
|
144
154
|
PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
|
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?
|
@@ -28,10 +29,11 @@ module God
|
|
28
29
|
gid_num = Etc.getgrnam(self.gid).gid if self.gid
|
29
30
|
|
30
31
|
::Dir.chroot(self.chroot) if self.chroot
|
32
|
+
::Process.groups = [gid_num] if self.gid
|
31
33
|
::Process::Sys.setgid(gid_num) if self.gid
|
32
34
|
::Process::Sys.setuid(uid_num) if self.uid
|
33
35
|
|
34
|
-
File.writable?(file) ? exit(0) : exit(1)
|
36
|
+
File.writable?(file_in_chroot(file)) ? exit(0) : exit(1)
|
35
37
|
end
|
36
38
|
|
37
39
|
wpid, status = ::Process.waitpid2(pid)
|
@@ -267,12 +269,17 @@ module God
|
|
267
269
|
|
268
270
|
::Dir.chroot(self.chroot) if self.chroot
|
269
271
|
::Process.setsid
|
272
|
+
::Process.groups = [gid_num] if self.gid
|
270
273
|
::Process::Sys.setgid(gid_num) if self.gid
|
271
274
|
::Process::Sys.setuid(uid_num) if self.uid
|
272
275
|
Dir.chdir "/"
|
273
276
|
$0 = command
|
274
277
|
STDIN.reopen "/dev/null"
|
275
|
-
|
278
|
+
if self.log_cmd
|
279
|
+
STDOUT.reopen IO.popen(self.log_cmd, "a")
|
280
|
+
else
|
281
|
+
STDOUT.reopen file_in_chroot(self.log), "a"
|
282
|
+
end
|
276
283
|
STDERR.reopen STDOUT
|
277
284
|
|
278
285
|
# 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/watch.rb
CHANGED
@@ -13,7 +13,7 @@ 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,
|
16
|
+
:pid_file, :pid_file=, :log, :log=, :log_cmd, :log_cmd=, :alive?, :pid,
|
17
17
|
:unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env=
|
18
18
|
#
|
19
19
|
def initialize
|
@@ -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
|
|
@@ -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
|
data/test/test_email.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class TestEmail < Test::Unit::TestCase
|
4
|
+
def test_exists
|
5
|
+
God::Contacts::Email
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_unknown_delivery_method_for_notify
|
9
|
+
assert_nothing_raised do
|
10
|
+
God::Contacts::Email.any_instance.expects(:notify_smtp).never
|
11
|
+
God::Contacts::Email.any_instance.expects(:notify_sendmail).never
|
12
|
+
God::Contacts::Email.delivery_method = :foo_protocol
|
13
|
+
LOG.expects(:log).times(2)
|
14
|
+
|
15
|
+
g = God::Contacts::Email.new
|
16
|
+
g.notify(:a, :b, :c, :d, :e)
|
17
|
+
assert_nil g.info
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_smtp_delivery_method_for_notify
|
22
|
+
assert_nothing_raised do
|
23
|
+
God::Contacts::Email.any_instance.expects(:notify_sendmail).never
|
24
|
+
God::Contacts::Email.any_instance.expects(:notify_smtp).once.returns(nil)
|
25
|
+
God::Contacts::Email.delivery_method = :smtp
|
26
|
+
g = God::Contacts::Email.new
|
27
|
+
g.email = 'joe@example.com'
|
28
|
+
g.notify(:a, :b, :c, :d, :e)
|
29
|
+
assert_equal "sent email to joe@example.com", g.info
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_sendmail_delivery_method_for_notify
|
34
|
+
assert_nothing_raised do
|
35
|
+
God::Contacts::Email.any_instance.expects(:notify_smtp).never
|
36
|
+
God::Contacts::Email.any_instance.expects(:notify_sendmail).once.returns(nil)
|
37
|
+
God::Contacts::Email.delivery_method = :sendmail
|
38
|
+
g = God::Contacts::Email.new
|
39
|
+
g.email = 'joe@example.com'
|
40
|
+
g.notify(:a, :b, :c, :d, :e)
|
41
|
+
assert_equal "sent email to joe@example.com", g.info
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mojombo-god
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Preston-Werner
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- lib/god/conditions/cpu_usage.rb
|
51
51
|
- lib/god/conditions/degrading_lambda.rb
|
52
52
|
- lib/god/conditions/disk_usage.rb
|
53
|
+
- lib/god/conditions/file_mtime.rb
|
53
54
|
- lib/god/conditions/flapping.rb
|
54
55
|
- lib/god/conditions/http_response_code.rb
|
55
56
|
- lib/god/conditions/lambda.rb
|
@@ -59,7 +60,11 @@ files:
|
|
59
60
|
- lib/god/conditions/tries.rb
|
60
61
|
- lib/god/configurable.rb
|
61
62
|
- lib/god/contact.rb
|
63
|
+
- lib/god/contacts/campfire.rb
|
62
64
|
- lib/god/contacts/email.rb
|
65
|
+
- lib/god/contacts/jabber.rb
|
66
|
+
- lib/god/contacts/twitter.rb
|
67
|
+
- lib/god/contacts/webhook.rb
|
63
68
|
- lib/god/dependency_graph.rb
|
64
69
|
- lib/god/diagnostics.rb
|
65
70
|
- lib/god/driver.rb
|
@@ -108,6 +113,7 @@ files:
|
|
108
113
|
- test/helper.rb
|
109
114
|
- test/suite.rb
|
110
115
|
- test/test_behavior.rb
|
116
|
+
- test/test_campfire.rb
|
111
117
|
- test/test_condition.rb
|
112
118
|
- test/test_conditions_disk_usage.rb
|
113
119
|
- test/test_conditions_http_response_code.rb
|
@@ -116,6 +122,7 @@ files:
|
|
116
122
|
- test/test_contact.rb
|
117
123
|
- test/test_dependency_graph.rb
|
118
124
|
- test/test_driver.rb
|
125
|
+
- test/test_email.rb
|
119
126
|
- test/test_event_handler.rb
|
120
127
|
- test/test_god.rb
|
121
128
|
- test/test_handlers_kqueue_handler.rb
|