public
Fork of mojombo/god
Description: Ruby process monitor
Homepage: http://god.rubyforge.org
Clone URL: git://github.com/Bertg/god.git
Search Repo:
add notification system
mojombo (author)
Mon Sep 10 00:32:49 -0700 2007
commit  4dd171d92d0e0d16192c563c73df4c6dfca0d6a3
tree    b5a0ed585c5655d0b1bcf7de86c5236e8e64fa43
parent  dd1a7678cb4fe0d58c7efc5d82d1900c312a78b8
...
3
4
5
 
6
7
8
9
 
 
 
 
10
11
12
...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0
@@ -3,10 +3,15 @@
0
 * Major Enhancements
0
   * Implement lifecycle scoped metric to allow for cross-state conditions
0
   * Add TriggerCondition for conditions that need info about state changes
0
+ * Implement notification system
0
 * Minor Enchancements
0
   * Allow EventConditions to do transition overloading
0
 * New Conditions
0
   * Flapping < TriggerCondition - trigger on state change
0
+* New Contacts
0
+ * Email < Contact - notify via email (smtp, sendmail)
0
+* Bug Fixes
0
+ * Fix abort not aborting problem
0
 
0
 == 0.4.0 / 2007-09-13
0
 
...
22
23
24
 
 
 
25
26
27
...
45
46
47
 
 
48
49
50
...
62
63
64
 
65
66
67
...
22
23
24
25
26
27
28
29
30
...
48
49
50
51
52
53
54
55
...
67
68
69
70
71
72
73
0
@@ -22,6 +22,9 @@ lib/god/conditions/memory_usage.rb
0
 lib/god/conditions/process_exits.rb
0
 lib/god/conditions/process_running.rb
0
 lib/god/conditions/tries.rb
0
+lib/god/configurable.rb
0
+lib/god/contact.rb
0
+lib/god/contacts/email.rb
0
 lib/god/dependency_graph.rb
0
 lib/god/errors.rb
0
 lib/god/event_handler.rb
0
@@ -45,6 +48,8 @@ test/configs/child_events/child_events.god
0
 test/configs/child_events/simple_server.rb
0
 test/configs/child_polls/child_polls.god
0
 test/configs/child_polls/simple_server.rb
0
+test/configs/contact/contact.god
0
+test/configs/contact/simple_server.rb
0
 test/configs/daemon_events/daemon_events.god
0
 test/configs/daemon_events/simple_server.rb
0
 test/configs/daemon_polls/daemon_polls.god
0
@@ -62,6 +67,7 @@ test/test_behavior.rb
0
 test/test_condition.rb
0
 test/test_conditions_process_running.rb
0
 test/test_conditions_tries.rb
0
+test/test_contact.rb
0
 test/test_dependency_graph.rb
0
 test/test_event_handler.rb
0
 test/test_god.rb
...
6
7
8
9
10
 
 
11
12
13
...
6
7
8
 
 
9
10
11
12
13
0
@@ -6,8 +6,8 @@
0
 
0
 RAILS_ROOT = "/Users/tom/dev/helloworld"
0
 
0
-God::Notifications::Email.system = :smtp
0
-God::Notifications::Email.host = ""
0
+God::Contacts::Email.delivery_method = :smtp
0
+God::Contacts::Email.server_settings = {}
0
 
0
 God.contact(:email) do |c|
0
   c.name = 'tom'
...
12
13
14
 
15
16
17
...
28
29
30
 
 
 
31
32
33
...
53
54
55
 
 
 
 
 
 
 
 
56
57
58
...
77
78
79
80
 
 
 
81
82
83
...
86
87
88
 
 
 
89
90
91
...
96
97
98
 
 
99
100
101
...
103
104
105
106
107
108
109
110
111
...
173
174
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
177
178
...
12
13
14
15
16
17
18
...
29
30
31
32
33
34
35
36
37
...
57
58
59
60
61
62
63
64
65
66
67
68
69
70
...
89
90
91
 
