add notification system
[god.git] / lib / god / hub.rb
blob3915d4d7c9c78d968d1201eeb094b96c24a46e67
1 module God
2   
3   class Hub
4     class << self
5       # directory to hold conditions and their corresponding metric
6       # {condition => metric}
7       attr_accessor :directory
8     end
9     
10     self.directory = {}
11     
12     def self.attach(condition, metric)
13       self.directory[condition] = metric
14       
15       case condition
16         when PollCondition
17           Timer.get.schedule(condition, 0)
18         when EventCondition, TriggerCondition
19           condition.register
20       end
21     end
22     
23     def self.detach(condition)
24       self.directory.delete(condition)
25       
26       case condition
27         when PollCondition
28           Timer.get.unschedule(condition)
29         when EventCondition, TriggerCondition
30           condition.deregister
31       end
32     end
33     
34     def self.trigger(condition)
35       case condition
36         when PollCondition
37           self.handle_poll(condition)
38         when EventCondition, TriggerCondition
39           self.handle_event(condition)
40       end
41     end
42     
43     def self.handle_poll(condition)
44       Thread.new do
45         begin
46           metric = self.directory[condition]
47           
48           # it's possible that the timer will trigger an event before it can be cleared
49           # by an exiting metric, in which case it should be ignored
50           unless metric.nil?
51             watch = metric.watch
52             
53             watch.mutex.synchronize do
54               # run the test
55               result = condition.test
56               
57               # log
58               msg = watch.name + ' ' + condition.class.name + " [#{result}] " + self.dest_desc(metric, condition)
59               Syslog.debug(msg)
60               LOG.log(watch, :info, msg)
61               
62               # notify
63               if condition.notify
64                 self.notify(condition, msg)
65               end
66               
67               # after-condition
68               condition.after
69               
70               # get the destination
71               dest = 
72               if result && condition.transition
73                 # condition override
74                 condition.transition
75               else
76                 # regular
77                 metric.destination && metric.destination[result]
78               end
79               
80               # transition or reschedule
81               if dest
82                 # transition
83                 begin
84                   watch.move(dest)
85                 rescue EventRegistrationFailedError
86                   msg = watch.name + ' Event registration failed, moving back to previous state'
87                   Syslog.debug(msg)
88                   LOG.log(watch, :info, msg)
89                   
90                   dest = watch.state
91                   retry
92                 end
93               else
94                 # reschedule
95                 Timer.get.schedule(condition)
96               end
97             end
98           end
99         rescue => e
100           message = format("Unhandled exception (%s): %s\n%s",
101                            e.class, e.message, e.backtrace.join("\n"))
102           Syslog.crit message
103           abort message
104         end
105       end
106     end
107     
108     def self.handle_event(condition)
109       Thread.new do
110         metric = self.directory[condition]
111         
112         unless metric.nil?
113           watch = metric.watch
114           
115           watch.mutex.synchronize do
116             msg = watch.name + ' ' + condition.class.name + " [true] " + self.dest_desc(metric, condition)
117             Syslog.debug(msg)
118             LOG.log(watch, :info, msg)
119             
120             # notify
121             if condition.notify
122               self.notify(condition, msg)
123             end
124             
125             # get the destination
126             dest = 
127             if condition.transition
128               # condition override
129               condition.transition
130             else
131               # regular
132               metric.destination && metric.destination[true]
133             end
134             
135             if dest
136               watch.move(dest)
137             end
138           end
139         end
140       end
141     end
142     
143     # helpers
144     
145     def self.dest_desc(metric, condition)
146       if metric.destination
147         metric.destination.inspect
148       else
149         if condition.transition
150           {true => condition.transition}.inspect
151         else
152           'none'
153         end
154       end
155     end
156     
157     def self.notify(condition, message)
158       begin
159         spec = Contact.normalize(condition.notify)
160       
161         # resolve contacts
162         resolved_contacts =
163         spec[:contacts].inject([]) do |acc, contact_name_or_group|
164           acc += Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group])
165           acc
166         end
167         
168         p resolved_contacts
169         
170         resolved_contacts.each do |c|
171           c.notify(message, Time.now, spec[:priority], spec[:category])
172         end
173       rescue => e
174         puts e.message
175         puts e.backtrace.join("\n")
176       end
177     end
178   end
179