default stop for auto-daemonized; bug fixes
[god.git] / lib / god / watch.rb
blob34a4e3a119ad91c1b3fff40d8e1024202d4758fe
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=
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       # no grace period by default
34       self.grace = self.start_grace = self.stop_grace = self.restart_grace = 0
35       
36       # the list of behaviors
37       self.behaviors = []
38       
39       # the list of conditions for each action
40       self.metrics = {:init => [],
41                       :start => [],
42                       :restart => [],
43                       :up => []}
44                          
45       # mutex
46       self.mutex = Mutex.new
47     end
48     
49     def valid?
50       @process.valid?
51     end
52     
53     ###########################################################################
54     #
55     # Behavior
56     #
57     ###########################################################################
58     
59     def behavior(kind)
60       # create the behavior
61       begin
62         b = Behavior.generate(kind, self)
63       rescue NoSuchBehaviorError => e
64         abort e.message
65       end
66       
67       # send to block so config can set attributes
68       yield(b) if block_given?
69       
70       # abort if the Behavior is invalid, the Behavior will have printed
71       # out its own error messages by now
72       abort unless b.valid?
73       
74       self.behaviors << b
75     end
76     
77     ###########################################################################
78     #
79     # Advanced mode
80     #
81     ###########################################################################
82     
83     # Define a transition handler which consists of a set of conditions
84     def transition(start_states, end_states)
85       # convert to into canonical hash form
86       canonical_end_states = canonical_hash_form(end_states)
87       
88       # for each start state do
89       Array(start_states).each do |start_state|
90         # validate start state
91         unless VALID_STATES.include?(start_state)
92           abort "Invalid state :#{start_state}. Must be one of the symbols #{VALID_STATES.map{|x| ":#{x}"}.join(', ')}"
93         end
94         
95         # create a new metric to hold the watch, end states, and conditions
96         m = Metric.new(self, canonical_end_states)
97         
98         # let the config file define some conditions on the metric
99         yield(m)
100         
101         # record the metric
102         self.metrics[start_state] << m
103       end
104     end
105     
106     ###########################################################################
107     #
108     # Simple mode
109     #
110     ###########################################################################
111     
112     def start_if
113       self.transition(:up, :start) do |on|
114         yield(on)
115       end
116     end
117     
118     def restart_if
119       self.transition(:up, :restart) do |on|
120         yield(on)
121       end
122     end
123     
124     ###########################################################################
125     #
126     # Lifecycle
127     #
128     ###########################################################################
129         
130     # Enable monitoring
131     def monitor
132       # start monitoring at the first available of the init or up states
133       if !self.metrics[:init].empty?
134         self.move(:init)
135       else
136         self.move(:up)
137       end
138     end
139     
140     # Disable monitoring
141     def unmonitor
142       self.move(nil)
143     end
144     
145     # Move from one state to another
146     def move(to_state)
147       msg = "#{self.name} move '#{self.state}' to '#{to_state}'"
148       Syslog.debug(msg)
149       puts msg
150        
151       # cleanup from current state
152       from_state = self.state
153       if from_state
154         self.metrics[from_state].each { |m| m.disable }
155       end
156       
157       # perform action
158       self.action(to_state)
159       
160       # enable simple mode
161       if [:start, :restart].include?(to_state) && self.metrics[to_state].empty?
162         to_state = :up
163       end
164       
165       # move to new state
166       if to_state
167         self.metrics[to_state].each { |m| m.enable }
168       end
169       
170       # set state
171       self.state = to_state
172       
173       # return self
174       self
175     end
176     
177     def action(a, c = nil)
178       case a
179       when :start
180         msg = "#{self.name} start: #{self.start.to_s}"
181         Syslog.debug(msg)
182         puts msg
183         call_action(c, :start)
184         sleep(self.start_grace + self.grace)
185       when :restart
186         if self.restart
187           msg = "#{self.name} restart: #{self.restart.to_s}"
188           Syslog.debug(msg)
189           puts msg
190           call_action(c, :restart)
191         else
192           action(:stop, c)
193           action(:start, c)
194         end
195         sleep(self.restart_grace + self.grace)
196       when :stop
197         if self.stop
198           msg = "#{self.name} stop: #{self.stop.to_s}"
199           Syslog.debug(msg)
200           puts msg
201         end
202         call_action(c, :stop)
203         sleep(self.stop_grace + self.grace)
204       end      
205     end
206     
207     def call_action(condition, action)
208       # before
209       before_items = self.behaviors
210       before_items += [condition] if condition
211       before_items.each { |b| b.send("before_#{action}") }
212       
213       @process.call_action(action)
215       # after
216       after_items = self.behaviors
217       after_items += [condition] if condition
218       after_items.each { |b| b.send("after_#{action}") }
219     end
220     
221     def canonical_hash_form(to)
222       to.instance_of?(Symbol) ? {true => to} : to
223     end
224     
225     def register!
226       God.registry.add(@process)
227     end
228     
229     def unregister!
230       God.registry.remove(@process)
231     end
232   end
233