92
93
94
95
96
97
...
100
101
102
103
104
105
106
107
108
...
113
114
115
116
117
118
119
120
...
122
123
124
 
 
 
125
126
127
...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
0
@@ -12,6 +12,7 @@ require 'god/logger'
0
 require 'god/system/process'
0
 require 'god/dependency_graph'
0
 require 'god/timeline'
0
+require 'god/configurable'
0
 
0
 require 'god/behavior'
0
 require 'god/behaviors/clean_pid_file'
0
@@ -28,6 +29,9 @@ require 'god/conditions/lambda'
0
 require 'god/conditions/degrading_lambda'
0
 require 'god/conditions/flapping'
0
 
0
+require 'god/contact'
0
+require 'god/contacts/email'
0
+
0
 require 'god/reporter'
0
 require 'god/server'
0
 require 'god/timer'
0
@@ -53,6 +57,14 @@ end
0
 
0
 God::EventHandler.load
0
 
0
+module Kernel
0
+ # Override abort to exit without executing the at_exit hook
0
+ def abort(text)
0
+ puts text
0
+ exit!
0
+ end
0
+end
0
+
0
 module God
0
   VERSION = '0.4.0'
0
   
0
@@ -77,7 +89,9 @@ module God
0
                   :pending_watches,
0
                   :server,
0
                   :watches,
0
- :groups
0
+ :groups,
0
+ :contacts,
0
+ :contact_groups
0
   end
0
   
0
   def self.init
0
@@ -86,6 +100,9 @@ module God
0
     end
0
     
0
     self.internal_init
0
+
0
+ # yield to the config file
0
+ yield self if block_given?
0
   end
0
   
0
   def self.internal_init
0
@@ -96,6 +113,8 @@ module God
0
     self.watches = {}
0
     self.groups = {}
0
     self.pending_watches = []
0
+ self.contacts = {}
0
+ self.contact_groups = {}
0
     
0
     # set defaults
0
     self.log_buffer_size = LOG_BUFFER_SIZE_DEFAULT
0
@@ -103,9 +122,6 @@ module God
0
     self.port = DRB_PORT_DEFAULT
0
     self.allow = DRB_ALLOW_DEFAULT
0
     
0
- # yield to the config file
0
- yield self if block_given?
0
-
0
     # init has been executed
0
     self.inited = true
0
     
0
@@ -173,6 +189,48 @@ module God
0
       self.groups[watch.group].delete(watch)
0
     end
0
   end
0
+
0
+ def self.contact(kind)
0
+ self.internal_init
0
+
0
+ # create the condition
0
+ begin
0
+ c = Contact.generate(kind)
0
+ rescue NoSuchContactError => e
0
+ abort e.message
0
+ end
0
+
0
+ # send to block so config can set attributes
0
+ yield(c) if block_given?
0
+
0
+ # call prepare on the contact
0
+ c.prepare
0
+
0
+ # ensure the new contact has a unique name
0
+ if self.contacts[c.name] || self.contact_groups[c.name]
0
+ abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
0
+ end
0
+
0
+ # abort if the Contact is invalid, the Contact will have printed
0
+ # out its own error messages by now
0
+ unless Contact.valid?(c) && c.valid?
0
+ abort "Exiting on invalid contact"
0
+ end
0
+
0
+ # add to list of contacts
0
+ self.contacts[c.name] = c
0
+
0
+ # add to contact group if specified
0
+ if c.group
0
+ # ensure group name hasn't been used for a contact already
0
+ if self.contacts[c.group]
0
+ abort "Contact Group name '#{c.group}' already used for a Contact"
0
+ end
0
+
0
+ self.contact_groups[c.group] ||= []
0
+ self.contact_groups[c.group] << c
0
+ end
0
+ end
0
     
0
   def self.control(name, command)
0
     # get the list of watches
...
1
2
3
 
 
4
5
6
...
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
43
44
45
...
60
61
62
63
64
65
66
67
68
 
 
 
 
 
