god 0.8.0 → 0.9.0

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