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,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
|