god 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.txt +67 -1
  2. data/Manifest.txt +3 -4
  3. data/Rakefile +1 -1
  4. data/bin/god +19 -1
  5. data/lib/god.rb +86 -49
  6. data/lib/god/cli/command.rb +7 -1
  7. data/lib/god/cli/run.rb +58 -0
  8. data/lib/god/condition.rb +6 -2
  9. data/lib/god/conditions/cpu_usage.rb +7 -6
  10. data/lib/god/conditions/http_response_code.rb +5 -1
  11. data/lib/god/conditions/memory_usage.rb +7 -6
  12. data/lib/god/conditions/process_exits.rb +15 -10
  13. data/lib/god/conditions/process_running.rb +17 -13
  14. data/lib/god/diagnostics.rb +37 -0
  15. data/lib/god/driver.rb +108 -0
  16. data/lib/god/event_handler.rb +41 -1
  17. data/lib/god/logger.rb +69 -19
  18. data/lib/god/metric.rb +2 -2
  19. data/lib/god/process.rb +84 -27
  20. data/lib/god/task.rb +286 -29
  21. data/lib/god/timeline.rb +20 -31
  22. data/lib/god/watch.rb +26 -15
  23. data/test/configs/child_events/child_events.god +0 -5
  24. data/test/configs/child_polls/simple_server.rb +1 -1
  25. data/test/configs/daemon_events/simple_server_stop.rb +2 -0
  26. data/test/configs/stress/stress.god +1 -1
  27. data/test/configs/test.rb +12 -28
  28. data/test/test_condition.rb +8 -0
  29. data/test/test_conditions_http_response_code.rb +5 -5
  30. data/test/test_conditions_process_running.rb +6 -4
  31. data/test/test_driver.rb +11 -0
  32. data/test/test_event_handler.rb +7 -0
  33. data/test/test_god.rb +63 -62
  34. data/test/test_metric.rb +0 -16
  35. data/test/test_process.rb +29 -1
  36. data/test/test_task.rb +177 -1
  37. data/test/test_timeline.rb +2 -1
  38. data/test/test_watch.rb +24 -6
  39. metadata +6 -8
  40. data/lib/god/hub.rb +0 -222
  41. data/lib/god/timer.rb +0 -87
  42. data/test/test_hub.rb +0 -240
  43. data/test/test_timer.rb +0 -69
@@ -1,4 +1,70 @@
1
- == 0.6.0 /
1
+ == 0.7.0 / 2008-02-01
2
+ * Minor Enhancements
3
+ * Better default pid_file_directory behavior
4
+ * Add --attach <pid> to specify that god should quit if <pid> exits
5
+ * Bug Fixes
6
+ * Handle ECONNRESET in HttpResponseCode
7
+
8
+ == 0.6.12 / 2008-01-31
9
+ * Minor Enhancements
10
+ * Allow log file output for non-daemonized god
11
+ * Switch to SIGTERM from SIGHUP for default lambda killer
12
+
13
+ == 0.6.11 / 2008-01-31
14
+ * Major Enhancements
15
+ * HUGE refactor of timer system to simplify scheduling
16
+ * Minor Enhancements
17
+ * Check for a truly working event system and disallow event conditions if none is present
18
+
19
+ == 0.6.10 / 2008-01-24
20
+ * Bug Fixes
21
+ * Fix ensure_stop nil pid no local variable bug
22
+
23
+ == 0.6.9 / 2008-01-23
24
+ * Bug Fixes
25
+ * Fix Timer condition dedup behavior
26
+
27
+ == 0.6.8 / 2008-01-23
28
+ * Minor Enhancements
29
+ * Warn if a command returns a non-zero exit code
30
+ * Ensure that stop command actually stops process
31
+
32
+ == 0.6.7 / 2008-01-22
33
+ * Minor Enhancements
34
+ * Add --no-syslog option to disable Syslog
35
+ * Allow contact redeclaration (dups are ignored)
36
+
37
+ == 0.6.6 / 2008-01-07
38
+ * Bug Fixes
39
+ * Redo Timer mutexing to reduce synchronization needs
40
+
41
+ == 0.6.5 / 2008-01-04
42
+ * Bug Fixes
43
+ * Fix Timer descheduling deadlock issue
44
+ * Change HttpResponseCode to use GET instead of HEAD
45
+
46
+ == 0.6.4 / 2008-12-31
47
+ * Bug Fixes
48
+ * Refactor Hub to clarify mutexing
49
+ * Eliminate potential iteration problem in Timer
50
+ * Add caching PID accessor to process to solve event deregistration failure
51
+
52
+ == 0.6.3 / 2007-12-18
53
+ * Minor Enhancements
54
+ * Output ProcessExits registration/deregistration info
55
+
56
+ == 0.6.2 / 2007-12-17
57
+ * Minor Enhancements
58
+ * Output registered PID for ProcessExits
59
+ * Bug Fixes
60
+ * Fix `god remove <group>` not working for unmonitored watches
61
+
62
+ == 0.6.1 / 2007-12-14
63
+
64
+ * Minor Enhancement
65
+ * Log when state change is complete
66
+
67
+ == 0.6.0 / 2007-12-4
2
68
 
