god 0.2.0 → 0.3.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 (62) hide show
  1. data/History.txt +26 -2
  2. data/Manifest.txt +19 -5
  3. data/Rakefile +7 -2
  4. data/bin/god +131 -11
  5. data/examples/events.god +52 -0
  6. data/examples/gravatar.god +29 -35
  7. data/ext/god/extconf.rb +49 -2
  8. data/ext/god/kqueue_handler.c +2 -2
  9. data/ext/god/netlink_handler.c +17 -4
  10. data/lib/god.rb +127 -25
  11. data/lib/god/behavior.rb +8 -3
  12. data/lib/god/behaviors/clean_pid_file.rb +2 -8
  13. data/lib/god/condition.rb +6 -5
  14. data/lib/god/conditions/cpu_usage.rb +4 -4
  15. data/lib/god/conditions/lambda.rb +19 -0
  16. data/lib/god/conditions/memory_usage.rb +4 -4
  17. data/lib/god/conditions/process_exits.rb +5 -7
  18. data/lib/god/conditions/process_running.rb +4 -4
  19. data/lib/god/errors.rb +3 -0
  20. data/lib/god/event_handler.rb +28 -3
  21. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  22. data/lib/god/event_handlers/kqueue_handler.rb +2 -0
  23. data/lib/god/event_handlers/netlink_handler.rb +2 -0
  24. data/lib/god/hub.rb +40 -23
  25. data/lib/god/metric.rb +4 -4
  26. data/lib/god/process.rb +105 -0
  27. data/lib/god/registry.rb +28 -0
  28. data/lib/god/server.rb +3 -4
  29. data/lib/god/sugar.rb +47 -0
  30. data/lib/god/system/process.rb +1 -2
  31. data/lib/god/timer.rb +13 -6
  32. data/lib/god/watch.rb +61 -29
  33. data/test/configs/child_events/child_events.god +25 -0
  34. data/test/configs/child_events/simple_server.rb +3 -0
  35. data/test/configs/child_polls/child_polls.god +15 -0
  36. data/test/configs/child_polls/simple_server.rb +3 -0
  37. data/test/configs/daemon_events/daemon_events.god +30 -0
  38. data/test/configs/daemon_events/simple_server.rb +6 -0
  39. data/test/configs/real.rb +47 -49
  40. data/test/configs/test.rb +52 -62
  41. data/test/helper.rb +44 -14
  42. data/test/test_behavior.rb +10 -2
  43. data/test/test_condition.rb +19 -3
  44. data/test/test_conditions_process_running.rb +42 -0
  45. data/test/test_event_handler.rb +73 -0
  46. data/test/test_god.rb +206 -9
  47. data/test/test_handlers_kqueue_handler.rb +12 -0
  48. data/test/test_hub.rb +157 -0
  49. data/test/test_metric.rb +30 -2
  50. data/test/test_process.rb +84 -0
  51. data/test/test_registry.rb +14 -0
  52. data/test/test_server.rb +3 -2
  53. data/test/test_sugar.rb +42 -0
  54. data/test/test_system_process.rb +1 -1
  55. data/test/test_timer.rb +8 -1
  56. data/test/test_watch.rb +137 -2
  57. metadata +28 -17
  58. data/examples/local.god +0 -60
  59. data/ext/god/Makefile +0 -149
  60. data/lib/god/base.rb +0 -13
  61. data/lib/god/meddle.rb +0 -38
  62. data/test/test_meddle.rb +0 -46
@@ -5,9 +5,9 @@
5
5
  #include <sys/time.h>
6
6
  #include <errno.h>
7
7
 
8
+ static VALUE mGod;
8
9
  static VALUE cKQueueHandler;
9
10
  static VALUE cEventHandler;
10
- static VALUE mGod;
11
11
 
12
12
  static ID proc_exit;
13
13
  static ID proc_fork;
@@ -119,4 +119,4 @@ Init_kqueue_handler_ext()
119
119
  rb_define_singleton_method(cKQueueHandler, "event_mask", kqh_event_mask, 1);
120
120
  }
121
121
 
122
- #endif
122
+ #endif
@@ -2,15 +2,16 @@
2
2
 
3
3
  #include <ruby.h>
4
4
  #include <sys/types.h>
5
+ #include <unistd.h>
5
6
  #include <sys/socket.h>
6
7
  #include <linux/netlink.h>
7
8
  #include <linux/connector.h>
8
9
  #include <linux/cn_proc.h>
9
10
  #include <errno.h>
10
11
 
12
+ static VALUE mGod;
11
13
  static VALUE cNetlinkHandler;
12
14
  static VALUE cEventHandler;
13
- static VALUE mGod;
14
15
 
