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.
- 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,150 @@
|
|
1
|
+
module AntHill
|
2
|
+
|
3
|
+
# Base class for setup/run logic
|
4
|
+
# Childrens should implement specific logic for particular ant type
|
5
|
+
class CreepModifier
|
6
|
+
|
7
|
+
# Creep object
|
8
|
+
attr_reader :creep
|
9
|
+
|
10
|
+
include DRbUndumped
|
11
|
+
|
12
|
+
# Initialize
|
13
|
+
# +creep+:: Creep which we gonna change
|
14
|
+
def initialize(creep)
|
15
|
+
@creep = creep
|
16
|
+
end
|
17
|
+
|
18
|
+
# Find diff between current params of creep and ant params
|
19
|
+
def find_diff(ant)
|
20
|
+
diff = {}
|
21
|
+
ant_config = ant.params
|
22
|
+
creep_config = creep.current_params
|
23
|
+
|
24
|
+
ant_config.each_key{|k|
|
25
|
+
diff[k] = ant_config[k] unless ant_config[k] == creep_config[k]
|
26
|
+
}
|
27
|
+
diff
|
28
|
+
end
|
29
|
+
|
30
|
+
# Run ant
|
31
|
+
# +ant+:: ant to run
|
32
|
+
def run_ant(ant)
|
33
|
+
begin
|
34
|
+
before_run(ant)
|
35
|
+
rescue => e
|
36
|
+
logger.error "Error during run ant_started method: #{e} \n #{e.backtrace}"
|
37
|
+
end
|
38
|
+
begin
|
39
|
+
out = run(ant)
|
40
|
+
ant.output = out
|
41
|
+
rescue => e
|
42
|
+
logger.error "Error during processing run method: #{e} \n #{e.backtrace}"
|
43
|
+
ensure
|
44
|
+
begin
|
45
|
+
after_run(ant)
|
46
|
+
rescue => e
|
47
|
+
logger.error "Error in ant_finished method: #{e} \n #{e.backtrace}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Setup ant for running
|
53
|
+
# +ant+:: Ant to setup
|
54
|
+
def setup_ant(ant)
|
55
|
+
ant.runner = creep
|
56
|
+
ant.change_status(:setup)
|
57
|
+
begin
|
58
|
+
before_setup(ant)
|
59
|
+
rescue => e
|
60
|
+
logger.error "Error during processing before_setup method: #{e} \n #{e.backtrace}"
|
61
|
+
end
|
62
|
+
result = nil
|
63
|
+
begin
|
64
|
+
result = setup(ant)
|
65
|
+
rescue => e
|
66
|
+
logger.error "Error during processing setup method: #{e} \n #{e.backtrace}"
|
67
|
+
end
|
68
|
+
begin
|
69
|
+
after_setup(ant)
|
70
|
+
rescue => e
|
71
|
+
logger.error "Error during processing after_setup method: #{e} \n #{e.backtrace}"
|
72
|
+
end
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
# Logger for creep
|
77
|
+
def logger
|
78
|
+
creep.logger
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return calculated setup time for ant
|
82
|
+
# +ant+:: ant to setup
|
83
|
+
# Can be redefined in child class
|
84
|
+
def get_setup_time(ant)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return calculated run time for ant
|
88
|
+
# +ant+:: ant to run
|
89
|
+
# Can be redefined in child class
|
90
|
+
def get_run_time(ant)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Before run hook
|
94
|
+
# +ant+:: ant to run
|
95
|
+
def before_run(ant)
|
96
|
+
end
|
97
|
+
|
98
|
+
# After run hook
|
99
|
+
# +ant+:: ant was ran
|
100
|
+
def after_run(ant)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Before setup hook
|
104
|
+
# +ant+:: ant to setup
|
105
|
+
def before_setup(ant)
|
106
|
+
end
|
107
|
+
|
108
|
+
# After setup hook
|
109
|
+
# +ant+:: ant was set up
|
110
|
+
def after_setup(ant)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Setup failed hook
|
114
|
+
# +ant+:: ant for wich setup failed
|
115
|
+
def setup_failed(ant)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Before process hook
|
119
|
+
# +ant+:: ant to be processed
|
120
|
+
def before_process(ant)
|
121
|
+
end
|
122
|
+
|
123
|
+
# After process hook
|
124
|
+
# +ant+:: ant was processed
|
125
|
+
def after_process(ant)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Setup implementation
|
129
|
+
# +ant+:: ant to set up
|
130
|
+
def setup(ant)
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
134
|
+
# Run implementation
|
135
|
+
# +ant+:: ant to run
|
136
|
+
def run(ant)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check node is set up
|
140
|
+
# +ant+:: ant to check
|
141
|
+
def check(ant)
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
# Params from ant to be coppied after setup
|
146
|
+
def creep_params
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/lib/ant_hill/log.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module AntHill
|
2
|
+
# Log class
|
3
|
+
class Log
|
4
|
+
include DRbUndumped
|
5
|
+
# Singleton class
|
6
|
+
class << self
|
7
|
+
@@loggers = {}
|
8
|
+
# Return logger for specified name
|
9
|
+
def logger_for(name, config = Configuration.config)
|
10
|
+
return @@loggers[name] if @@loggers[name]
|
11
|
+
verbose = config.log_level
|
12
|
+
path = config.log_dir
|
13
|
+
FileUtils.mkdir_p path unless File.exists?(path)
|
14
|
+
logger = Logger.new(path+"/#{name}.log", (config.log_num_old_files) || 5, (config.log_size || 50*1024*1024) )
|
15
|
+
logger.level = level(config.log_level)
|
16
|
+
logger.formatter = Logger::Formatter.new
|
17
|
+
@@loggers[name] = logger
|
18
|
+
end
|
19
|
+
private
|
20
|
+
# Convert symbol log level to Logger log level
|
21
|
+
#
|
22
|
+
def level(level)
|
23
|
+
case level
|
24
|
+
when :fatal then Logger::FATAL
|
25
|
+
when :error then Logger::ERROR
|
26
|
+
when :warn then Logger::WARN
|
27
|
+
when :info then Logger::INFO
|
28
|
+
when :debug then Logger::DEBUG
|
29
|
+
else Logger::ERROR
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
module AntHill
|
2
|
+
# Main class that rule all the kingdom
|
3
|
+
class Queen
|
4
|
+
# +creeps+:: list of creeps
|
5
|
+
# +ants+:: list of ants
|
6
|
+
# +colonies+:: list of colonies
|
7
|
+
attr_reader :creeps, :ants, :colonies
|
8
|
+
|
9
|
+
# Default host for DRb
|
10
|
+
DRB_HOST = '127.0.0.1'
|
11
|
+
# Default port for DRb
|
12
|
+
DRB_PORT = 6666
|
13
|
+
|
14
|
+
include DRbUndumped
|
15
|
+
|
16
|
+
# Initialize
|
17
|
+
# +config+:: Configuration object
|
18
|
+
def initialize(config = Configuration.config)
|
19
|
+
@config = config
|
20
|
+
@ants = []
|
21
|
+
@colony_queue = []
|
22
|
+
@colonies = []
|
23
|
+
@drb_host = config.drb_host
|
24
|
+
@drb_port = config.drb_port
|
25
|
+
@@mutex = Mutex.new
|
26
|
+
trap("INT") do
|
27
|
+
puts "Terminating... Pls wait"
|
28
|
+
@creeps.each{|c| c.kill_connections }
|
29
|
+
@threads.each{|th| th.exit }
|
30
|
+
end
|
31
|
+
@active = true
|
32
|
+
@loaded_params = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return ants size
|
36
|
+
def size
|
37
|
+
@ants.size
|
38
|
+
end
|
39
|
+
|
40
|
+
# Service method
|
41
|
+
def service
|
42
|
+
@threads = []
|
43
|
+
spawn_creeps(@config.creeps)
|
44
|
+
spawn_drb_queen
|
45
|
+
spawn_colonies_processor
|
46
|
+
at_exit do
|
47
|
+
save_queen(@config.queen_filename || "queen.yml")
|
48
|
+
end
|
49
|
+
@threads.each{|t| t.join}
|
50
|
+
rescue => e
|
51
|
+
logger.error "There was an error in queen. Details: #{e}\n#{e.backtrace.join("\n")}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create colony
|
55
|
+
# +params+:: params for colony
|
56
|
+
# +loaded_params+:: loaded params for respawning queen
|
57
|
+
def create_colony(params={}, loaded_params = nil)
|
58
|
+
type = params['type']
|
59
|
+
type = loaded_params[:params]['type'] if loaded_params
|
60
|
+
colony_class = @config.ant_colony_class(type)
|
61
|
+
if colony_class
|
62
|
+
colony = colony_class.new(params)
|
63
|
+
colony.from_hash(loaded_params) if loaded_params
|
64
|
+
@colony_queue << colony unless loaded_params
|
65
|
+
@colonies << colony
|
66
|
+
else
|
67
|
+
logger.error "Couldn't process request #{params} because of previous errors"
|
68
|
+
end
|
69
|
+
colony
|
70
|
+
end
|
71
|
+
|
72
|
+
# Initialize and start threads for each creep
|
73
|
+
# +creeps+: array of hashes with creep params
|
74
|
+
# Example: [{'name' => 'creep1', 'host'=> 'hostname', 'user' => 'login_user', 'password' => 'user_password'}]
|
75
|
+
def spawn_creeps(creeps)
|
76
|
+
@creeps = []
|
77
|
+
loaded_params = @loaded_params[:creeps]
|
78
|
+
creeps.each do |creep_config|
|
79
|
+
creep_loaded = loaded_params && loaded_params.find{|cr| cr[:hill_cfg]['name'] == creep_config['name'] } || {}
|
80
|
+
add_creep(creep_config, creep_loaded)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Adding new creep and creatinf thread for it
|
85
|
+
# +creep_config+:: hash of params for creep
|
86
|
+
# Example: {'name' => 'creep1', 'host'=> 'hostname', 'user' => 'login_user', 'password' => 'user_password'}
|
87
|
+
# +creep_loaded+:: creep loaded from saved file
|
88
|
+
def add_creep(creep_config, creep_loaded={})
|
89
|
+
@threads << Thread.new{
|
90
|
+
c = Creep.new
|
91
|
+
c.configure(creep_config)
|
92
|
+
c.from_hash(creep_loaded)
|
93
|
+
@creeps << c
|
94
|
+
Thread.current["name"]=c.to_s
|
95
|
+
c.service
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Delete creep
|
100
|
+
# +creep_name+:: creep name to delete
|
101
|
+
# +graceful+:: default true, if true finish processing before delete
|
102
|
+
def delete_creep(creep_name, graceful=true)
|
103
|
+
creep = @creeps.find{|c| c.name.to_s =~ /#{creep_name}/}
|
104
|
+
thread = @threads.find{|t| t['name'] == creep.to_s} if creep
|
105
|
+
if graceful
|
106
|
+
creep.disable!
|
107
|
+
while creep.busy? do
|
108
|
+
sleep 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
thread.terminate if thread
|
112
|
+
@threads.delete(thread) if thread
|
113
|
+
@creeps.delete(creep) if creep
|
114
|
+
end
|
115
|
+
|
116
|
+
# Create drb interface for queen
|
117
|
+
def spawn_drb_queen
|
118
|
+
@threads << Thread.new{
|
119
|
+
begin
|
120
|
+
Thread.current["name"]="main"
|
121
|
+
DRb.start_service "druby://#{@drb_host || DRB_HOST}:#{@drb_port || DRB_PORT}", self
|
122
|
+
DRb.thread.join
|
123
|
+
rescue => e
|
124
|
+
logger.error "There was an error in drb_queen =(. Details: #{e}\n#{e.backtrace}"
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
# Create thread for processing new colonies
|
130
|
+
def spawn_colonies_processor
|
131
|
+
@threads << Thread.new{
|
132
|
+
Thread.current["name"]="colony queue processor"
|
133
|
+
while true do
|
134
|
+
if @active
|
135
|
+
@colony_processor_busy = true
|
136
|
+
colony = @colony_queue.shift
|
137
|
+
if colony
|
138
|
+
new_ants = colony.get_ants
|
139
|
+
add_ants(new_ants)
|
140
|
+
end
|
141
|
+
@colony_processor_busy = false
|
142
|
+
end
|
143
|
+
sleep 1
|
144
|
+
end
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add new ants to queue
|
149
|
+
def add_ants(ants)
|
150
|
+
@ants += ants
|
151
|
+
end
|
152
|
+
|
153
|
+
# Find ant for creep
|
154
|
+
# +creep+:: creep to find ant
|
155
|
+
def find_ant(creep)
|
156
|
+
return nil if @ants.empty?
|
157
|
+
winner = nil
|
158
|
+
@@mutex.synchronize{
|
159
|
+
winner = max_priority_ant(creep)
|
160
|
+
@ants.delete(winner) if winner
|
161
|
+
}
|
162
|
+
winner
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return ant with max priority for creep
|
166
|
+
# +creep+:: creep object
|
167
|
+
def max_priority_ant(creep)
|
168
|
+
max_ant = nil
|
169
|
+
max_priority =-Float::INFINITY
|
170
|
+
@ants.each do |a|
|
171
|
+
next if a.prior < max_priority
|
172
|
+
if (prior=a.priority_cache(creep)) > max_priority
|
173
|
+
max_priority = prior
|
174
|
+
max_ant = a
|
175
|
+
end
|
176
|
+
end
|
177
|
+
max_ant
|
178
|
+
rescue NoFreeConnectionError => e
|
179
|
+
logger.error "Couldn't find any free connection for creep #{creep}. #{e}: #{e.backtrace.join("\n")}"
|
180
|
+
creep.disable!
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
|
184
|
+
# Reset priority for specified creep for all ants
|
185
|
+
def reset_priority_for_creep(creep)
|
186
|
+
@ants.each{|a| a.delete_cache_for_creep(creep)}
|
187
|
+
end
|
188
|
+
|
189
|
+
# Return logger for queen
|
190
|
+
def logger
|
191
|
+
Log.logger_for(:queen)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Suspend all processing and wait while it's done
|
195
|
+
def suspend
|
196
|
+
@active = false
|
197
|
+
while creeps.any?{|creep| creep.busy? }
|
198
|
+
sleep 1
|
199
|
+
end
|
200
|
+
while @colony_processor_busy
|
201
|
+
sleep 1
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Release all processing
|
206
|
+
def release
|
207
|
+
@active = true
|
208
|
+
end
|
209
|
+
|
210
|
+
# Find colonies for params
|
211
|
+
# +params+:: hash of params to match colony
|
212
|
+
def find_colonies(params)
|
213
|
+
@colonies.select do |colony|
|
214
|
+
colony.is_it_me?(params)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Kill colonies matching params
|
219
|
+
# +params+:: hash of params
|
220
|
+
def kill_colony(params)
|
221
|
+
if params.is_a?(AntColony)
|
222
|
+
to_kill = [ params ]
|
223
|
+
else
|
224
|
+
to_kill = find_colonies(params)
|
225
|
+
end
|
226
|
+
@@mutex.synchronize{
|
227
|
+
to_kill.each do |colony|
|
228
|
+
colony.kill
|
229
|
+
@ants.reject!{|ant|
|
230
|
+
ant.colony == colony
|
231
|
+
}
|
232
|
+
@colonies.delete(colony)
|
233
|
+
end
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
# Save queen to file
|
238
|
+
# +filename+:: filename to store queen data
|
239
|
+
def save_queen(filename)
|
240
|
+
queen_hash = to_hash
|
241
|
+
File.open(filename, "w+") { |f| f.puts queen_hash.to_yaml}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Restore queen from file
|
245
|
+
# +filename+:: filenme with queen data
|
246
|
+
def restore_queen(filename)
|
247
|
+
hash = YAML::load_file(filename)
|
248
|
+
@loaded_params = hash
|
249
|
+
from_hash(hash)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Initialize queen from loaded hash
|
253
|
+
# +hash+:: queen hash
|
254
|
+
def from_hash(hash)
|
255
|
+
colonies = hash[:colonies]
|
256
|
+
tmp = {}
|
257
|
+
@config.from_hash(hash[:configuration])
|
258
|
+
colonies.each do |col|
|
259
|
+
colony = create_colony({},col)
|
260
|
+
tmp[col[:id]] = colony
|
261
|
+
end
|
262
|
+
@colonies.each{|c| add_ants(c.ants)}
|
263
|
+
@colony_queue = hash[:colony_queue].collect{|cq| tmp[cq]}
|
264
|
+
end
|
265
|
+
|
266
|
+
# Convert queen to hash
|
267
|
+
# +include_finished+:: should finished colonies and ants be includes to hash?
|
268
|
+
def to_hash(include_finished = false)
|
269
|
+
{
|
270
|
+
:colonies => @colonies.collect{|ac| ac.to_hash(include_finished) },
|
271
|
+
:colony_queue => @colony_queue.collect{|ac| ac.object_id },
|
272
|
+
:creeps => @creeps.collect{|c| c.to_hash },
|
273
|
+
:configuration => @config.to_hash
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
# Check if queen is active
|
278
|
+
def active?; @active; end
|
279
|
+
|
280
|
+
# Singleton object
|
281
|
+
class << self
|
282
|
+
# Check if mutex is locked
|
283
|
+
def locked?
|
284
|
+
@@mutex.locked?
|
285
|
+
end
|
286
|
+
|
287
|
+
# Return or create current queen
|
288
|
+
def queen
|
289
|
+
@@queen ||= self.new
|
290
|
+
end
|
291
|
+
|
292
|
+
# Connect to DRb interface of queen
|
293
|
+
# +host+:: host where DRb is started
|
294
|
+
def drb_queen(host = 'localhost')
|
295
|
+
DRb.start_service
|
296
|
+
queen = DRbObject.new_with_uri "druby://#{host}:6666"
|
297
|
+
rescue => e
|
298
|
+
puts e
|
299
|
+
end
|
300
|
+
|
301
|
+
# Creates colony for arguments
|
302
|
+
# +args+:: command line arguments
|
303
|
+
# +host+:: DRb queen host
|
304
|
+
def create_colony(args, host = 'localhost')
|
305
|
+
drb_queen(host).create_colony parse_args(args)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Return list of creeps
|
309
|
+
# +host+:: DRb queen host
|
310
|
+
def creeps(host = 'localhost')
|
311
|
+
drb_queen(host).creeps
|
312
|
+
end
|
313
|
+
|
314
|
+
# Kill colony(colonies) matching arguments
|
315
|
+
# +args+:: command line arguments
|
316
|
+
# +host+:: DRb queen host
|
317
|
+
def kill_colony(args, host = 'localhost')
|
318
|
+
drb_queen(host).kill_colony parse_args(args)
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
# Convert command line arguments to hash
|
323
|
+
# +args+:: command line arguments
|
324
|
+
def parse_args(args)
|
325
|
+
result = {}
|
326
|
+
args.each do |arg|
|
327
|
+
pair = arg.split("=")
|
328
|
+
result[pair[0]]=pair[1]
|
329
|
+
end
|
330
|
+
result
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|