3
69
  * Minor Enhancement
4
70
  * Move Syslog calls into God::Logger and clean up all calling code
@@ -34,12 +34,13 @@ lib/god/configurable.rb
34
34
  lib/god/contact.rb
35
35
  lib/god/contacts/email.rb
36
36
  lib/god/dependency_graph.rb
37
+ lib/god/diagnostics.rb
38
+ lib/god/driver.rb
37
39
  lib/god/errors.rb
38
40
  lib/god/event_handler.rb
39
41
  lib/god/event_handlers/dummy_handler.rb
40
42
  lib/god/event_handlers/kqueue_handler.rb
41
43
  lib/god/event_handlers/netlink_handler.rb
42
- lib/god/hub.rb
43
44
  lib/god/logger.rb
44
45
  lib/god/metric.rb
45
46
  lib/god/process.rb
@@ -49,7 +50,6 @@ lib/god/sugar.rb
49
50
  lib/god/system/process.rb
50
51
  lib/god/task.rb
51
52
  lib/god/timeline.rb
52
- lib/god/timer.rb
53
53
  lib/god/trigger.rb
54
54
  lib/god/watch.rb
55
55
  test/configs/child_events/child_events.god
@@ -84,10 +84,10 @@ test/test_conditions_process_running.rb
84
84
  test/test_conditions_tries.rb
85
85
  test/test_contact.rb
86
86
  test/test_dependency_graph.rb
87
+ test/test_driver.rb
87
88
  test/test_event_handler.rb
88
89
  test/test_god.rb
89
90
  test/test_handlers_kqueue_handler.rb
90
- test/test_hub.rb
91
91
  test/test_logger.rb
92
92
  test/test_metric.rb
93
93
  test/test_process.rb
@@ -97,6 +97,5 @@ test/test_sugar.rb
97
97
  test/test_system_process.rb
98
98
  test/test_task.rb
99
99
  test/test_timeline.rb
100
- test/test_timer.rb
101
100
  test/test_trigger.rb
102
101
  test/test_watch.rb
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'hoe'
3
3
 
4
- Hoe.new('god', '0.6.0') do |p|
4
+ Hoe.new('god', '0.7.0') 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
@@ -9,7 +9,7 @@ require 'optparse'
9
9
  require 'drb'
10
10
 
11
11
  begin
12
- options = {:daemonize => true, :port => 17165}
12
+ options = {:daemonize => true, :port => 17165, :syslog => true}
13
13
 
14
14
  opts = OptionParser.new do |opts|
15
15
  opts.banner = <<-EOF
@@ -71,10 +71,28 @@ begin
71
71
  opts.on("-V", "Print extended version and build information") do
72
72
  options[:info] = true
73
73
  end
74
+
75
+ opts.on("--log-level LEVEL", "Log level [debug|info|warn|error|fatal]") do |x|
76
+ options[:log_level] = x.to_sym
77
+ end
78
+
79
+ opts.on("--no-syslog", "Disable output to syslog") do
80
+ options[:syslog] = false
81
+ end
82
+
83
+ opts.on("--attach PID", "Quit god when the attached process dies") do |x|
84
+ options[:attach] = x
85
+ end
74
86
  end
75
87
 
76
88
  opts.parse!
77
89
 
90
+ # validate
91
+ if options[:log_level] && ![:debug, :info, :warn, :error, :fatal].include?(options[:log_level])
92
+ abort("Invalid log level '#{options[:log_level]}'")
93
+ end
94
+
95
+ # dispatch
78
96
  if !options[:config] && options[:version]
79
97
  require 'god'
80
98
  God::CLI::Version.version