15
16
  static ID proc_exit;
16
17
  static ID proc_fork;
@@ -71,6 +72,12 @@ nlh_handle_events()
71
72
 
72
73
  rb_funcall(cEventHandler, m_call, 2, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork));
73
74
  return INT2FIX(1);
75
+
76
+ case PROC_EVENT_NONE:
77
+ case PROC_EVENT_EXEC:
78
+ case PROC_EVENT_UID:
79
+ case PROC_EVENT_GID:
80
+ break;
74
81
  }
75
82
  }
76
83
 
@@ -87,17 +94,23 @@ connect_to_netlink()
87
94
  struct sockaddr_nl sa_nl; /* netlink interface info */
88
95
  char buff[NL_MESSAGE_SIZE];
89
96
  struct nlmsghdr *hdr; /* for telling netlink what we want */
90
- struct cn_msg *msg; /* the actual connector message
97
+ struct cn_msg *msg; /* the actual connector message */
91
98
 
92
99
  /* connect to netlink socket */
93
100
  nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
94
101
 
102
+ if (-1 == nl_sock) {
103
+ rb_raise(rb_eStandardError, strerror(errno));
104
+ }
105
+
95
106
  bzero(&sa_nl, sizeof(sa_nl));
96
107
  sa_nl.nl_family = AF_NETLINK;
97
108
  sa_nl.nl_groups = CN_IDX_PROC;
98
109
  sa_nl.nl_pid = getpid();
99
110
 
100
- bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
111
+ if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) {
112
+ rb_raise(rb_eStandardError, strerror(errno));
113
+ }
101
114
 
102
115
  /* Fill header */
103
116
  hdr = (struct nlmsghdr *)buff;
@@ -137,4 +150,4 @@ Init_netlink_handler_ext()
137
150
  connect_to_netlink();
138
151
  }
139
152
 
140
- #endif
153
+ #endif
data/lib/god.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
2
 
3
+ require 'syslog'
4
+
3
5
  # internal requires
4
- require 'god/base'
5
6
  require 'god/errors'
6
7
 
7
8
  require 'god/system/process'
@@ -23,46 +24,147 @@ require 'god/timer'
23
24
  require 'god/hub'
24
25
 
25
26
  require 'god/metric'
26
-
27
27
  require 'god/watch'
28
- require 'god/meddle'
29
28
 
30
29
  require 'god/event_handler'
30
+ require 'god/registry'
31
+ require 'god/process'
32
+
33
+ require 'god/sugar'
31
34
 
32
- Thread.abort_on_exception = true
35
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
36
+
37
+ begin
38
+ Syslog.open('god')
39
+ rescue RuntimeError
40
+ Syslog.reopen('god')
41
+ end
42
+
43
+ God::EventHandler.load
33
44
 
34
45
  module God
35
- VERSION = '0.2.0'
46
+ VERSION = '0.3.0'
47
+
48
+ class << self
49
+ attr_accessor :inited, :host, :port
50
+
51
+ # drb
52
+ attr_accessor :server
53
+
54
+ # api
55
+ attr_accessor :watches, :groups
56
+ end
57
+
58
+ def self.init
59
+ # only do this once
60
+ return if self.inited
61
+
62
+ # variable init
63
+ self.watches = {}
64
+ self.groups = {}
65
+
66
+ # yield to the config file
67
+ yield self if block_given?
68
+
69
+ # instantiate server
70
+ self.server = Server.new(self.host, self.port)
71
+
72
+ # init has been executed
73
+ self.inited = true
74
+ end
75
+
76
+ # Where pid files created by god will go by default
77
+ def self.pid_file_directory
78
+ @pid_file_directory ||= '/var/run/god'
79
+ end
36
80
 
37
- case RUBY_PLATFORM
38
- when /darwin/i, /bsd/i
39
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
40
- require 'god/event_handlers/kqueue_handler'
41
- EventHandler.handler = KQueueHandler
42
- when /linux/i
43
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
44
- require 'god/event_handlers/netlink_handler'
45
- EventHandler.handler = NetlinkHandler
46
- else
47
- raise NotImplementedError, "Platform not supported for EventHandler"
81
+ def self.pid_file_directory=(value)
82
+ @pid_file_directory = value
48
83
  end
49
84
 
50
- def self.meddle(options = {})
51
- m = Meddle.new(options)
85
+ # Instantiate a new, empty Watch object and pass it to the mandatory
86
+ # block. The attributes of the watch will be set by the configuration
87
+ # file.
88
+ def self.watch
89
+ self.init
52
90
 
