change task mutex to monitor and synchronize move method
[god.git] / lib / god / task.rb
blob1d5d506ccb6510839f1cf345597aadac2571c352
1 module God
2   
3   class Task
4     attr_accessor :name, :interval, :group, :valid_states, :initial_state
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         LOG.log(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         LOG.log(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         LOG.log(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         orig_to_state = to_state
132         from_state = self.state
133         
134         msg = "#{self.name} move '#{from_state}' to '#{to_state}'"
135         Syslog.debug(msg)
136         LOG.log(self, :info, msg)
137         
138         # cleanup from current state
139         self.metrics[from_state].each { |m| m.disable }
140         
141         if to_state == :unmonitored
142           self.metrics[nil].each { |m| m.disable }
143         end
144         
145         # perform action
146         self.action(to_state)
147         
148         # enable simple mode
149         if [:start, :restart].include?(to_state) && self.metrics[to_state].empty?
150           to_state = :up
151         end
152         
153         # move to new state
154         self.metrics[to_state].each { |m| m.enable }
155         
156         # if no from state, enable lifecycle metric
157         if from_state == :unmonitored
158           self.metrics[nil].each { |m| m.enable }
159         end
160         
161         # set state
162         self.state = to_state
163         
164         # trigger
165         Trigger.broadcast(:state_change, [from_state, orig_to_state])
166         
167         # return self
168         self
169       end
170     end
171     
172     ###########################################################################
173     #
174     # Actions
175     #
176     ###########################################################################
177     
178     def method_missing(sym, *args)
179       unless (sym.to_s =~ /=$/)
180         super
181       end
182       
183       base = sym.to_s.chop.intern
184       
185       unless self.valid_states.include?(base)
186         super
187       end
188       
189       self.class.send(:attr_accessor, base)
190       self.send(sym, *args)
191     end
192     
193     #   +a+ is the action Symbol
194     #   +c+ is the Condition
195     def action(a, c = nil)
196       if self.respond_to?(a)
197         command = self.send(a)
198         
199         case command
200           when String
201             msg = "#{self.name} #{a}: #{command}"
202             Syslog.debug(msg)
203             LOG.log(self, :info, msg)
204             
205             system(command)
206           when Proc
207             msg = "#{self.name} #{a}: lambda"
208             Syslog.debug(msg)
209             LOG.log(self, :info, msg)
210             
211             command.call
212           else
213             raise NotImplementedError
214         end
215       end
216     end
217     
218     ###########################################################################
219     #
220     # Registration
221     #
222     ###########################################################################
223     
224     def register!
225       # override if necessary
226     end
227     
228     def unregister!
229       # override if necessary
230     end
231   end
232