data/lib/god.rb CHANGED
@@ -6,9 +6,16 @@ require 'rubygems'
6
6
  # core
7
7
  require 'stringio'
8
8
  require 'logger'
9
+ require 'fileutils'
10
+
11
+ begin
12
+ require 'fastthread'
13
+ rescue LoadError
14
+ ensure
15
+ require 'thread'
16
+ end
9
17
 
10
18
  # stdlib
11
- require 'syslog'
12
19
 
13
20
  # internal requires
14
21
  require 'god/errors'
@@ -42,8 +49,7 @@ require 'god/contact'
42
49
  require 'god/contacts/email'
43
50
 
44
51
  require 'god/socket'
45
- require 'god/timer'
46
- require 'god/hub'
52
+ require 'god/driver'
47
53
 
48
54
  require 'god/metric'
49
55
  require 'god/watch'
@@ -58,6 +64,8 @@ require 'god/sugar'
58
64
  require 'god/cli/version'
59
65
  require 'god/cli/command'
60
66
 
67
+ require 'god/diagnostics'
68
+
61
69
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
62
70
 
63
71
  # App wide logging system
@@ -76,13 +84,6 @@ $run ||= nil
76
84
 
77
85
  GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
78
86
 
79
- # Ensure that Syslog is open
80
- begin
81
- Syslog.open('god')
82
- rescue RuntimeError
83
- Syslog.reopen('god')
84
- end
85
-
86
87
  # Return the binding of god's root level
87
88
  def root_binding
88
89
  binding
@@ -132,12 +133,13 @@ class Module
132
133
  end
133
134
 
134
135
  module God
135
- VERSION = '0.6.0'
136
+ VERSION = '0.7.0'
136
137
 
137
- LOG_BUFFER_SIZE_DEFAULT = 1000
138
- PID_FILE_DIRECTORY_DEFAULT = '/var/run/god'
138
+ LOG_BUFFER_SIZE_DEFAULT = 10
139
+ PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
139
140
  DRB_PORT_DEFAULT = 17165
140
141
  DRB_ALLOW_DEFAULT = ['127.0.0.1']
142
+ LOG_LEVEL_DEFAULT = :info
141
143
 
142
144
  class << self
143
145
  # user configurable
@@ -146,7 +148,8 @@ module God
146
148
  :port,
147
149
  :allow,
148
150
  :log_buffer_size,
149
- :pid_file_directory
151
+ :pid_file_directory,
152
+ :log_level
150
153
 
151
154
  # internal
152
155
  attr_accessor :inited,
@@ -157,7 +160,8 @@ module God
157
160
  :watches,
158
161
  :groups,
159
162
  :contacts,
160
- :contact_groups
163
+ :contact_groups,
164
+ :main
161
165
  end
162
166
 
163
167
  # initialize class instance variables
@@ -167,6 +171,7 @@ module God
167
171
  self.allow = nil
168
172
  self.log_buffer_size = nil
169
173
  self.pid_file_directory = nil
174
+ self.log_level = nil
170
175
 
171
176
  # Initialize internal data.
172
177
  #
@@ -185,10 +190,20 @@ module God
185
190
 
186
191
  # set defaults
187
192
  self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
188
- self.pid_file_directory ||= PID_FILE_DIRECTORY_DEFAULT
189
193
  self.port ||= DRB_PORT_DEFAULT
190
194
  self.allow ||= DRB_ALLOW_DEFAULT
191
- LOG.level = Logger::INFO
195
+ self.log_level ||= LOG_LEVEL_DEFAULT
196
+
197
+ # additional setup
198
+ self.setup
199
+
200
+ # log level
201
+ log_level_map = {:debug => Logger::DEBUG,
202
+ :info => Logger::INFO,
203
+ :warn => Logger::WARN,
204
+ :error => Logger::ERROR,
205
+ :fatal => Logger::FATAL}
206
+ LOG.level = log_level_map[self.log_level]
192
207
 
193
208
  # init has been executed
194
209
  self.inited = true
@@ -290,6 +305,8 @@ module God
290
305
  if watch.group
291
306
  self.groups[watch.group].delete(watch)
292
307
  end
308
+
309
+ applog(watch, :info, "#{watch.name} unwatched")
293
310
  end
294
311
 
295
312
  # Instantiate a new Contact of the given kind and send it to the block.
@@ -324,9 +341,10 @@ module God
324
341
  self.uncontact(existing_contact)