69
70
71
...
1
2
3
4
5
6
7
8
...
16
17
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
20
21
22
...
37
38
39
 
 
 
 
 
 
40
41
42
43
44
45
46
47
0
@@ -1,6 +1,8 @@
0
 module God
0
   
0
   class Behavior
0
+ include Configurable
0
+
0
     attr_accessor :watch
0
     
0
     # Generate a Behavior of the given kind. The proper class if found by camel casing the
0
@@ -14,32 +16,7 @@ module God
0
     rescue NameError
0
       raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}")
0
     end
0
-
0
- # Override this method in your Behaviors (optional)
0
- #
0
- # Called once after the Condition has been sent to the block and attributes have been
0
- # set. Do any post-processing on attributes here
0
- def prepare
0
-
0
- end
0
-
0
- # Override this method in your Behaviors (optional)
0
- #
0
- # Called once during evaluation of the config file. Return true if valid, false otherwise
0
- #
0
- # A convenience method 'complain' is available that will print out a message and return false,
0
- # making it easy to report multiple validation errors:
0
- #
0
- # def valid?
0
- # valid = true
0
- # valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
0
- # valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
0
- # valid
0
- # end
0
- def valid?
0
- true
0
- end
0
-
0
+
0
     #######
0
     
0
     def before_start
0
@@ -60,12 +37,11 @@ module God
0
     def after_stop
0
     end
0
     
0
- protected
0
-
0
- def complain(text)
0
- Syslog.err(text)
0
- puts text
0
- false
0
+ # Construct the friendly name of this Behavior, looks like:
0
+ #
0
+ # Behavior FooBar on Watch 'baz'
0
+ def friendly_name
0
+ "Behavior " + super + " on Watch '#{self.watch.name}'"
0
     end
0
   end
0
   
...
4
5
6
7
 
8
9
10
...
4
5
6
 
7
8
9
10
0
@@ -4,7 +4,7 @@ module God
0
     class CleanPidFile < Behavior
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'pid_file' attribute on the Watch for :clean_pid_file") if self.watch.pid_file.nil?
0
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
0
         valid
0
       end
0
   
...
13
14
15
16
17
18
 
 
 
19
20
21
22
 
23
24
25
...
13
14
15
 
 
 
16
17
18
19
20
21
 
22
23
24
25
0
@@ -13,13 +13,13 @@ module God
0
       
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'failures' attribute for :notify_when_flapping") unless self.failures
0
- valid &= complain("You must specify the 'seconds' attribute for :notify_when_flapping") unless self.seconds
0
- valid &= complain("You must specify the 'notifier' attribute for :notify_when_flapping") unless self.notifier
0
+ valid &= complain("Attribute 'failures' must be specified", self) unless self.failures
0
+ valid &= complain("Attribute 'seconds' must be specified", self) unless self.seconds
0
+ valid &= complain("Attribute 'notifier' must be specified", self) unless self.notifier
0
                 
0
         # Must take one arg or variable args
0
         unless self.notifier.respond_to?(:notify) and [1,-1].include?(self.notifier.method(:notify).arity)
0
- valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args")
0
+ valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args", self)
0
         end
0
         
0
         valid
...
1
2
3
4
 
5
6
7
8
 
9
10
11
12
13
14
 
15
16
17
...
19
20
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
23
24
...
1
2
3
 
4
5
6
7
 
8
9
10
11
12
13
 
14
15
16
17
...
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
0
@@ -1,17 +1,17 @@
0
 module God
0
   
0
   class Condition < Behavior
0
- attr_accessor :transition
0
+ attr_accessor :transition, :notify
0
     
0
     # Generate a Condition of the given kind. The proper class if found by camel casing the
0
     # kind (which is given as an underscored symbol).
0
- # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Conditions::FooBar)
0
+ # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar)
0
     def self.generate(kind, watch)
0
       sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
0
       c = God::Conditions.const_get(sym).new
0
       
0
       unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition)
0
- abort "Condition '#{c.class.name}' must subclass either God::PollCondition or God::EventCondition"
0
+ abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition"
0
       end
0
       
