prevent carry-over conditions
[god.git] / lib / god / task.rb
blob9eba751f0e2b4990a43c5fca16f9df105dbabe19
1 module God
2   
3   class Task
4     attr_accessor :name, :interval, :group, :valid_states, :initial_state, :phase
5     
6     attr_writer   :autostart
7     def autostart?; @autostart; end
8     
9     # api
10     attr_accessor :state, :behaviors, :metrics
11     
12     # internal
13     attr_accessor :mutex
14     
15     def initialize
16       @autostart ||= true
17       
18       # initial state is unmonitored
19       self.state = :unmonitored
20       
21       # the list of behaviors
22       self.behaviors = []
23       
24       # the list of conditions for each action
25       self.metrics = {nil => [], :unmonitored => []}
26       
27       # mutex
28       self.mutex = Monitor.new
29     end
30     
31     def prepare
32       self.valid_states.each do |state|
33         self.metrics[state] ||= []
34       end
35     end
36     
37     def valid?
38       valid = true
39       
40       # a name must be specified
41       if self.name.nil?
42         valid = false
43         applog(self, :error, "No name was specified")
44       end
45       
46       # valid_states must be specified
47       if self.valid_states.nil?
48         valid = false
49         applog(self, :error, "No valid_states array was specified")
50       end
51       
52       # valid_states must be specified
53       if self.initial_state.nil?
54         valid = false
55         applog(self, :error, "No initial_state was specified")
56       end
57       
58       valid
59     end
60     
61     ###########################################################################
62     #
63     # Advanced mode
64     #
65     ###########################################################################
66     
67     def canonical_hash_form(to)
68       to.instance_of?(Symbol) ? {true => to} : to
69     end
70     
71     # Define a transition handler which consists of a set of conditions
72     def transition(start_states, end_states)
73       # convert end_states into canonical hash form
74       canonical_end_states = canonical_hash_form(end_states)
75       
76       Array(start_states).each do |start_state|
77         # validate start state
78         unless self.valid_states.include?(start_state)
79           abort "Invalid state :#{start_state}. Must be one of the symbols #{self.valid_states.map{|x| ":#{x}"}.join(', ')}"
80         end
81         
82         # create a new metric to hold the watch, end states, and conditions
83         m = Metric.new(self, canonical_end_states)
84         
85         if block_given?
86           # let the config file define some conditions on the metric
87           yield(m)
88         else
89           # add an :always condition if no block
90           m.condition(:always) do |c|
91             c.what = true
92           end
93         end
94         
95         # record the metric
96         self.metrics[start_state] ||= []
97         self.metrics[start_state] << m
98       end
99     end
100     
101     def lifecycle
102       # create a new metric to hold the watch and conditions
103       m = Metric.new(self)
104       
105       # let the config file define some conditions on the metric
106       yield(m)
107       
108       # record the metric
109       self.metrics[nil] << m
110     end
111     
112     ###########################################################################
113     #
114     # Lifecycle
115     #
116     ###########################################################################
117         
118     # Enable monitoring
119     def monitor
120       self.move(self.initial_state)
121     end
122     
123     # Disable monitoring
124     def unmonitor
125       self.move(:unmonitored)
126     end
127     
128     # Move from one state to another
129     def move(to_state)
130       self.mutex.synchronize do
131         # set the phase for this move
132         self.phase = Time.now
133         
134         orig_to_state = to_state
135         from_state = self.state
136         
137         msg = "#{self.name} move '#{from_state}' to '#{to_state}'"
138         applog(self, :info, msg)
139         
140         # cleanup from current state
141         self.metrics[from_state].each { |m| m.disable }
142         
143         if to_state == :unmonitored
144           self.metrics[nil].each { |m| m.disable }
145         end
146         
147         # perform action
148         self.action(to_state)
149         
150         # enable simple mode
151         if [:start, :restart].include?(to_state) && self.metrics[to_state].empty?
152           to_state = :up
153         end
154         
155         # move to new state
156         self.metrics[to_state].each { |m| m.enable }
157         
158         # if no from state, enable lifecycle metric
159         if from_state == :unmonitored
160           self.metrics[nil].each { |m| m.enable }
161         end
162         
163         # set state
164         self.state = to_state
165         
166         # trigger
167         Trigger.broadcast(self, :state_change, [from_state, orig_to_state])
168         
169         msg = "#{self.name} moved '#{from_state}' to '#{to_state}'"
170         applog(self, :info, msg)
171         
172         # return self
173         self
174       end
175     end
176     
177     ###########################################################################
178     #
179     # Actions
180     #
181     ###########################################################################
182     
183     def method_missing(sym, *args)
184       unless (sym.to_s =~ /=$/)
185         super
186       end
187       
188       base = sym.to_s.chop.intern
189       
190       unless self.valid_states.include?(base)
191         super
192       end
193       
194       self.class.send(:attr_accessor, base)
195       self.send(sym, *args)
196     end
197     
198     #   +a+ is the action Symbol
199     #   +c+ is the Condition
200     def action(a, c = nil)
201       if self.respond_to?(a)
202         command = self.send(a)
203         
204         case command
205           when String
206             msg = "#{self.name} #{a}: #{command}"
207             applog(self, :info, msg)
208             
209             system(command)
210           when Proc
211             msg = "#{self.name} #{a}: lambda"
212             applog(self, :info, msg)
213             
214             command.call
215           else
216             raise NotImplementedError
217         end
218       end
219     end
220     
221     ###########################################################################
222     #
223     # Registration
224     #
225     ###########################################################################
226     
227     def register!
228       # override if necessary
229     end
230     
231     def unregister!
232       # override if necessary
233     end
234   end
235