god 0.7.8 → 0.7.10

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