0
       c.watch = watch
0
@@ -19,6 +19,25 @@ module God
0
     rescue NameError
0
       raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
0
     end
0
+
0
+ def self.valid?(condition)
0
+ valid = true
0
+ if condition.notify
0
+ begin
0
+ Contact.normalize(condition.notify)
0
+ rescue ArgumentError => e
0
+ valid &= Configurable.complain("Attribute 'notify' " + e.message, condition)
0
+ end
0
+ end
0
+ valid
0
+ end
0
+
0
+ # Construct the friendly name of this Condition, looks like:
0
+ #
0
+ # Condition FooBar on Watch 'baz'
0
+ def friendly_name
0
+ "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'"
0
+ end
0
   end
0
   
0
   class PollCondition < Condition
...
6
7
8
9
 
10
11
12
...
6
7
8
 
9
10
11
12
0
@@ -6,7 +6,7 @@ module God
0
       
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'what' attribute for :always") if self.what.nil?
0
+ valid &= complain("Attribute 'what' must be specified", self) if self.what.nil?
0
         valid
0
       end
0
       
...
20
21
22
23
24
 
 
25
26
27
...
20
21
22
 
 
23
24
25
26
27
0
@@ -20,8 +20,8 @@ module God
0
     
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
0
- valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
0
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
0
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
0
         valid
0
       end
0
     
...
12
13
14
15
 
16
17
18
...
12
13
14
 
15
16
17
18
0
@@ -12,7 +12,7 @@ module God
0
       
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'lambda' attribute for :degrading_lambda") if self.lambda.nil?
0
+ valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil?
0
         valid
0
       end
0
 
...
11
12
13
14
15
16
 
 
 
17
18
19
...
11
12
13
 
 
 
14
15
16
17
18
19
0
@@ -11,9 +11,9 @@ module God
0
       
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'times' attribute for :flapping") if self.times.nil?
0
- valid &= complain("You must specify the 'within' attribute for :flapping") if self.within.nil?
0
- valid &= complain("You must specify either the 'from_state', 'to_state', or both attributes for :flapping") if self.from_state.nil? && self.to_state.nil?
0
+ valid &= complain("Attribute 'times' must be specified", self) if self.times.nil?
0
+ valid &= complain("Attribute 'within' must be specified", self) if self.within.nil?
0
+ valid &= complain("Attributes 'from_state', 'to_state', or both must be specified", self) if self.from_state.nil? && self.to_state.nil?
0
         valid
0
       end
0
       
...
6
7
8
9
 
10
11
12
...
6
7
8
 
9
10
11
12
0
@@ -6,7 +6,7 @@ module God
0
 
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'lambda' attribute for :lambda") if self.lambda.nil?
0
+ valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil?
0
         valid
0
       end
0
 
...
20
21
22
23
24
 
 
25
26
27
...
20
21
22
 
 
23
24
25
26
27
0
@@ -20,8 +20,8 @@ module God
0
     
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
0
- valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
0
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
0
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
0
         valid
0
       end
0
     
...
4
5
6
7
 
8
9
10
...
4
5
6
 
7
8
9
10
0
@@ -4,7 +4,7 @@ module God
0
     class ProcessExits < EventCondition
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_exits") if self.watch.pid_file.nil?
0
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
0
         valid
0
       end
0
     
...
6
7
8
9
10
 
 
11
12
13
...
6
7
8
 
 
9
10
11
12
13
0
@@ -6,8 +6,8 @@ module God
0
       
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_running") if self.watch.pid_file.nil?
0
- valid &= complain("You must specify the 'running' attribute for :process_running") if self.running.nil?
0
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
0
+ valid &= complain("Attribute 'running' must be specified", self) if self.running.nil?
0
         valid
0
       end
0
     
...
10
11
12
13
 
14
15
16
...
10
11
12
 
13
14
15
16
0
@@ -10,7 +10,7 @@ module God
0
     
0
       def valid?
0
         valid = true
