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