53
- # yeild to the config file
54
- yield m
91
+ w = Watch.new
92
+ yield(w)
93
+
94
+ # ensure the new watch has a unique name
95
+ if self.watches[w.name] || self.groups[w.name]
96
+ abort "Watch name '#{w.name}' already used for a Watch or Group"
97
+ end
98
+
99
+ # add to list of watches
100
+ self.watches[w.name] = w
101
+
102
+ # add to group if specified
103
+ if w.group
104
+ # ensure group name hasn't been used for a watch already
105
+ if self.watches[w.group]
106
+ abort "Group name '#{w.group}' already used for a Watch"
107
+ end
108
+
109
+ self.groups[w.group] ||= []
110
+ self.groups[w.group] << w.name
111
+ end
112
+
113
+ # register watch
114
+ w.register!
115
+ end
116
+
117
+ def self.control(name, command)
118
+ # get the list of watches
119
+ watches = Array(self.watches[name] || self.groups[name])
120
+
121
+ # do the command
122
+ case command
123
+ when "start", "monitor"
124
+ watches.each { |w| w.monitor }
125
+ when "restart"
126
+ watches.each { |w| w.move(:restart) }
127
+ when "stop"
128
+ watches.each { |w| w.unmonitor.action(:stop) }
129
+ when "unmonitor"
130
+ watches.each { |w| w.unmonitor }
131
+ else
132
+ raise InvalidCommandError.new
133
+ end
134
+
135
+ watches
136
+ end
137
+
138
+ def self.start
139
+ # make sure there's something to do
140
+ if self.watches.nil? || self.watches.empty?
141
+ abort "You must specify at least one watch!"
142
+ end
55
143
 
56
144
  # start event handler system
57
- EventHandler.start
145
+ EventHandler.start if EventHandler.loaded?
58
146
 
59
147
  # start the timer system
60
148
  Timer.get
61
149
 
62
- # start monitoring each watch
63
- m.monitor
150
+ # start monitoring any watches set to autostart
151
+ self.watches.values.each { |w| w.monitor if w.autostart? }
64
152
 
65
- # join the timer thread to we don't exit
153
+ # join the timer thread so we don't exit
66
154
  Timer.get.join
67
- end
155
+ end
156
+
157
+ def self.at_exit
158
+ self.start
159
+ end
160
+
161
+ def self.load(glob)
162
+ Dir[glob].each do |f|
163
+ Kernel.load f
164
+ end
165
+ end
68
166
  end
167
+
168
+ at_exit do
169
+ God.at_exit
170
+ end
@@ -1,12 +1,16 @@
1
1
  module God
2
2
 
3
- class Behavior < Base
3
+ class Behavior
4
+ attr_accessor :watch
5
+
4
6
  # Generate a Behavior of the given kind. The proper class if found by camel casing the
5
7
  # kind (which is given as an underscored symbol).
6
8
  # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Behaviors::FooBar)
7
- def self.generate(kind)
9
+ def self.generate(kind, watch)
8
10
  sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
9
- God::Behaviors.const_get(sym).new
11
+ b = God::Behaviors.const_get(sym).new
12
+ b.watch = watch
13
+ b
10
14
  rescue NameError
11
15
  raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}")
12
16
  end
@@ -59,6 +63,7 @@ module God
59
63
  protected
60
64
 
61
65
  def complain(text)
66
+ Syslog.err(text)
62
67
  puts text
63
68
  false
64
69
  end
@@ -2,20 +2,14 @@ module God
2
2
  module Behaviors
3
3
 
4
4
  class CleanPidFile < Behavior
5
- attr_accessor :pid_file
6
-
7
- def initialize
8
- self.pid_file = nil
9
- end
10
-
11
5
  def valid?
12
6
  valid = true
13
- valid &= complain("You must specify the 'pid_file' attribute for :clean_pid_file") if self.pid_file.nil?
7
+ valid &= complain("You must specify the 'pid_file' attribute on the Watch for :clean_pid_file") if self.watch.pid_file.nil?
14
8
  valid
15
9
  end
16
10
 
17
11
  def before_start
18
- File.delete(self.pid_file) rescue nil
12
+ File.delete(self.watch.pid_file) rescue nil
19
13
  end
20
14
  end
21
15
 
@@ -4,15 +4,16 @@ module God
4
4
  # Generate a Condition of the given kind. The proper class if found by camel casing the
5
5
  # kind (which is given as an underscored symbol).
6
6
  # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Conditions::FooBar)
7
- def self.generate(kind)
7
+ def self.generate(kind, watch)
8
8
  sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
9
- cond = God::Conditions.const_get(sym).new
9
+ c = God::Conditions.const_get(sym).new
10
10
 
