ant_hill 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,98 @@
1
+ module AntHill
2
+ # Exception
3
+ class NoFreeConnectionError < Exception; end
4
+ # Base class for storing connections
5
+ class ConnectionPool
6
+
7
+ # Attribute readers
8
+ # +creep+:: +Creep+ object
9
+ attr_reader :creep
10
+ include DRbUndumped
11
+
12
+ # Initialize
13
+ # +creep+:: creep for which we'll create connections
14
+ def initialize(creep)
15
+ @creep = creep
16
+ @connection_pool = []
17
+ end
18
+
19
+ # Execute command on creep
20
+ def exec(command)
21
+ conn = get_connection
22
+ if conn
23
+ begin
24
+ execute(conn, command)
25
+ rescue Timeout::Error => e
26
+ kill_connection(conn)
27
+ raise e
28
+ end
29
+ else
30
+ logger.error "Couldn't find any free connection or create new one"
31
+ raise NoFreeConnectionError
32
+ end
33
+ end
34
+
35
+ # Find free connection
36
+ def get_connection
37
+ @connection_pool.delete_if{ |connection| closed?(connection) }
38
+ connection = @connection_pool.find{|c| !c.busy?}
39
+ return connection if connection
40
+ new_conn = nil
41
+ begin
42
+ Timeout::timeout( 10 ) do
43
+ new_conn = get_new
44
+ end
45
+ rescue Timeout::Error => e
46
+ return nil
47
+ end
48
+ @connection_pool << new_conn if new_conn
49
+ new_conn
50
+ end
51
+
52
+ # Close all connections
53
+ def destroy
54
+ @connection_pool.each{|connection| kill_connection(connection)}
55
+ end
56
+
57
+ # logger object
58
+ def logger
59
+ creep.logger
60
+ end
61
+
62
+ # Check if connection is closed
63
+ # Should be redefined in child class
64
+ # +connection+:: connection to check
65
+ def closed?(connection)
66
+ raise "Should be implemented in child class"
67
+ end
68
+
69
+ # Check if connection is busy
70
+ # Should be redefined in child class
71
+ # +connection+:: connection to check
72
+ def busy?(connection)
73
+ raise "Should be implemented in child class"
74
+ end
75
+
76
+ # Kill connection
77
+ # Should be redefined in child class
78
+ # +connection+:: connection to kill
79
+ def kill_connection(connection)
80
+ raise "Should be implemented in child class"
81
+ end
82
+
83
+ # Establish new connection
84
+ # Should be redefined in child class
85
+ def get_new
86
+ raise "Should be implemented in child class"
87
+ end
88
+
89
+ # Execute command on connection
90
+ # Should be redefined in child class
91
+ # +connection+:: connection where execute command
92
+ # +command+:: cpmmad to execute
93
+ def execute(connection, command)
94
+ raise "Should be implemented in child class"
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,67 @@
1
+ require 'net/ssh'
2
+ module AntHill
3
+ # Child of ConnectionPool
4
+ class SSHConnection < ConnectionPool
5
+ include DRbUndumped
6
+
7
+ # Check if SSH connection is closed
8
+ # +connection+:: SSH connection to check
9
+ def closed?(connection)
10
+ return true if connection.closed?
11
+ begin
12
+ # Hack to check connection isn't dead
13
+ connection.exec!('true') unless connection.busy?
14
+ rescue Net::SSH::Exception, SystemCallError => e
15
+ return true
16
+ end
17
+ return false
18
+ end
19
+
20
+ # Check if SSH connection is busy
21
+ # +connection+:: SSH connection to check
22
+ def busy?(connection)
23
+ connection.busy?
24
+ end
25
+
26
+ # Get new SSH connection
27
+ def get_new
28
+ logger.debug "Establishing connection for #{creep.user}@#{creep.host} passwd:#{creep.password}"
29
+ ssh = Net::SSH.start(creep.host, creep.user, {:password => creep.password, :verbose => (ENV['SSH_DEBUG'] && ENV['SSH_DEBUG'].to_sym) || :fatal })
30
+ ssh.send_global_request("keepalive@openssh.com")
31
+ ssh
32
+ rescue Net::SSH::Exception => ex
33
+ logger.error "There was an exception in method get_new for SSConnection. Details #{ex}:\n#{ex.backtrace}"
34
+ return nil
35
+ rescue SystemCallError => ex
36
+ logger.error "There was an system error in method get_new for SSConnection. Details #{ex}:\n#{ex.backtrace}"
37
+ return nil
38
+ end
39
+
40
+ # Execute command on SSH connection
41
+ # +connection+:: SSH connection
42
+ # +command+:: command to execute
43
+ def execute(connection, command)
44
+ stdout = ""
45
+ stderr = ""
46
+ connection.exec!(command) do |ch, stream, data|
47
+ stderr << data if stream == :stderr
48
+ stdout << data if stream == :stdout
49
+ end
50
+ [stdout, stderr]
51
+ rescue Net::SSH::Exception => ex
52
+ logger.error "There was an exception in method execute for SSHConnection. Details #{ex}:\n#{ex.backtrace}"
53
+ kill_connection(connection)
54
+ raise NoFreeConnectionError
55
+ rescue SystemCallError => ex
56
+ logger.error "There was an system error in method get_new for SSConnection. Details #{ex}:\n#{ex.backtrace}"
57
+ kill_connection(connection)
58
+ raise NoFreeConnectionError
59
+ end
60
+
61
+ # Kill SSH connection
62
+ # +connection+:: SSH connection
63
+ def kill_connection(connection)
64
+ connection.shutdown!
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,301 @@
1
+ module AntHill
2
+ # Node object
3
+ class Creep
4
+
5
+ # +host+:: hostname of creep
6
+ # +user+:: username to connect
7
+ # +password+:: password to connect
8
+ # +status+:: status of creep
9
+ # +processed+:: number of ants processed
10
+ # +passed+:: number of successfully processed ants
11
+ # +start_time+:: status time
12
+ # +hill_cfg+:: creep configuration
13
+ # +current_ant+:: currently processing ant
14
+ attr_reader :host, :user, :password, :status, :processed, :passed, :start_time, :hill_cfg, :current_ant
15
+ # +active+:: node activness
16
+ # +custom_data+:: custom data hash
17
+ # +force_priority+:: If true recalculate priority for this creep instaed of taking cahced values
18
+ # +current_params+:: current creep configuration
19
+ # +name+:: creep name
20
+ attr_accessor :active, :custom_data, :force_priority, :current_params, :name
21
+ include DRbUndumped
22
+
23
+ #Initialize method
24
+ #[+queen+]:: queen obeject
25
+ #[+config+]:: configuration
26
+ def initialize(queen=Queen.queen, config=Configuration.config)
27
+ @config = config
28
+ @queen = queen
29
+ @current_params = {}
30
+ @custom_data = {}
31
+ @status = :wait
32
+ @current_ant = nil
33
+ @processed = 0
34
+ @passed = 0
35
+ @active = true
36
+ @start_time = Time.now
37
+ @force_priority = false
38
+ @modifiers = {}
39
+ @changed_params = []
40
+ end
41
+
42
+ #Find ant best matching current configuration based on priority
43
+ def require_ant
44
+ time = Time.now
45
+ ant = @queen.find_ant(self)
46
+ logger.debug "Find min ant took #{Time.now - time}"
47
+ ant
48
+ end
49
+
50
+ # Return priority of ant for this creep
51
+ # +ant+:: ant to calculate priority
52
+ def priority(ant)
53
+ mod = modifier(ant)
54
+ ant.prior - mod.get_setup_time(ant)
55
+ end
56
+
57
+ # Initialize instance variables from hash
58
+ # +hash+:: creep hash
59
+ def from_hash(hash)
60
+ @current_params = (hash[:current_parmas] || {})
61
+ @custom_data = hash[:custom_data] || {}
62
+ @status = hash[:status] || :wait
63
+ @processed = hash[:processed] || 0
64
+ @passed = hash[:passed] || 0
65
+ @active = hash[:active].nil? ? true : hash[:active]
66
+ @start_time = hash[:start_time] || Time.now
67
+ @hill_cfg.merge!(hash[:hill_cfg] || {})
68
+ end
69
+
70
+ # Convert current creep to hash
71
+ def to_hash
72
+ {
73
+ :id => object_id,
74
+ :current_params => current_params,
75
+ :custom_data => @custom_data,
76
+ :status => @status,
77
+ :processed => @processed,
78
+ :passed => @passed,
79
+ :active => @active,
80
+ :start_time => @start_time,
81
+ :hill_cfg => @hill_cfg
82
+ }
83
+ end
84
+
85
+ # Return modifier object for ant
86
+ # +ant+:: ant for which we need modifier
87
+ def modifier(ant)
88
+ @modifiers[ant.type] ||= ant.colony.creep_modifier_class.new(self)
89
+ end
90
+
91
+ # Do setup and run ant
92
+ # +ant+:: ant to setup and run
93
+ def setup_and_process_ant(ant)
94
+ @current_ant = ant
95
+ @modifier = modifier(ant)
96
+ ant.start
97
+ safe do
98
+ before_process(ant)
99
+ ok = setup(ant)
100
+ if ok
101
+ ant.params.each do |k,v|
102
+ if !@modifier.creep_params || @modifier.creep_params.include?(k)
103
+ if current_params[k] != v
104
+ current_params[k]=v
105
+ self.force_priority = true
106
+ end
107
+ end
108
+ end
109
+ run(ant)
110
+ else
111
+ setup_failed(ant)
112
+ self.force_priority = true
113
+ end
114
+ end
115
+ ant.finish
116
+ safe{ after_process(ant) }
117
+ if self.force_priority
118
+ Queen.queen.reset_priority_for_creep(self)
119
+ self.force_priority = false
120
+ end
121
+ @processed+=1
122
+ @passed +=1 if @current_ant.execution_status.to_sym == :passed
123
+ @current_ant = nil
124
+ end
125
+
126
+ # Before process hook
127
+ # +ant+:: Ant object
128
+ def before_process(ant)
129
+ @modifier.before_process(ant)
130
+ end
131
+
132
+ # After process hook
133
+ # +ant+:: Ant object
134
+ def after_process(ant)
135
+ @modifier.after_process(ant)
136
+ end
137
+
138
+ # Setup failed hook
139
+ # +ant+:: Ant object
140
+ def setup_failed(ant)
141
+ @modifier.setup_failed(ant)
142
+ end
143
+
144
+ # Setup method
145
+ # +ant+:: Ant object
146
+ def setup(ant)
147
+ timeout = 0
148
+ timeout = @modifier.get_setup_time(ant)
149
+ change_status(:setup)
150
+ ok = false
151
+ logger.debug "executing setup method with timeout #{timeout}"
152
+ ok = timeout_execution(timeout, "setup #{ant.params.inspect}", false) do
153
+ @modifier.setup_ant(ant)
154
+ end
155
+ ok &&= timeout_execution( timeout , "check params is #{ant.params.inspect}", false ) do #FIXME: Should we have other value for timeout?
156
+ @modifier.check(ant)
157
+ end
158
+ ok
159
+ end
160
+
161
+ # Run method
162
+ # +ant+:: Ant object
163
+ def run(ant)
164
+ timeout = @modifier.get_run_time(ant)
165
+ change_status(:run)
166
+ timeout_execution(timeout, "run #{ant.to_s}") do
167
+ @modifier.run_ant(ant)
168
+ end
169
+ end
170
+
171
+ # Return logger object
172
+ def logger
173
+ Log.logger_for host
174
+ end
175
+
176
+ # Setup creep configuration
177
+ # +creep_configuration+:: Ant object
178
+ def configure(creep_configuration)
179
+ @hill_cfg = creep_configuration
180
+ @host = @hill_cfg['host']
181
+ @user = @hill_cfg['user']
182
+ @password = @hill_cfg['password']
183
+ @name = @hill_cfg['name']
184
+ @connection_pool = @config.get_connection_class.new(self)
185
+ end
186
+
187
+ # Execute command on creep
188
+ # +command+:: Command to run
189
+ # +timeout+:: Timeout for command. If nil - no timeout
190
+ def exec!(command, timeout=nil)
191
+ logger.info("Executing: #{command}")
192
+ stderr,stdout = '', ''
193
+ stdout, stderr = timeout_execution(timeout, "exec!(#{command})") do
194
+ @connection_pool.exec(command)
195
+ end
196
+ logger.info("STDERR: #{stderr}") unless stderr.empty?
197
+ logger.info("STDOUT: #{stdout}") unless stdout.empty?
198
+ logger.info("Executing done")
199
+ stdout
200
+ end
201
+
202
+ # Silent version of exec!. Return NoFreeConnection error instance if failed to execute
203
+ def run_once(command, timeout = nil)
204
+ exec!(command,timeout)
205
+ rescue NoFreeConnectionError => ex
206
+ ex
207
+ end
208
+
209
+ # Execute block with timeout
210
+ # +timeout+:: Timeout in seconds or nil if no timeout
211
+ # +process+:: description string, describing process where timeout happened
212
+ # +default_responce+:: default responce if timeout was raised
213
+ def timeout_execution(timeout=nil, process = nil, default_response = ['', ''])
214
+ result = default_response
215
+ begin
216
+ Timeout::timeout( timeout ) do
217
+ result = yield
218
+ end
219
+ rescue Timeout::Error => e
220
+ change_status(:error)
221
+ logger.error "#{self.host}: timeout error for #{process.to_s}"
222
+ end
223
+ result
224
+ end
225
+
226
+ # Create string representation of Creep
227
+ def to_s
228
+ took_time = Time.at(Time.now - @start_time).gmtime.strftime('%R:%S')
229
+ "%s (%i): %s (%s): %s " % [@hill_cfg['host'], @processed, status, took_time, @current_ant]
230
+ end
231
+
232
+ # Retunr if creep is active
233
+ def active?; @active; end
234
+
235
+ # Deactivate creep and set status to +:disabled+
236
+ def disable!
237
+ @active = false
238
+ change_status(:disabled)
239
+ end
240
+
241
+ # Activate creep and set status to +:wait+
242
+ def enable!
243
+ @active = true
244
+ change_status(:wait)
245
+ end
246
+
247
+ # Check if creep is busy
248
+ # "Free" statuses are +:wait+, +:disabled+, +:error+
249
+ def busy?
250
+ !(@status == :wait || @status == :disabled || @status == :error)
251
+ end
252
+
253
+ # Start service
254
+ def service
255
+ loop do
256
+ if !active?
257
+ logger.debug("Node was disabled")
258
+ change_status(:disabled)
259
+ sleep @config.sleep_interval
260
+ elsif @queen.active? && ant = self.require_ant
261
+ logger.debug("Setupping and processing ant")
262
+ setup_and_process_ant(ant)
263
+ else
264
+ logger.debug("Waiting for more ants or release")
265
+ change_status(:wait)
266
+ sleep @config.sleep_interval
267
+ end
268
+ end
269
+ @connection_pool.destroy
270
+ end
271
+
272
+ # Kill all connections
273
+ def kill_connections
274
+ @connection_pool.destroy
275
+ end
276
+
277
+ # Change status and timer for this status of creep
278
+ def change_status(status)
279
+ unless @status == status
280
+ @status = status
281
+ @start_time = Time.now
282
+ end
283
+ end
284
+
285
+ # Execute block without raising errors
286
+ def safe
287
+ begin
288
+ yield
289
+ rescue NoFreeConnectionError => e
290
+ disable!
291
+ custom_data['disabled_reason'] = :no_free_connections
292
+ custom_data['disabled_description'] = 'Cannot find free connection or create new one'
293
+ logger.error "#{e}\n#{e.backtrace}"
294
+ rescue => e
295
+ change_status(:error)
296
+ logger.error "#{e}\n#{e.backtrace}"
297
+ end
298
+ end
299
+ end
300
+ end
301
+