nulogy-hydra 0.23.2.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/.document +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +56 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +131 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra/cucumber/formatter.rb +29 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/js/lint.js +5150 -0
- data/lib/hydra/listener/abstract.rb +39 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +31 -0
- data/lib/hydra/master.rb +252 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +46 -0
- data/lib/hydra/message/worker_messages.rb +52 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/messaging_io.rb +49 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/proxy_config.rb +27 -0
- data/lib/hydra/runner.rb +306 -0
- data/lib/hydra/runner_listener/abstract.rb +23 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +3 -0
- data/lib/hydra/spec/hydra_formatter.rb +26 -0
- data/lib/hydra/ssh.rb +41 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +366 -0
- data/lib/hydra/threadsafe_io.rb +18 -0
- data/lib/hydra/tmpdir.rb +11 -0
- data/lib/hydra/trace.rb +29 -0
- data/lib/hydra/worker.rb +168 -0
- data/lib/hydra.rb +16 -0
- data/nulogy-hydra.gemspec +122 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/bad_proxy_config.yml +4 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/conflicting.rb +10 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/hydra_worker_init.rb +2 -0
- data/test/fixtures/js_file.js +4 -0
- data/test/fixtures/json_data.json +4 -0
- data/test/fixtures/many_outputs_to_console.rb +9 -0
- data/test/fixtures/master_listeners.rb +10 -0
- data/test/fixtures/proxy_config.yml +4 -0
- data/test/fixtures/proxy_config_http.yml +4 -0
- data/test/fixtures/runner_listeners.rb +23 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/task_test_config.yml +6 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +383 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/proxy_config_test.rb +31 -0
- data/test/runner_test.rb +196 -0
- data/test/ssh_test.rb +25 -0
- data/test/sync_test.rb +113 -0
- data/test/task_test.rb +21 -0
- data/test/test_helper.rb +107 -0
- data/test/worker_test.rb +60 -0
- metadata +208 -0
data/lib/hydra/tasks.rb
ADDED
@@ -0,0 +1,366 @@
|
|
1
|
+
require 'open3'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Hydra Task Common attributes and methods
|
4
|
+
class Task
|
5
|
+
# Name of the task. Default 'hydra'
|
6
|
+
attr_accessor :name
|
7
|
+
|
8
|
+
# Files to test.
|
9
|
+
# You can add files manually via:
|
10
|
+
# t.files << [file1, file2, etc]
|
11
|
+
#
|
12
|
+
# Or you can use the add_files method
|
13
|
+
attr_accessor :files
|
14
|
+
|
15
|
+
# True if you want to see Hydra's message traces
|
16
|
+
attr_accessor :verbose
|
17
|
+
|
18
|
+
# Path to the hydra config file.
|
19
|
+
# If not set, it will check 'hydra.yml' and 'config/hydra.yml'
|
20
|
+
attr_accessor :config
|
21
|
+
|
22
|
+
# Automatically sort files using their historical runtimes.
|
23
|
+
# Defaults to true
|
24
|
+
# To disable:
|
25
|
+
# t.autosort = false
|
26
|
+
attr_accessor :autosort
|
27
|
+
|
28
|
+
# Event listeners. Defaults to the MinimalOutput listener.
|
29
|
+
# You can add additional listeners if you'd like. For example,
|
30
|
+
# on linux (with notify-send) you can add the notifier listener:
|
31
|
+
# t.listeners << Hydra::Listener::Notifier.new
|
32
|
+
attr_accessor :listeners
|
33
|
+
|
34
|
+
# Set to true if you want to run this task only on the local
|
35
|
+
# machine with one runner. A "Safe Mode" for some test
|
36
|
+
# files that may not play nice with others.
|
37
|
+
attr_accessor :serial
|
38
|
+
|
39
|
+
attr_accessor :environment
|
40
|
+
|
41
|
+
# Set to false if you don't want to show the total running time
|
42
|
+
attr_accessor :show_time
|
43
|
+
|
44
|
+
# Set to a valid file path if you want to save the output of the runners
|
45
|
+
# in a log file
|
46
|
+
attr_accessor :runner_log_file
|
47
|
+
|
48
|
+
#
|
49
|
+
# Search for the hydra config file
|
50
|
+
def find_config_file
|
51
|
+
@config ||= 'hydra.yml'
|
52
|
+
return @config if File.exists?(@config)
|
53
|
+
@config = File.join('config', 'hydra.yml')
|
54
|
+
return @config if File.exists?(@config)
|
55
|
+
@config = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add files to test by passing in a string to be run through Dir.glob.
|
59
|
+
# For example:
|
60
|
+
#
|
61
|
+
# t.add_files 'test/units/*.rb'
|
62
|
+
def add_files(pattern)
|
63
|
+
@files += Dir.glob(pattern)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define a test task that uses hydra to test the files.
|
69
|
+
#
|
70
|
+
# Hydra::TestTask.new('hydra') do |t|
|
71
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
72
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
73
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
74
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
75
|
+
# t.autosort = false # disable automatic sorting based on runtime of tests
|
76
|
+
# end
|
77
|
+
class TestTask < Hydra::Task
|
78
|
+
|
79
|
+
# Create a new HydraTestTask
|
80
|
+
def initialize(name = :hydra)
|
81
|
+
@name = name
|
82
|
+
@files = []
|
83
|
+
@verbose = false
|
84
|
+
@autosort = true
|
85
|
+
@serial = false
|
86
|
+
@listeners = [Hydra::Listener::ProgressBar.new]
|
87
|
+
@show_time = true
|
88
|
+
|
89
|
+
yield self if block_given?
|
90
|
+
|
91
|
+
# Ensure we override rspec's at_exit
|
92
|
+
if defined?(RSpec)
|
93
|
+
RSpec::Core::Runner.disable_autorun!
|
94
|
+
end
|
95
|
+
|
96
|
+
unless @serial
|
97
|
+
@config = find_config_file
|
98
|
+
end
|
99
|
+
|
100
|
+
@opts = {
|
101
|
+
:verbose => @verbose,
|
102
|
+
:autosort => @autosort,
|
103
|
+
:files => @files,
|
104
|
+
:listeners => @listeners,
|
105
|
+
:environment => @environment,
|
106
|
+
:runner_log_file => @runner_log_file
|
107
|
+
}
|
108
|
+
if @config
|
109
|
+
@opts.merge!(:config => @config)
|
110
|
+
else
|
111
|
+
@opts.merge!(:workers => [{:type => :local, :runners => 1}])
|
112
|
+
end
|
113
|
+
|
114
|
+
define
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
# Create the rake task defined by this HydraTestTask
|
119
|
+
def define
|
120
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
121
|
+
task @name do
|
122
|
+
if Object.const_defined?('Rails') && Rails.env == 'development'
|
123
|
+
$stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
|
124
|
+
end
|
125
|
+
|
126
|
+
start = Time.now if @show_time
|
127
|
+
|
128
|
+
master = Hydra::Master.new(@opts)
|
129
|
+
|
130
|
+
$stdout.puts "\nFinished in #{'%.6f' % (Time.now - start)} seconds." if @show_time
|
131
|
+
|
132
|
+
unless master.failed_files.empty?
|
133
|
+
raise "Hydra: Not all tests passes"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Define a test task that uses hydra to profile your test files
|
140
|
+
#
|
141
|
+
# Hydra::ProfileTask.new('hydra:prof') do |t|
|
142
|
+
# t.add_files 'test/unit/**/*_test.rb'
|
143
|
+
# t.add_files 'test/functional/**/*_test.rb'
|
144
|
+
# t.add_files 'test/integration/**/*_test.rb'
|
145
|
+
# t.generate_html = true # defaults to false
|
146
|
+
# t.generate_text = true # defaults to true
|
147
|
+
# end
|
148
|
+
class ProfileTask < Hydra::Task
|
149
|
+
# boolean: generate html output from ruby-prof
|
150
|
+
attr_accessor :generate_html
|
151
|
+
# boolean: generate text output from ruby-prof
|
152
|
+
attr_accessor :generate_text
|
153
|
+
|
154
|
+
# Create a new Hydra ProfileTask
|
155
|
+
def initialize(name = 'hydra:profile')
|
156
|
+
@name = name
|
157
|
+
@files = []
|
158
|
+
@verbose = false
|
159
|
+
@generate_html = false
|
160
|
+
@generate_text = true
|
161
|
+
|
162
|
+
yield self if block_given?
|
163
|
+
|
164
|
+
# Ensure we override rspec's at_exit
|
165
|
+
require 'hydra/spec/autorun_override'
|
166
|
+
|
167
|
+
@config = find_config_file
|
168
|
+
|
169
|
+
@opts = {
|
170
|
+
:verbose => @verbose,
|
171
|
+
:files => @files
|
172
|
+
}
|
173
|
+
define
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
# Create the rake task defined by this HydraTestTask
|
178
|
+
def define
|
179
|
+
desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
|
180
|
+
task @name do
|
181
|
+
require 'ruby-prof'
|
182
|
+
RubyProf.start
|
183
|
+
|
184
|
+
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
|
185
|
+
@files.each do |file|
|
186
|
+
$stdout.write runner.run_file(file)
|
187
|
+
$stdout.flush
|
188
|
+
end
|
189
|
+
|
190
|
+
$stdout.write "\nTests complete. Generating profiling output\n"
|
191
|
+
$stdout.flush
|
192
|
+
|
193
|
+
result = RubyProf.stop
|
194
|
+
|
195
|
+
if @generate_html
|
196
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
197
|
+
out = File.new("ruby-prof.html", 'w')
|
198
|
+
printer.print(out, :min_self => 0.05)
|
199
|
+
out.close
|
200
|
+
$stdout.write "Profiling data written to [ruby-prof.html]\n"
|
201
|
+
end
|
202
|
+
|
203
|
+
if @generate_text
|
204
|
+
printer = RubyProf::FlatPrinter.new(result)
|
205
|
+
out = File.new("ruby-prof.txt", 'w')
|
206
|
+
printer.print(out, :min_self => 0.05)
|
207
|
+
out.close
|
208
|
+
$stdout.write "Profiling data written to [ruby-prof.txt]\n"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Define a sync task that uses hydra to rsync the source tree under test to remote workers.
|
215
|
+
#
|
216
|
+
# This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
|
217
|
+
# file is up to date on the remote workers.
|
218
|
+
#
|
219
|
+
# Hydra::SyncTask.new('hydra:sync') do |t|
|
220
|
+
# t.verbose = false # optionally set to true for lots of debug messages
|
221
|
+
# end
|
222
|
+
class SyncTask < Hydra::Task
|
223
|
+
|
224
|
+
# Create a new SyncTestTask
|
225
|
+
def initialize(name = :sync)
|
226
|
+
@name = name
|
227
|
+
@verbose = false
|
228
|
+
|
229
|
+
yield self if block_given?
|
230
|
+
|
231
|
+
@config = find_config_file
|
232
|
+
|
233
|
+
@opts = {
|
234
|
+
:verbose => @verbose
|
235
|
+
}
|
236
|
+
@opts.merge!(:config => @config) if @config
|
237
|
+
|
238
|
+
define
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
# Create the rake task defined by this HydraSyncTask
|
243
|
+
def define
|
244
|
+
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
245
|
+
task @name do
|
246
|
+
Hydra::Sync.sync_many(@opts)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Setup a task that will be run across all remote workers
|
252
|
+
# Hydra::RemoteTask.new('db:reset')
|
253
|
+
#
|
254
|
+
# Then you can run:
|
255
|
+
# rake hydra:remote:db:reset
|
256
|
+
class RemoteTask < Hydra::Task
|
257
|
+
include Open3
|
258
|
+
# Create a new hydra remote task with the given name.
|
259
|
+
# The task will be named hydra:remote:<name>
|
260
|
+
def initialize(name, command=nil)
|
261
|
+
@name = name
|
262
|
+
@command = command
|
263
|
+
yield self if block_given?
|
264
|
+
@config = find_config_file
|
265
|
+
if @config
|
266
|
+
define
|
267
|
+
else
|
268
|
+
task "hydra:remote:#{@name}" do ; end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
def define
|
274
|
+
desc "Run #{@name} remotely on all workers"
|
275
|
+
task "hydra:remote:#{@name}" do
|
276
|
+
config = Hydra::ProxyConfig.load(IO.read(@config))
|
277
|
+
environment = config.fetch('environment') { 'test' }
|
278
|
+
workers = config.fetch('workers') { [] }
|
279
|
+
workers = workers.select{|w| w['type'] == 'ssh'}
|
280
|
+
@command = "RAILS_ENV=#{environment} rake #{@name}" unless @command
|
281
|
+
|
282
|
+
$stdout.write "==== Hydra Running #{@name} ====\n"
|
283
|
+
Thread.abort_on_exception = true
|
284
|
+
@listeners = []
|
285
|
+
@results = {}
|
286
|
+
workers.each do |worker|
|
287
|
+
@listeners << Thread.new do
|
288
|
+
begin
|
289
|
+
@results[worker] = if run_command(worker, @command)
|
290
|
+
"==== #{@name} passed on #{worker['connect']} ====\n"
|
291
|
+
else
|
292
|
+
"==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
|
293
|
+
end
|
294
|
+
rescue
|
295
|
+
@results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
@listeners.each{|l| l.join}
|
300
|
+
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
|
301
|
+
$stdout.write @results.values.join("\n")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def run_command worker, command
|
306
|
+
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
|
307
|
+
ssh_opts = worker.fetch('ssh_opts') { '' }
|
308
|
+
writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
|
309
|
+
writer.write("cd #{worker['directory']}\n")
|
310
|
+
writer.write "echo BEGIN HYDRA\n"
|
311
|
+
writer.write(command + "\r")
|
312
|
+
writer.write "echo END HYDRA\n"
|
313
|
+
writer.write("exit\n")
|
314
|
+
writer.close
|
315
|
+
ignoring = true
|
316
|
+
passed = true
|
317
|
+
while line = reader.gets
|
318
|
+
line.chomp!
|
319
|
+
if line =~ /^rake aborted!$/
|
320
|
+
passed = false
|
321
|
+
end
|
322
|
+
if line =~ /echo END HYDRA$/
|
323
|
+
ignoring = true
|
324
|
+
end
|
325
|
+
$stdout.write "#{worker['connect']}: #{line}\n" unless ignoring
|
326
|
+
if line == 'BEGIN HYDRA'
|
327
|
+
ignoring = false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
passed
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# A Hydra global task is a task that is run both locally and remotely.
|
335
|
+
#
|
336
|
+
# For example:
|
337
|
+
#
|
338
|
+
# Hydra::GlobalTask.new('db:reset')
|
339
|
+
#
|
340
|
+
# Allows you to run:
|
341
|
+
#
|
342
|
+
# rake hydra:db:reset
|
343
|
+
#
|
344
|
+
# Then, db:reset will be run locally and on all remote workers. This
|
345
|
+
# makes it easy to setup your workers and run tasks all in a row.
|
346
|
+
#
|
347
|
+
# For example:
|
348
|
+
#
|
349
|
+
# rake hydra:db:reset hydra:factories hydra:tests
|
350
|
+
#
|
351
|
+
# Assuming you setup hydra:db:reset and hydra:db:factories as global
|
352
|
+
# tasks and hydra:tests as a Hydra::TestTask for all your tests
|
353
|
+
class GlobalTask < Hydra::Task
|
354
|
+
def initialize(name)
|
355
|
+
@name = name
|
356
|
+
define
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
def define
|
361
|
+
Hydra::RemoteTask.new(@name)
|
362
|
+
desc "Run #{@name.to_s} Locally and Remotely across all Workers"
|
363
|
+
task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'fcntl'
|
3
|
+
|
4
|
+
module Hydra
|
5
|
+
class ThreadsafeIO < IO
|
6
|
+
def initialize(existing_io)
|
7
|
+
fd = existing_io.fcntl(Fcntl::F_DUPFD)
|
8
|
+
super(fd)
|
9
|
+
@mutex = Mutex.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(*args)
|
13
|
+
@mutex.synchronize do
|
14
|
+
super(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/hydra/tmpdir.rb
ADDED
data/lib/hydra/trace.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Trace output when in verbose mode.
|
3
|
+
module Trace
|
4
|
+
module ClassMethods
|
5
|
+
# Make a class traceable. Takes one parameter,
|
6
|
+
# which is the prefix for the trace to identify this class
|
7
|
+
def traceable(prefix = self.class.to_s)
|
8
|
+
include Hydra::Trace::InstanceMethods
|
9
|
+
class << self; attr_accessor :_traceable_prefix; end
|
10
|
+
self._traceable_prefix = prefix
|
11
|
+
$stdout.sync = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# Trace some output with the class's prefix and a newline.
|
17
|
+
# Checks to ensure we're running verbosely.
|
18
|
+
def trace(str)
|
19
|
+
$stdout.write "#{Time.now.to_f} #{self.class._traceable_prefix}| #{str}\n" if @verbose
|
20
|
+
|
21
|
+
prefix = self.class._traceable_prefix.upcase
|
22
|
+
unless (prefix == 'MASTER' || prefix == 'SYNC')
|
23
|
+
$stdout.write "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
Object.extend(Hydra::Trace::ClassMethods)
|
data/lib/hydra/worker.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Hydra class responsible to dispatching runners and communicating with the master.
|
3
|
+
#
|
4
|
+
# The Worker is never run directly by a user. Workers are created by a
|
5
|
+
# Master to delegate to Runners.
|
6
|
+
#
|
7
|
+
# The general convention is to have one Worker per machine on a distributed
|
8
|
+
# network.
|
9
|
+
class Worker
|
10
|
+
include Hydra::Messages::Worker
|
11
|
+
traceable('WORKER')
|
12
|
+
|
13
|
+
attr_reader :runners
|
14
|
+
# Create a new worker.
|
15
|
+
# * io: The IO object to use to communicate with the master
|
16
|
+
# * num_runners: The number of runners to launch
|
17
|
+
def initialize(opts = {})
|
18
|
+
@verbose = opts.fetch(:verbose) { false }
|
19
|
+
@io = opts.fetch(:io) { raise "No IO Object" }
|
20
|
+
@runners = []
|
21
|
+
@listeners = []
|
22
|
+
|
23
|
+
load_worker_initializer
|
24
|
+
|
25
|
+
@runner_event_listeners = Array(opts.fetch(:runner_listeners) { nil })
|
26
|
+
@runner_event_listeners.select{|l| l.is_a? String}.each do |l|
|
27
|
+
@runner_event_listeners.delete_at(@runner_event_listeners.index(l))
|
28
|
+
listener = eval(l)
|
29
|
+
@runner_event_listeners << listener if listener.is_a?(Hydra::RunnerListener::Abstract)
|
30
|
+
end
|
31
|
+
@runner_log_file = opts.fetch(:runner_log_file) { nil }
|
32
|
+
|
33
|
+
boot_runners(opts.fetch(:runners) { 1 })
|
34
|
+
@io.write(Hydra::Messages::Worker::WorkerBegin.new)
|
35
|
+
|
36
|
+
process_messages
|
37
|
+
|
38
|
+
@runners.each{|r| Process.wait r[:pid] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_worker_initializer
|
42
|
+
if File.exist?('./hydra_worker_init.rb')
|
43
|
+
trace('Requiring hydra_worker_init.rb')
|
44
|
+
require 'hydra_worker_init'
|
45
|
+
else
|
46
|
+
trace('hydra_worker_init.rb not present')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# message handling methods
|
51
|
+
|
52
|
+
# When a runner wants a file, it hits this method with a message.
|
53
|
+
# Then the worker bubbles the file request up to the master.
|
54
|
+
def request_file(message, runner)
|
55
|
+
@io.write(RequestFile.new)
|
56
|
+
runner[:idle] = true
|
57
|
+
end
|
58
|
+
|
59
|
+
# When the master sends a file down to the worker, it hits this
|
60
|
+
# method. Then the worker delegates the file down to a runner.
|
61
|
+
def delegate_file(message)
|
62
|
+
runner = idle_runner
|
63
|
+
runner[:idle] = false
|
64
|
+
runner[:io].write(RunFile.new(eval(message.serialize)))
|
65
|
+
end
|
66
|
+
|
67
|
+
# When a runner finishes, it sends the results up to the worker. Then the
|
68
|
+
# worker sends the results up to the master.
|
69
|
+
def relay_results(message, runner)
|
70
|
+
runner[:idle] = true
|
71
|
+
@io.write(Results.new(eval(message.serialize)))
|
72
|
+
end
|
73
|
+
|
74
|
+
# When a master issues a shutdown order, it hits this method, which causes
|
75
|
+
# the worker to send shutdown messages to its runners.
|
76
|
+
def shutdown
|
77
|
+
@running = false
|
78
|
+
trace "Notifying #{@runners.size} Runners of Shutdown"
|
79
|
+
@runners.each do |r|
|
80
|
+
trace "Sending Shutdown to Runner"
|
81
|
+
trace "\t#{r.inspect}"
|
82
|
+
r[:io].write(Shutdown.new)
|
83
|
+
end
|
84
|
+
Thread.exit
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def boot_runners(num_runners) #:nodoc:
|
90
|
+
trace "Booting #{num_runners} Runners"
|
91
|
+
num_runners.times do
|
92
|
+
pipe = Hydra::Pipe.new
|
93
|
+
child = SafeFork.fork do
|
94
|
+
pipe.identify_as_child
|
95
|
+
Hydra::Runner.new(:io => pipe, :verbose => @verbose, :runner_listeners => @runner_event_listeners, :runner_log_file => @runner_log_file )
|
96
|
+
end
|
97
|
+
pipe.identify_as_parent
|
98
|
+
@runners << { :pid => child, :io => pipe, :idle => false }
|
99
|
+
end
|
100
|
+
trace "#{@runners.size} Runners booted"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Continuously process messages
|
104
|
+
def process_messages #:nodoc:
|
105
|
+
trace "Processing Messages"
|
106
|
+
@running = true
|
107
|
+
|
108
|
+
Thread.abort_on_exception = true
|
109
|
+
|
110
|
+
process_messages_from_master
|
111
|
+
process_messages_from_runners
|
112
|
+
|
113
|
+
@listeners.each{|l| l.join }
|
114
|
+
@io.close
|
115
|
+
trace "Done processing messages"
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_messages_from_master
|
119
|
+
@listeners << Thread.new do
|
120
|
+
while @running
|
121
|
+
begin
|
122
|
+
message = @io.gets
|
123
|
+
if message and !message.class.to_s.index("Master").nil?
|
124
|
+
trace "Received Message from Master"
|
125
|
+
trace "\t#{message.inspect}"
|
126
|
+
message.handle(self)
|
127
|
+
else
|
128
|
+
trace "Nothing from Master, Pinging"
|
129
|
+
@io.write Ping.new
|
130
|
+
end
|
131
|
+
rescue IOError => ex
|
132
|
+
trace "Worker lost Master"
|
133
|
+
shutdown
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_messages_from_runners
|
140
|
+
@runners.each do |r|
|
141
|
+
@listeners << Thread.new do
|
142
|
+
while @running
|
143
|
+
begin
|
144
|
+
message = r[:io].gets
|
145
|
+
if message and !message.class.to_s.index("Runner").nil?
|
146
|
+
trace "Received Message from Runner"
|
147
|
+
trace "\t#{message.inspect}"
|
148
|
+
message.handle(self, r)
|
149
|
+
end
|
150
|
+
rescue IOError => ex
|
151
|
+
trace "Worker lost Runner [#{r.inspect}]"
|
152
|
+
Thread.exit
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get the next idle runner
|
160
|
+
def idle_runner #:nodoc:
|
161
|
+
idle_r = nil
|
162
|
+
while idle_r.nil?
|
163
|
+
idle_r = @runners.detect{|runner| runner[:idle]}
|
164
|
+
end
|
165
|
+
return idle_r
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/hydra.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'hydra/trace'
|
2
|
+
require 'hydra/pipe'
|
3
|
+
require 'hydra/ssh'
|
4
|
+
require 'hydra/stdio'
|
5
|
+
require 'hydra/message'
|
6
|
+
require 'hydra/safe_fork'
|
7
|
+
require 'hydra/runner'
|
8
|
+
require 'hydra/worker'
|
9
|
+
require 'hydra/master'
|
10
|
+
require 'hydra/sync'
|
11
|
+
require 'hydra/listener/abstract'
|
12
|
+
require 'hydra/listener/minimal_output'
|
13
|
+
require 'hydra/listener/report_generator'
|
14
|
+
require 'hydra/listener/notifier'
|
15
|
+
require 'hydra/listener/progress_bar'
|
16
|
+
require 'hydra/runner_listener/abstract'
|