0
- valid &= complain("You must specify the 'times' attribute for :tries") if self.times.nil?
0
+ valid &= complain("Attribute 'times' must be specified", self) if self.times.nil?
0
         valid
0
       end
0
     
...
12
13
14
 
 
 
15
16
17
...
12
13
14
15
16
17
18
19
20
0
@@ -12,6 +12,9 @@ module God
0
   class NoSuchBehaviorError < StandardError
0
   end
0
   
0
+ class NoSuchContactError < StandardError
0
+ end
0
+
0
   class InvalidCommandError < StandardError
0
   end
0
   
...
59
60
61
 
 
 
 
 
62
63
64
...
106
107
108
109
110
 
 
111
112
113
114
 
 
 
 
 
 
115
116
117
...
131
132
133
134
 
135
136
137
...
143
144
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
147
148
149
...
59
60
61
62
63
64
65
66
67
68
69
...
111
112
113
 
 
114
115
116
117
118
 
119
120
121
122
123
124
125
126
127
...
141
142
143
 
144
145
146
147
...
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
0
@@ -59,6 +59,11 @@ module God
0
               Syslog.debug(msg)
0
               LOG.log(watch, :info, msg)
0
               
0
+ # notify
0
+ if condition.notify
0
+ self.notify(condition, msg)
0
+ end
0
+
0
               # after-condition
0
               condition.after
0
               
0
@@ -106,12 +111,17 @@ module God
0
         
0
         unless metric.nil?
0
           watch = metric.watch
0
-
0
- watch.mutex.synchronize do
0
+
0
+ watch.mutex.synchronize do
0
             msg = watch.name + ' ' + condition.class.name + " [true] " + self.dest_desc(metric, condition)
0
             Syslog.debug(msg)
0
             LOG.log(watch, :info, msg)
0
-
0
+
0
+ # notify
0
+ if condition.notify
0
+ self.notify(condition, msg)
0
+ end
0
+
0
             # get the destination
0
             dest =
0
             if condition.transition
0
@@ -131,7 +141,7 @@ module God
0
     end
0
     
0
     # helpers
0
-
0
+
0
     def self.dest_desc(metric, condition)
0
       if metric.destination
0
         metric.destination.inspect
0
@@ -143,6 +153,28 @@ module God
0
         end
0
       end
0
     end
0
+
0
+ def self.notify(condition, message)
0
+ begin
0
+ spec = Contact.normalize(condition.notify)
0
+
0
+ # resolve contacts
0
+ resolved_contacts =
0
+ spec[:contacts].inject([]) do |acc, contact_name_or_group|
0
+ acc += Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group])
0
+ acc
0
+ end
0
+
0
+ p resolved_contacts
0
+
0
+ resolved_contacts.each do |c|
0
+ c.notify(message, Time.now, spec[:priority], spec[:category])
0
+ end
0
+ rescue => e
0
+ puts e.message
0
+ puts e.backtrace.join("\n")
0
+ end
0
+ end
0
   end
0
   
0
 end
0
\ No newline at end of file
...
25
26
27
28
29
30
 
 
31
32
33
...
25
26
27
 
 
 
28
29
30
31
32
0
@@ -25,9 +25,8 @@ module God
0
       # call prepare on the condition
0
       c.prepare
0
       
0
- # abort if the Condition is invalid, the Condition will have printed
0
- # out its own error messages by now
0
- unless c.valid?
0
+ # test generic and specific validity
0
+ unless Condition.valid?(c) && c.valid?
0
         abort "Exiting on invalid condition"
0
       end
0
       
...
95
96
97
 
 
 
 
 
 
98
99
100
...
95
96
97
98
99
100
101
102
103
104
105
106
0
@@ -95,6 +95,12 @@ def no_stderr
0
   $stderr.reopen(old_stderr)
0
 end
0
 
0
+module Kernel
0
+ def abort(text)
0
+ raise SystemExit
0
+ end
0
+end
0
+
0
 module Test::Unit::Assertions
0
   def assert_abort
0
     assert_raise SystemExit do

Comments

    No one has commented yet.