god 0.8.0 → 0.9.0

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.
Files changed (50) hide show
  1. data/History.txt +14 -0
  2. data/Rakefile +42 -26
  3. data/ext/god/kqueue_handler.c +6 -4
  4. data/ext/god/netlink_handler.c +6 -6
  5. data/god.gemspec +127 -151
  6. data/lib/god.rb +26 -5
  7. data/lib/god/cli/command.rb +1 -1
  8. data/lib/god/cli/run.rb +5 -5
  9. data/lib/god/compat19.rb +36 -0
  10. data/lib/god/contacts/prowl.rb +77 -0
  11. data/lib/god/contacts/scout.rb +64 -0
  12. data/lib/god/driver.rb +5 -0
  13. data/lib/god/logger.rb +8 -29
  14. data/lib/god/process.rb +8 -2
  15. data/lib/god/simple_logger.rb +7 -1
  16. data/lib/god/socket.rb +13 -2
  17. data/lib/god/sys_logger.rb +45 -0
  18. data/lib/god/watch.rb +2 -1
  19. data/test/configs/contact/contact.god +7 -1
  20. data/test/configs/task/logs/.placeholder +0 -0
  21. data/test/helper.rb +2 -1
  22. data/test/test_behavior.rb +1 -1
  23. data/test/test_logger.rb +1 -1
  24. data/test/test_prowl.rb +15 -0
  25. metadata +6 -27
  26. data/.gitignore +0 -8
  27. data/VERSION.yml +0 -5
  28. data/ideas/execve/execve.c +0 -29
  29. data/ideas/execve/extconf.rb +0 -11
  30. data/ideas/execve/go.rb +0 -8
  31. data/ideas/future.god +0 -82
  32. data/init/god +0 -42
  33. data/init/lsb_compliant_god +0 -109
  34. data/site/images/banner.jpg +0 -0
  35. data/site/images/bg.gif +0 -0
  36. data/site/images/bg_grey.gif +0 -0
  37. data/site/images/bullet.jpg +0 -0
  38. data/site/images/corner_green.gif +0 -0
  39. data/site/images/corner_green.psd +0 -0
  40. data/site/images/corner_pink.gif +0 -0
  41. data/site/images/god_logo.png +0 -0
  42. data/site/images/header_bg.gif +0 -0
  43. data/site/images/header_bg.jpg +0 -0
  44. data/site/images/red_dot.gif +0 -0
  45. data/site/images/top_bg.gif +0 -0
  46. data/site/index.html +0 -563
  47. data/site/install.html +0 -2
  48. data/site/javascripts/code_highlighter.js +0 -188
  49. data/site/javascripts/ruby.js +0 -18
  50. data/site/stylesheets/layout.css +0 -174
data/lib/god.rb CHANGED
@@ -63,6 +63,11 @@ begin
63
63
  require 'god/contacts/campfire'
64
64
  rescue LoadError
65
65
  end
66
+ begin
67
+ require 'god/contacts/prowl'
68
+ rescue LoadError
69
+ end
70
+
66
71
 
67
72
  require 'god/socket'
68
73
  require 'god/driver'
@@ -145,11 +150,13 @@ class Module
145
150
  end
146
151
 
147
152
  module God
153
+ VERSION = '0.9.0'
148
154
  LOG_BUFFER_SIZE_DEFAULT = 100
149
155
  PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
150
156
  DRB_PORT_DEFAULT = 17165
151
157
  DRB_ALLOW_DEFAULT = ['127.0.0.1']
152
158
  LOG_LEVEL_DEFAULT = :info
159
+ TERMINATE_TIMEOUT_DEFAULT = 10
153
160
 
154
161
  class << self
155
162
  # user configurable
@@ -161,7 +168,11 @@ module God
161
168
  :pid_file_directory,
162
169
  :log_file,
163
170
  :log_level,
164
- :use_events
171
+ :use_events,
172
+ :terminate_timeout,
173
+ :socket_user,
174
+ :socket_group,
175
+ :socket_perms
165
176
 
166
177
  # internal
167
178
  attr_accessor :inited,
@@ -184,6 +195,10 @@ module God
184
195
  self.log_buffer_size = nil
185
196
  self.pid_file_directory = nil
186
197
  self.log_level = nil
198
+ self.terminate_timeout = nil
199
+ self.socket_user = nil
200
+ self.socket_group = nil
201
+ self.socket_perms = 0755
187
202
 
188
203
  # Initialize internal data.
189
204
  #
@@ -205,6 +220,7 @@ module God
205
220
  self.port ||= DRB_PORT_DEFAULT
