god 0.2.0 → 0.3.0

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