ant_hill 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+