206
221
  self.allow ||= DRB_ALLOW_DEFAULT
207
222
  self.log_level ||= LOG_LEVEL_DEFAULT
223
+ self.terminate_timeout ||= TERMINATE_TIMEOUT_DEFAULT
208
224
 
209
225
  # additional setup
210
226
  self.setup
@@ -441,7 +457,7 @@ module God
441
457
  end
442
458
  end
443
459
 
444
- 10.times do
460
+ terminate_timeout.times do
445
461
  return true unless self.watches.map { |name, w| w.alive? }.any?
446
462
  sleep 1
447
463
  end
@@ -592,6 +608,12 @@ module God
592
608
  end
593
609
  end
594
610
 
611
+ if God::Logger.syslog
612
+ LOG.info("Syslog enabled.")
613
+ else
614
+ LOG.info("Syslog disabled.")
615
+ end
616
+
595
617
  applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
596
618
  end
597
619
 
@@ -602,7 +624,7 @@ module God
602
624
  self.internal_init
603
625
 
604
626
  # instantiate server
605
- self.server = Socket.new(self.port)
627
+ self.server = Socket.new(self.port, self.socket_user, self.socket_group, self.socket_perms)
606
628
 
607
629
  # start monitoring any watches set to autostart
608
630
  self.watches.values.each { |w| w.monitor if w.autostart? }
@@ -625,8 +647,7 @@ module God
625
647
  end
626
648
 
627
649
  def self.version
628
- yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
629
- "#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
650
+ God::VERSION
630
651
  end
631
652
 
632
653
  # To be called on program exit to start god
@@ -172,7 +172,7 @@ module God
172
172
  puts 'Stopped all watches'
173
173
  else
174
174
  t.kill; STDOUT.puts
175
- puts 'Could not stop all watches within 10 seconds'
175
+ puts "Could not stop all watches within #{@server.terminate_timeout} seconds"
176
176
  end
177
177
 
178
178
  begin
@@ -12,6 +12,10 @@ module God
12
12
  # have at_exit start god
13
13
  $run = true
14
14
 
15
+ if @options[:syslog]
16
+ require 'god/sys_logger'
17
+ end
18
+
15
19
  # run
16
20
  if @options[:daemonize]
17
21
  run_daemonized
@@ -36,7 +40,7 @@ module God
36
40
  def default_run
37
41
  # make sure we have STDIN/STDOUT redirected immediately
38
42
  setup_logging
39
-
43
+
40
44
  # start attached pid watcher if necessary
41
45
  if @options[:attach]
42
46
  self.attach
@@ -93,10 +97,6 @@ module God
93
97
  God.pid = @options[:pid]
94
98
  end
95
99
 
96
- unless @options[:syslog]
97
- Logger.syslog = false
98
- end
99
-
100
100
  default_run
101
101
 
102
102
  unless God::EventHandler.loaded?