325
342
  end
326
343
 
327
- # ensure the new contact has a unique name
344
+ # warn and noop if the contact has been defined before
328
345
  if self.contacts[c.name] || self.contact_groups[c.name]
329
- abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
346
+ applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
347
+ return
330
348
  end
331
349
 
332
350
  # abort if the Contact is invalid, the Contact will have printed
@@ -373,30 +391,30 @@ module God
373
391
  #
374
392
  # Returns String[]:task_names
375
393
  def self.control(name, command)
376
- # get the list of watches
377
- watches = Array(self.watches[name] || self.groups[name])
394
+ # get the list of items
395
+ items = Array(self.watches[name] || self.groups[name]).dup
378
396
 
379
397
  jobs = []
380
398
 
381
399
  # do the command
382
400
  case command
383
401
  when "start", "monitor"
384
- watches.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
402
+ items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
385
403
  when "restart"
386
- watches.each { |w| jobs << Thread.new { w.move(:restart) } }
404
+ items.each { |w| jobs << Thread.new { w.move(:restart) } }
387
405
  when "stop"
388
- watches.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
406
+ items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
389
407
  when "unmonitor"
390
- watches.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
408
+ items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
391
409
  when "remove"
392
- watches.each { |w| jobs << Thread.new { self.unwatch(w) } }
410
+ items.each { |w| self.unwatch(w) }
393
411
  else
394
412
  raise InvalidCommandError.new
395
413
  end
396
414
 
397
415
  jobs.each { |j| j.join }
398
416
 
399
- watches.map { |x| x.name }
417
+ items.map { |x| x.name }
400
418
  end
401
419
 
402
420
  # Unmonitor and stop all tasks.
@@ -513,20 +531,40 @@ module God
513
531
  end
514
532
 
515
533
  def self.setup
516
- # Make pid directory
517
- unless test(?d, self.pid_file_directory)
518
- begin
519
- FileUtils.mkdir_p(self.pid_file_directory)
520
- rescue Errno::EACCES => e
521
- abort "Failed to create pid file directory: #{e.message}"
534
+ if self.pid_file_directory
535
+ # pid file dir was specified, ensure it is created and writable
536
+ unless File.exist?(self.pid_file_directory)
537
+ begin
538
+ FileUtils.mkdir_p(self.pid_file_directory)
539
+ rescue Errno::EACCES => e
540
+ abort "Failed to create pid file directory: #{e.message}"
541
+ end
542
+ end
543
+
544
+ unless File.writable?(self.pid_file_directory)
545
+ abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
546
+ end
547
+ else
548
+ # no pid file dir specified, try defaults
549
+ PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
550
+ dir = File.expand_path(idir)
551
+ begin
552
+ FileUtils.mkdir_p(dir)
553
+ if File.writable?(dir)
554
+ self.pid_file_directory = dir
555
+ break
556
+ end
557
+ rescue Errno::EACCES => e
558
+ end
559
+ end
560
+
561
+ unless self.pid_file_directory
562
+ dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
563
+ abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
522
564
  end
523
565
  end
524
- end
525
-
526
- def self.validater
527
- unless test(?w, self.pid_file_directory)
528
- abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
529
- end
566
+
567
+ applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
530
568
  end
531
569
 
532
570
  # Initialize and startup the machinery that makes god work.
@@ -534,18 +572,10 @@ module God
534
572
  # Returns nothing
535
573
  def self.start
536
574
  self.internal_init
537
- self.setup
538
- self.validater
539
575
 
540
576
  # instantiate server
541
577
  self.server = Socket.new(self.port)
542
578
 
543
- # start event handler system
544
- EventHandler.start if EventHandler.loaded?
545
-
546
- # start the timer system
547
- Timer.get
548
-
549
579
  # start monitoring any watches set to autostart
550
580
  self.watches.values.each { |w| w.monitor if w.autostart? }
551
581
 
@@ -555,8 +585,15 @@ module God
555
585
  # mark as running
556
586
  self.running = true
557
587
 
558
- # join the timer thread so we don't exit
559
- Timer.get.join
588
+ # don't exit
589
+ self.main =
590
+ Thread.new do
591
+ loop do
592
+ sleep 60
593
+ end
594
+ end
595
+
596
+ self.main.join
560
597
  end
561
598
 
562
599
  # To be called on program exit to start god
@@ -77,6 +77,12 @@ module God
77
77
  begin
