ant_hill 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/ant_hill.gemspec +29 -0
- data/bin/spawn_queen +14 -0
- data/lib/ant_hill.rb +36 -0
- data/lib/ant_hill/ant.rb +126 -0
- data/lib/ant_hill/ant_colony.rb +212 -0
- data/lib/ant_hill/configuration.rb +154 -0
- data/lib/ant_hill/connection_pool.rb +98 -0
- data/lib/ant_hill/connections/ssh_connection.rb +67 -0
- data/lib/ant_hill/creep.rb +301 -0
- data/lib/ant_hill/creep_modifier.rb +150 -0
- data/lib/ant_hill/log.rb +35 -0
- data/lib/ant_hill/queen.rb +334 -0
- data/lib/ant_hill/version.rb +4 -0
- data/lib/tasks/ant_hill.rake +48 -0
- data/spec/ant_hill/ant_colony_spec.rb +118 -0
- data/spec/ant_hill/ant_spec.rb +101 -0
- data/spec/ant_hill/configuration_spec.rb +201 -0
- data/spec/ant_hill/creep_modifier_spec.rb +30 -0
- data/spec/ant_hill/creep_spec.rb +165 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/config.yml +43 -0
- metadata +117 -0
@@ -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
|
+
|