implemented state based lifecycle
[god.git] / lib / god / watch.rb
blobfc69220718094b8773be61746c87fa11cb927986
1 module God
2   
3   class Watch < Base
4     VALID_STATES = [:init, :up, :start, :restart]
5     
6     # config
7     attr_accessor :name, :state, :start, :stop, :restart, :interval, :grace
8     
9     # api
10     attr_accessor :behaviors, :metrics
11     
12     # internal
13     attr_accessor :mutex
14     
15     # 
16     def initialize(meddle)
17       @meddle = meddle
18             
19       # no grace period by default
20       self.grace = 0
21       
22       # the list of behaviors
23       self.behaviors = []
24       
25       # the list of conditions for each action
26       self.metrics = {:init => [],
27                       :start => [],
28                       :restart => [],
29                       :up => []}
30                          
31       # mutex
32       self.mutex = Mutex.new
33     end
34     
35     def behavior(kind)
36       # create the behavior
37       begin
38         b = Behavior.generate(kind)
39       rescue NoSuchBehaviorError => e
40         abort e.message
41       end
42       
43       # send to block so config can set attributes
44       yield(b) if block_given?
45       
46       # abort if the Behavior is invalid, the Behavior will have printed
47       # out its own error messages by now
48       abort unless b.valid?
49       
50       self.behaviors << b
51     end
52     
53     ###########################################################################
54     #
55     # Advanced mode
56     #
57     ###########################################################################
58     
59     # Define a transition handler which consists of a set of conditions
60     def transition(start_states, end_states)
61       # convert to into canonical hash form
62       canonical_end_states = canonical_hash_form(end_states)
63       
64       # for each start state do
65       Array(start_states).each do |start_state|
66         # validate start state
67         unless VALID_STATES.include?(start_state)
68           abort "Invalid state :#{start_state}. Must be one of the symbols #{VALID_STATES.map{|x| ":#{x}"}.join(', ')}"
69         end
70         
71         # create a new metric to hold the watch, end states, and conditions
72         m = Metric.new(self, canonical_end_states)
73         
74         # let the config file define some conditions on the metric
75         yield(m)
76         
77         # record the metric
78         self.metrics[start_state] << m
79       end
80     end
81     
82     ###########################################################################
83     #
84     # Simple mode
85     #
86     ###########################################################################
87     
88     def start_if
89       self.transition(:up, :start) do |on|
90         yield(on)
91       end
92     end
93     
94     def restart_if
95       self.transition(:up, :restart) do |on|
96         yield(on)
97       end
98     end
99     
100     ###########################################################################
101     #
102     # Lifecycle
103     #
104     ###########################################################################
105         
106     # Schedule all poll conditions and register all condition events
107     def monitor
108       # start monitoring at the init state
109       self.move(:init)
110     end
111     
112     # Move from one state to another
113     def move(to_state)
114       puts "move '#{self.state}' to '#{to_state}'"
115        
116       # cleanup from current state
117       if from_state = self.state
118         self.metrics[from_state].each { |m| m.disable }
119       end
120       
121       # perform action (if available)
122       self.action(to_state)
123       
124       # move to new state
125       self.metrics[to_state].each { |m| m.enable }
126       
127       # set state
128       self.state = to_state
129     end
130     
131     def action(a, c = nil)
132       case a
133       when :start
134         puts self.start
135         call_action(c, :start, self.start)
136         sleep(self.grace)
137       when :restart
138         if self.restart
139           puts self.restart
140           call_action(c, :restart, self.restart)
141         else
142           action(:stop, c)
143           action(:start, c)
144         end
145         sleep(self.grace)
146       when :stop
147         puts self.stop
148         call_action(c, :stop, self.stop)
149         sleep(self.grace)
150       end      
151     end
152     
153     def call_action(condition, action, command)
154       # before
155       before_items = self.behaviors
156       before_items += [condition] if condition
157       before_items.each { |b| b.send("before_#{action}") }
158       
159       # action
160       if command.kind_of?(String)
161         # string command
162         system(command)
163       else
164         # lambda command
165         command.call
166       end
167       
168       # after
169       after_items = self.behaviors
170       after_items += [condition] if condition
171       after_items.each { |b| b.send("after_#{action}") }
172     end
173     
174     def canonical_hash_form(to)
175       to.instance_of?(Symbol) ? {true => to} : to
176     end
177   end
178