@@ -0,0 +1,36 @@
1
+
2
+ require 'monitor'
3
+
4
+
5
+ # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/monitor.rb
6
+
7
+ module MonitorMixin
8
+ class ConditionVariable
9
+ def wait(timeout = nil)
10
+ @monitor.__send__(:mon_check_owner)
11
+ count = @monitor.__send__(:mon_exit_for_cond)
12
+ begin
13
+ @cond.wait(@monitor.instance_variable_get("@mon_mutex"), timeout)
14
+ return true
15
+ ensure
16
+ @monitor.__send__(:mon_enter_for_cond, count)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+
23
+ # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/thread.rb
24
+
25
+ class ConditionVariable
26
+ def wait(mutex, timeout=nil)
27
+ begin
28
+ # TODO: mutex should not be used
29
+ @waiters_mutex.synchronize do
30
+ @waiters.push(Thread.current)
31
+ end
32
+ mutex.sleep timeout
33
+ end
34
+ self
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ # For Prowl notifications you need the 'prowly' gem
2
+ # (gem install prowly)
3
+ #
4
+ # Configure your watches like this:
5
+ #
6
+ # God.contact(:prowl) do |c|
7
+ # c.name = 'georgette'
8
+ # c.apikey = 'ffffffffffffffffffffffffffffffffffffffff'
9
+ # c.group = 'developers'
10
+ # end
11
+ #
12
+ #
13
+ # God.contact(:prowl) do |c|
14
+ # c.name = 'johnny'
15
+ # c.apikey = 'ffffffffffffffffffffffffffffffffffffffff'
16
+ # c.group = 'developers'
17
+ # end
18
+ #
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 = 'developers'
26
+ # end
27
+ # end
28
+
29
+ require 'prowly'
30
+
31
+ module God
32
+ module Contacts
33
+ class Prowl < Contact
34
+
35
+ attr_accessor :apikey
36
+
37
+ def valid?
38
+ valid = true
39
+ valid &= complain("Attribute 'apikey' must be specified", self) if self.apikey.nil?
40
+ valid
41
+ end
42
+
43
+ def notify(message, time, priority, category, host)
44
+ begin
45
+ result = Prowly.notify do |n|
46
+ n.apikey = self.apikey
47
+ n.priority = map_priority(priority.to_i)
48
+ n.application = category || "God"
49
+ n.event = "on " + host.to_s
50
+ n.description = message.to_s + " at " + time.to_s
51
+ end
52
+
53
+ if result.succeeded?
54
+ self.info = "sent prowl notification to #{self.name}"
55
+ else
56
+ self.info = "failed to send prowl notification to #{self.name}: #{result.message}"
57
+ end
58
+ end
59
+ rescue Object => e
60
+ self.info = "failed to send prowl notification to #{self.name}: #{e.message}"
61
+ end
62
+
63
+ private
64
+
65
+ def map_priority(priority)
66
+ case priority
67
+ when 1 then Prowly::Notification::Priority::EMERGENCY
68
+ when 2 then Prowly::Notification::Priority::HIGH
69
+ when 3 then Prowly::Notification::Priority::NORMAL
70
+ when 4 then Prowly::Notification::Priority::MODERATE
71
+ when 5 then Prowly::Notification::Priority::VERY_LOW
72
+ else Prowly::Notification::Priority::NORMAL
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,64 @@
1
+ # Configure your Scout client key:
2
+ #
3
+ # God::Contacts::Scout.client_key = '1qpw29ie38ur37yt5
4
+ #
5
+ # A client key is configured per god process. Inside this God process,
6
+ # you can create multiple Scout 'contacts' - which are actually Scout
7
+ # plugins. This allows you to use Scout's UI to configure who gets
8
+ # notifications for each plugin, and to disable notifications when you
9
+ # go on vacation, etc.
10
+ #
11
+ # God.contact(:scout) do |c|
12
+ # c.name = 'scout_delayed_job_plugin'
13
+ # c.plugin_id = '12345
14
+ # end
15
+ #
16
+ # God.contact(:scout) do |c|
17
+ # c.name = 'scout_apache_plugin'
18
+ # c.plugin_id = '54312
19
+ # end
20
+
21
+ require 'net/http'
22
+ require 'uri'
23
+
24
+ module God
25
+ module Contacts
26
+ class Scout < Contact
27
+ class << self
28
+ attr_accessor :client_key, :format
29
+ end
30
+ attr_accessor :plugin_id
31
+
32
+ self.format = lambda do |message, priority, category, host|
33
+ text = "Message: #{message}\n"
34
+ text += "Host: #{host}\n" if host
35
+ text += "Priority: #{priority}\n" if priority
36
+ text += "Category: #{category}\n" if category
37
+ return text
38
+ end
39
+
40
+ def valid?
41
+ valid = true
42
+ end
43
+
44
+ def notify(message, time, priority, category, host)
45
+ begin
46
+ data = {
47
+ :client_key => Scout.client_key,
48
+ :plugin_id => plugin_id,
49
+ :format => 'xml',
50
+ 'alert[subject]' => message,
51
+ 'alert[body]' => Scout.format.call(message, priority, category, host)
52
+ }
53
+
54
+ uri = URI.parse('http://scoutapp.com/alerts/create')
55
+ Net::HTTP.post_form(uri, data)
56
+
57
+ self.info = "sent scout alert to plugin ##{plugin_id}"
58
+ rescue => e
59
+ self.info = "failed to send scout alert to plugin ##{plugin_id}: #{e.message}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,5 +1,10 @@
1
1
  require 'monitor'
2
2
 
3
+ # ruby 1.9 specific fixes
4
+ unless RUBY_VERSION < '1.9'
5
+ require 'god/compat19'
6
+ end
7
+
3
8
  module God
4
9
  class TimedEvent
5
10
  include Comparable
@@ -1,19 +1,14 @@
1
1
  module God
2
2
 
3
3
  class Logger < SimpleLogger
4
- SYSLOG_EQUIVALENTS = {:fatal => :crit,
5
- :error => :err,
6
- :warn => :debug,
7
- :info => :debug,
8
- :debug => :debug}
9
-
4
+
10
5
  attr_accessor :logs
11
6
 
12
7
  class << self
13
8
  attr_accessor :syslog
14
9
  end
15
10
 
