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.
@@ -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]
@@ -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
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'hoe'
3
3
 
4
- Hoe.new('god', '0.7.8') do |p|
4
+ Hoe.new('god', '0.7.10') do |p|
5
5
  p.rubyforge_name = 'god'
6
6
  p.author = 'Tom Preston-Werner'
7
7
  p.email = 'tom@rubyisawesome.com'
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.8'
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
@@ -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 }
@@ -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
@@ -1,47 +1,171 @@
1
1
  module God
2
-
3
- class DriverEvent
4
- attr_accessor :condition, :at
2
+ class TimedEvent
3
+ include Comparable
4
+
5
+ attr_accessor :at
5
6
 
6
- # Instantiate a new TimerEvent that will be triggered after the specified delay
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 TimerEvent
11
- def initialize(condition, delay)
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
- if !@ops.empty?
39
- self.handle_op
40
- elsif !@events.empty?
41
- self.handle_event
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
- @ops.push([name, args])
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.concat([DriverEvent.new(condition, delay)])
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
@@ -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
- STDOUT.reopen file_in_chroot(self.log), "a"
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
@@ -204,6 +204,10 @@ module God
204
204
  self.driver.message(:handle_event, [condition])
205
205
  end
206
206
 
207
+ def signal(sig)
208
+ # noop
209
+ end
210
+
207
211
  ###########################################################################
208
212
  #
209
213
  # Actions
@@ -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
@@ -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 => ['tom', 'foobar'], :priority => 1, :category => 'product'}
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 => 123, :name => 'foo'))
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.8
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
- rdoc_options:
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