finish lifecycle conditions handling and add flapper condition
[god.git] / lib / god / watch.rb
blobf9f67e2d0e6c185711b8b11204b842853736ddab
1 require 'etc'
2 require 'forwardable'
4 module God
5   
6   class Watch
7     VALID_STATES = [:init, :up, :start, :restart]
8     
9     # config
10     attr_accessor :state, :interval, :group,
11                   :grace, :start_grace, :stop_grace, :restart_grace
12     
13     
14     attr_writer   :autostart
15     def autostart?; @autostart; end
16     
17     extend Forwardable
18     def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart,
19                               :name=, :uid=, :gid=, :start=, :stop=, :restart=,
20                               :pid_file, :pid_file=, :log, :log=, :alive?
21     
22     # api
23     attr_accessor :behaviors, :metrics
24     
25     # internal
26     attr_accessor :mutex
27     
28     # 
29     def initialize
30       @autostart ||= true
31       @process = God::Process.new
32       
33       # initial state is unmonitored
34       self.state = :unmonitored
35       
36       # no grace period by default
37       self.grace = self.start_grace = self.stop_grace = self.restart_grace = 0
38       
39       # the list of behaviors
40       self.behaviors = []
41       
42       # the list of conditions for each action
43       self.metrics = {nil => [],
44                       :unmonitored => [],
45                       :init => [],
46                       :start => [],
47                       :restart => [],
48                       :up => []}
49       
50       # mutex
51       self.mutex = Mutex.new
52     end
53     
54     def valid?
55       @process.valid?
56     end
57     
58     ###########################################################################
59     #
60     # Behavior
61     #
62     ###########################################################################
63     
64     def behavior(kind)
65       # create the behavior
66       begin
67         b = Behavior.generate(kind, self)
68       rescue NoSuchBehaviorError => e
69         abort e.message
70       end
71       
72       # send to block so config can set attributes
73       yield(b) if block_given?
74       
75       # abort if the Behavior is invalid, the Behavior will have printed
76       # out its own error messages by now
77       abort unless b.valid?
78       
79       self.behaviors << b
80     end
81     
82     ###########################################################################
83     #
84     # Advanced mode
85     #
86     ###########################################################################
87     
88     # Define a transition handler which consists of a set of conditions
89     def transition(start_states, end_states)
90       # convert end_states into canonical hash form
91       canonical_end_states = canonical_hash_form(end_states)
92       
93       Array(start_states).each do |start_state|
94         # validate start state
95         unless VALID_STATES.include?(start_state)
96           abort "Invalid state :#{start_state}. Must be one of the symbols #{VALID_STATES.map{|x| ":#{x}"}.join(', ')}"
97         end
98         
99         # create a new metric to hold the watch, end states, and conditions
100         m = Metric.new(self, canonical_end_states)
101         
102         # let the config file define some conditions on the metric
103         yield(m)
104         
105         # record the metric
106         self.metrics[start_state] << m
107       end
108     end
109     
110     def lifecycle
111       # create a new metric to hold the watch and conditions
112       m = Metric.new(self)
113       
114       # let the config file define some conditions on the metric
115       yield(m)
116       
117       # record the metric
118       self.metrics[nil] << m
119     end
120     
121     ###########################################################################
122     #
123     # Simple mode
124     #
125     ###########################################################################
126     
127     def start_if
128       self.transition(:up, :start) do |on|
129         yield(on)
130       end
131     end
132     
133     def restart_if
134       self.transition(:up, :restart) do |on|
135         yield(on)
136       end
137     end
138     
139     ###########################################################################
140     #
141     # Lifecycle
142     #
143     ###########################################################################
144         
145     # Enable monitoring
146     def monitor
147       # start monitoring at the first available of the init or up states
148       if !self.metrics[:init].empty?
149         self.move(:init)
150       else
151         self.move(:up)
152       end
153     end
154     
155     # Disable monitoring
156     def unmonitor
157       self.move(:unmonitored)
158     end
159     
160     # Move from one state to another
161     def move(to_state)
162       from_state = self.state
163       
164       msg = "#{self.name} move '#{from_state}' to '#{to_state}'"
165       Syslog.debug(msg)
166       LOG.log(self, :info, msg)
167       
168       # cleanup from current state
169       self.metrics[from_state].each { |m| m.disable }
170       
171       if to_state == :unmonitored
172         self.metrics[nil].each { |m| m.disable }
173       end
174       
175       # perform action
176       self.action(to_state)
177       
178       # enable simple mode
179       if [:start, :restart].include?(to_state) && self.metrics[to_state].empty?
180         to_state = :up
181       end
182       
183       # move to new state
184       self.metrics[to_state].each { |m| m.enable }
185       
186       # if no from state, enable lifecycle metric
187       if from_state == :unmonitored
188         self.metrics[nil].each { |m| m.enable }
189       end
190       
191       # set state
192       self.state = to_state
193       
194       # trigger
195       Trigger.broadcast(:state_change, [from_state, to_state])
196       
197       # return self
198       self
199     end
200     
201     def action(a, c = nil)
202       case a
203       when :start
204         msg = "#{self.name} start: #{self.start.to_s}"
205         Syslog.debug(msg)
206         LOG.log(self, :info, msg)
207         call_action(c, :start)
208         sleep(self.start_grace + self.grace)
209       when :restart
210         if self.restart
211           msg = "#{self.name} restart: #{self.restart.to_s}"
212           Syslog.debug(msg)
213           LOG.log(self, :info, msg)
214           call_action(c, :restart)
215         else
216           action(:stop, c)
217           action(:start, c)
218         end
219         sleep(self.restart_grace + self.grace)
220       when :stop
221         if self.stop
222           msg = "#{self.name} stop: #{self.stop.to_s}"
223           Syslog.debug(msg)
224           LOG.log(self, :info, msg)
225         end
226         call_action(c, :stop)
227         sleep(self.stop_grace + self.grace)
228       end      
229     end
230     
231     def call_action(condition, action)
232       # before
233       before_items = self.behaviors
234       before_items += [condition] if condition
235       before_items.each { |b| b.send("before_#{action}") }
236       
237       @process.call_action(action)
239       # after
240       after_items = self.behaviors
241       after_items += [condition] if condition
242       after_items.each { |b| b.send("after_#{action}") }
243     end
244     
245     def canonical_hash_form(to)
246       to.instance_of?(Symbol) ? {true => to} : to
247     end
248     
249     def register!
250       God.registry.add(@process)
251     end
252     
253     def unregister!
254       God.registry.remove(@process)
255     end
256   end
257