16
- self.syslog ||= true
11
+ self.syslog = defined?(Syslog)
17
12
 
18
13
  # Instantiate a new Logger object
19
14
  def initialize(io = $stdout)
@@ -25,28 +20,12 @@ module God
25
20
  @templogio = StringIO.new
26
21
  @templog = SimpleLogger.new(@templogio)
27
22
  @templog.level = Logger::INFO
28
- load_syslog
29
23
  end
30
24
 
31
- # If Logger.syslog is true then attempt to load the syslog bindings. If syslog
32
- # cannot be loaded, then set Logger.syslog to false and continue.
33
- #
34
- # Returns nothing
35
- def load_syslog
36
- return unless Logger.syslog
37
-
38
- begin
39
- require 'syslog'
40
-
41
- # Ensure that Syslog is open
42
- begin
43
- Syslog.open('god')
44
- rescue RuntimeError
45
- Syslog.reopen('god')
46
- end
47
- rescue Exception
48
- Logger.syslog = false
49
- end
25
+
26
+ def level=(lev)
27
+ SysLogger.level = SimpleLogger::CONSTANT_TO_SYMBOL[lev] if Logger.syslog
28
+ super(lev)
50
29
  end
51
30
 
52
31
  # Log a message
@@ -80,7 +59,7 @@ module God
80
59
  self.send(level, text)
81
60
 
82
61
  # send to syslog
83
- Syslog.send(SYSLOG_EQUIVALENTS[level], text) if Logger.syslog
62
+ SysLogger.log(level, text) if Logger.syslog
84
63
  end
85
64
 
86
65
  # Get all log output for a given Watch since a certain Time.
@@ -127,4 +106,4 @@ module God
127
106
  end
128
107
  end
129
108
 
130
- end
109
+ end
@@ -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, :log_cmd, :start, :stop, :restart,
5
+ attr_accessor :name, :uid, :gid, :log, :log_cmd, :err_log, :err_log_cmd, :start, :stop, :restart,
6
6
  :unix_socket, :chroot, :env, :dir
7
7
 
8
8
  def initialize
@@ -300,7 +300,13 @@ module God
300
300
  else
301
301
  STDOUT.reopen file_in_chroot(self.log), "a"
302
302
  end
303
- STDERR.reopen STDOUT
303
+ if err_log_cmd
304
+ STDERR.reopen IO.popen(err_log_cmd, "a")
305
+ elsif err_log && (log_cmd || err_log != log)
306
+ STDERR.reopen file_in_chroot(err_log), "a"
307
+ else
308
+ STDERR.reopen STDOUT
309
+ end
304
310
 
305
311
  # close any other file descriptors
306
312
  3.upto(256){|fd| IO::new(fd).close rescue nil}
@@ -13,6 +13,12 @@ module God
13
13
  ERROR => 'ERROR',
14
14
  FATAL => 'FATAL'}
15
15
 
16
+ CONSTANT_TO_SYMBOL = { DEBUG => :debug,
17
+ INFO => :info,
18
+ WARN => :warn,
19
+ ERROR => :error,
20
+ FATAL => :fatal }
21
+
16
22
  attr_accessor :datetime_format, :level
17
23
 
18
24
  def initialize(io)
@@ -26,7 +32,7 @@ module God
26
32
 
27
33
  time = Time.now.strftime(self.datetime_format)
28
34
  label = SEV_LABEL[level]
29
- @io.print(label[0..0], ' [', time, '] ', label.rjust(5), ': ', msg, "\n")
35
+ @io.puts("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}")
30
36
  end
31
37
 
32
38
  def fatal(msg)
@@ -38,8 +38,11 @@ module God
38
38
 
39
39
  # Create a new Server and star the DRb server
40
40
  # +port+ is the port on which to start the DRb service (default nil)
41
- def initialize(port = nil)
42
- @port = port
41
+ def initialize(port = nil, user = nil, group = nil, perm = nil)
42
+ @port = port
43
+ @user = user
44
+ @group = group
45
+ @perm = perm
43
46
  start
44
47
  end
45
48
 
@@ -90,6 +93,14 @@ module God
90
93
  applog(nil, :info, "Started on #{DRb.uri}")
91
94
  end
92
95
  end
96
+
97
+ if File.exists?(self.socket_file)
98
+ uid = Etc.getpwnam(@user).uid if @user
99
+ gid = Etc.getgrnam(@group).gid if @group
100
+
101
+ File.chmod(Integer(@perm), socket_file) if @perm
102
+ File.chown(uid, gid, socket_file) if uid or gid
103
+ end
93
104
  end
94
105
  end
95
106