78
78
  Signal.trap('INT') { exit }
79
79
  name = @args[1]
80
+
81
+ unless name
82
+ puts "You must specify a Task or Group name"
83
+ exit!
84
+ end
85
+
80
86
  t = Time.at(0)
81
87
  loop do
82
88
  print @server.running_log(name, t)
@@ -141,7 +147,7 @@ module God
141
147
 
142
148
  God::EventHandler.register(pid, :proc_exit) do
143
149
  puts "[ok] process exit event received"
144
- exit(0)
150
+ exit!(0)
145
151
  end
146
152
 
147
153
  sleep(1)
@@ -12,6 +12,7 @@ module God
12
12
  # have at_exit start god
13
13
  $run = true
14
14
 
15
+ # run
15
16
  if @options[:daemonize]
16
17
  run_daemonized
17
18
  else
@@ -19,6 +20,19 @@ module God
19
20
  end
20
21
  end
21
22
 
23
+ def attach
24
+ process = System::Process.new(@options[:attach])
25
+ Thread.new do
26
+ loop do
27
+ unless process.exists?
28
+ applog(nil, :info, "Going down because attached process #{@options[:attach]} exited")
29
+ exit!
30
+ end
31
+ sleep 5
32
+ end
33
+ end
34
+ end
35
+
22
36
  def run_daemonized
23
37
  # trap and ignore SIGHUP
24
38
  Signal.trap('HUP') {}
@@ -40,6 +54,11 @@ module God
40
54
  puts
41
55
  end
42
56
 
57
+ # start attached pid watcher if necessary
58
+ if @options[:attach]
59
+ self.attach
60
+ end
61
+
43
62
  # set port if requested
44
63
  if @options[:port]
45
64
  God.port = @options[:port]
@@ -50,12 +69,26 @@ module God
50
69
  God.pid = @options[:pid]
51
70
  end
52
71
 
72
+ unless @options[:syslog]
73
+ Logger.syslog = false
74
+ end
75
+
53
76
  # load config
54
77
  if @options[:config]
78
+ # set log level, defaults to WARN
79
+ if @options[:log_level]
80
+ God.log_level = @options[:log_level]
81
+ else
82
+ God.log_level = :warn
83
+ end
84
+
55
85
  unless File.exist?(@options[:config])
56
86
  abort "File not found: #{@options[:config]}"
57
87
  end
58
88
 
89
+ # start the event handler
90
+ God::EventHandler.start if God::EventHandler.loaded?
91
+
59
92
  begin
60
93
  load File.expand_path(@options[:config])
61
94
  rescue Exception => e
@@ -73,6 +106,7 @@ module God
73
106
  STDIN.reopen "/dev/null"
74
107
  STDOUT.reopen(log_file, "a")
75
108
  STDERR.reopen STDOUT
109
+ STDOUT.sync = true
76
110
  rescue => e
77
111
  puts e.message
78
112
  puts e.backtrace.join("\n")
@@ -92,15 +126,28 @@ module God
92
126
  def run_in_front
93
127
  require 'god'
94
128
 
129
+ # start attached pid watcher if necessary
130
+ if @options[:attach]
131
+ self.attach
132
+ end
133
+
95
134
  if @options[:port]
96
135
  God.port = @options[:port]
97
136
  end
98
137
 
138
+ # set log level if requested
139
+ if @options[:log_level]
140
+ God.log_level = @options[:log_level]
141
+ end
142
+
99
143
  if @options[:config]
100
144
  unless File.exist?(@options[:config])
101
145
  abort "File not found: #{@options[:config]}"
102
146
  end
103
147
 
148
+ # start the event handler
149
+ God::EventHandler.start if God::EventHandler.loaded?
150
+
104
151
  begin
105
152
  load File.expand_path(@options[:config])
106
153
  rescue Exception => e
@@ -112,6 +159,17 @@ module God
112
159
  abort "There was an error in your configuration file (see above)"
113
160
  end
114
161
  end
162
+
163
+ if @options[:log]
164
+ log_file = File.expand_path(@options[:log])
165
+ puts "Sending output to log file: #{log_file}"
166
+
167
+ # reset file descriptors
168
+ STDIN.reopen "/dev/null"
169
+ STDOUT.reopen(log_file, "a")
170
+ STDERR.reopen STDOUT
171
+ STDOUT.sync = true
172
+ end
115
173
  end
116
174
  end
117
175
  end # Run