11
- unless cond.kind_of?(PollCondition) || cond.kind_of?(EventCondition)
12
- abort "Condition '#{cond.class.name}' must subclass either God::PollCondition or God::EventCondition"
11
+ unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition)
12
+ abort "Condition '#{c.class.name}' must subclass either God::PollCondition or God::EventCondition"
13
13
  end
14
14
 
15
- cond
15
+ c.watch = watch
16
+ c
16
17
  rescue NameError
17
18
  raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
18
19
  end
@@ -2,7 +2,7 @@ module God
2
2
  module Conditions
3
3
 
4
4
  class CpuUsage < PollCondition
5
- attr_accessor :pid_file, :above, :times
5
+ attr_accessor :above, :times
6
6
 
7
7
  def initialize
8
8
  super
@@ -20,15 +20,15 @@ module God
20
20
 
21
21
  def valid?
22
22
  valid = true
23
- valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
23
+ valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
24
24
  valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
25
25
  valid
26
26
  end
27
27
 
28
28
  def test
29
- return false unless File.exist?(self.pid_file)
29
+ return false unless File.exist?(self.watch.pid_file)
30
30
 
31
- pid = File.open(self.pid_file).read.strip
31
+ pid = File.read(self.watch.pid_file).strip
32
32
  process = System::Process.new(pid)
33
33
  @timeline.push(process.percent_cpu)
34
34
  if @timeline.select { |x| x > self.above }.size >= self.times.first
@@ -0,0 +1,19 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Lambda < PollCondition
5
+ attr_accessor :lambda
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("You must specify the 'lambda' attribute for :lambda") if self.lambda.nil?
10
+ valid
11
+ end
12
+
13
+ def test
14
+ self.lambda.call()
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -2,7 +2,7 @@ module God
2
2
  module Conditions
3
3
 
4
4
  class MemoryUsage < PollCondition
5
- attr_accessor :pid_file, :above, :times
5
+ attr_accessor :above, :times
6
6
 
7
7
  def initialize
8
8
  super
@@ -20,15 +20,15 @@ module God
20
20
 
21
21
  def valid?
22
22
  valid = true
23
- valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
23
+ valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
24
24
  valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
25
25
  valid
26
26
  end
27
27
 
28
28
  def test
29
- return false unless File.exist?(self.pid_file)
29
+ return false unless File.exist?(self.watch.pid_file)
30
30
 
31
- pid = File.open(self.pid_file).read.strip
31
+ pid = File.read(self.watch.pid_file).strip
32
32
  process = System::Process.new(pid)
33
33
  @timeline.push(process.memory)
34
34
  if @timeline.select { |x| x > self.above }.size >= self.times.first
@@ -2,24 +2,22 @@ module God
2
2
  module Conditions
3
3
 
4
4
  class ProcessExits < EventCondition
5
- attr_accessor :pid_file
6
-
7
5
  def valid?
8
6
  valid = true
9
- valid &= complain("You must specify the 'pid_file' attribute for :process_exits") if self.pid_file.nil?
7
+ valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_exits") if self.watch.pid_file.nil?
10
8
  valid
11
9
  end
12
10
 
13
11
  def register
14
- pid = File.open(self.pid_file).read.strip.to_i
12
+ pid = File.read(self.watch.pid_file).strip.to_i
15
13
 
16
- EventHandler.register(pid, :proc_exit) {
14
+ EventHandler.register(pid, :proc_exit) do
17
15
  Hub.trigger(self)
18
- }
16
+ end
19
17
  end
20
18
 
21
19
  def deregister
22
- pid = File.open(self.pid_file).read.strip.to_i
20
+ pid = File.read(self.watch.pid_file).strip.to_i
23
21
  EventHandler.deregister(pid, :proc_exit)
24
22
  end
25
23
  end
@@ -2,19 +2,19 @@ module God
2
2
  module Conditions
3
3
 
4
4
  class ProcessRunning < PollCondition
5
- attr_accessor :pid_file, :running
5
+ attr_accessor :running
6
6
 
7
7
  def valid?
8
8
  valid = true
9
- valid &= complain("You must specify the 'pid_file' attribute for :process_running") if self.pid_file.nil?
9
+ valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_running") if self.watch.pid_file.nil?
10
10
  valid &= complain("You must specify the 'running' attribute for :process_running") if self.running.nil?
11
11
  valid
12
12
  end
13
13
 
14
14
  def test
15
- return !self.running unless File.exist?(self.pid_file)
15
+ return !self.running unless File.exist?(self.watch.pid_file)
16
16
 
17
- pid = File.open(self.pid_file).read.strip
17
+ pid = File.read(self.watch.pid_file).strip
18
18
  active = System::Process.new(pid).exists?
19
19
 
20
20
  (self.running && active) || (!self.running && !active)