xpflow 0.1b
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/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
@@ -0,0 +1,574 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
#
|
4
|
+
# Implementation of core execution engine.
|
5
|
+
#
|
6
|
+
|
7
|
+
$original_argv = [ ]
|
8
|
+
|
9
|
+
module XPFlow
|
10
|
+
|
11
|
+
class Context
|
12
|
+
|
13
|
+
attr_reader :activity_full_name
|
14
|
+
attr_reader :arguments
|
15
|
+
|
16
|
+
constructor :activity_full_name, :arguments
|
17
|
+
|
18
|
+
def short_name
|
19
|
+
return @activity_full_name.split(".").last
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class AbstractActivity
|
25
|
+
|
26
|
+
attr_reader :name
|
27
|
+
attr_reader :opts
|
28
|
+
constructor :name, :opts
|
29
|
+
|
30
|
+
def execute; raise end
|
31
|
+
def info; @opts[:info] end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class ProcessActivity
|
36
|
+
|
37
|
+
include Traverse
|
38
|
+
include Meta
|
39
|
+
|
40
|
+
attr_reader :name
|
41
|
+
attr_reader :args
|
42
|
+
attr_reader :body
|
43
|
+
|
44
|
+
attr_accessor :doc
|
45
|
+
attr_accessor :opts
|
46
|
+
|
47
|
+
constructor :name, :args, :body
|
48
|
+
children :body
|
49
|
+
|
50
|
+
def init
|
51
|
+
# attach checkpoints
|
52
|
+
@body.attach_to_checkpoints(self)
|
53
|
+
@macro = false
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_macro
|
57
|
+
@macro = true
|
58
|
+
end
|
59
|
+
|
60
|
+
def split(node)
|
61
|
+
return @body.split(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute(args)
|
65
|
+
result = Scope.region do |scope|
|
66
|
+
@args.length.times do |i|
|
67
|
+
key = @args[i]
|
68
|
+
scope[key] = args[i]
|
69
|
+
end
|
70
|
+
|
71
|
+
scope[:__activity__] = self
|
72
|
+
full_name = (scope[:__namespace__] + [ @name ]).join(".")
|
73
|
+
scope[:__name__] = Context.new(full_name, args)
|
74
|
+
|
75
|
+
unless opts[:idempotent].nil?
|
76
|
+
scope[:__idempotent__] = opts[:idempotent]
|
77
|
+
end
|
78
|
+
@body.run()
|
79
|
+
end
|
80
|
+
return result
|
81
|
+
end
|
82
|
+
|
83
|
+
def collect_meta(skip)
|
84
|
+
super(skip)
|
85
|
+
@body.collect_meta(skip + 1)
|
86
|
+
end
|
87
|
+
|
88
|
+
def doc_name
|
89
|
+
return doc unless doc.nil?
|
90
|
+
return name.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
"#<#{info}>"
|
95
|
+
end
|
96
|
+
|
97
|
+
def arity
|
98
|
+
return @args.length
|
99
|
+
end
|
100
|
+
|
101
|
+
def info
|
102
|
+
return "Process '#{@name}' with arity = #{arity}"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
class ReturnException < Exception
|
108
|
+
|
109
|
+
attr_reader :value
|
110
|
+
|
111
|
+
def initialize(value)
|
112
|
+
super()
|
113
|
+
@value = value
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class BlockActivity < AbstractActivity
|
118
|
+
|
119
|
+
constructor [ :name, :opts ], :block
|
120
|
+
|
121
|
+
attr_reader :block
|
122
|
+
attr_accessor :doc
|
123
|
+
attr_accessor :opts
|
124
|
+
|
125
|
+
def doc_name
|
126
|
+
return doc unless doc.nil?
|
127
|
+
return name.to_s
|
128
|
+
end
|
129
|
+
|
130
|
+
def execute(args, &block)
|
131
|
+
result = Scope.region do |scope|
|
132
|
+
unless opts[:idempotent].nil?
|
133
|
+
scope[:__idempotent__] = opts[:idempotent]
|
134
|
+
end
|
135
|
+
# puts scope[:__namespace__].inspect
|
136
|
+
scope[:__activity__] = self
|
137
|
+
full_name = (scope[:__namespace__] + [ @name ]).join(".")
|
138
|
+
scope[:__name__] = Context.new(full_name, args)
|
139
|
+
proxy = EngineProxy.new(self, block)
|
140
|
+
proxy.__parent__ = @opts[:__parent__]
|
141
|
+
proxy.execute_proxy(args, @block)
|
142
|
+
end
|
143
|
+
return result
|
144
|
+
end
|
145
|
+
|
146
|
+
def arity
|
147
|
+
return @block.arity
|
148
|
+
end
|
149
|
+
|
150
|
+
def info
|
151
|
+
block_info = XPFlow::block_info(@block)
|
152
|
+
return "Activity '#{@name}' with args: { #{block_info} }"
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
return info()
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
class EngineProxy
|
162
|
+
|
163
|
+
attr_accessor :__parent__
|
164
|
+
|
165
|
+
constructor :activity, :block
|
166
|
+
|
167
|
+
def log(*msgs)
|
168
|
+
instance_name = Scope.current[:__name__].activity_full_name
|
169
|
+
msg = "Activity %s: %s" % [ instance_name.green, msgs.join('') ]
|
170
|
+
engine.log(msg)
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def engine
|
175
|
+
return Scope.engine
|
176
|
+
end
|
177
|
+
|
178
|
+
def collect(v)
|
179
|
+
engine.test_lib.invoke(:collect, v)
|
180
|
+
end
|
181
|
+
|
182
|
+
def __block__
|
183
|
+
return @block
|
184
|
+
end
|
185
|
+
|
186
|
+
def execute_proxy(args, block)
|
187
|
+
begin
|
188
|
+
return self.instance_exec(*args, &block)
|
189
|
+
rescue ReturnException => e
|
190
|
+
return e.value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def system(cmd)
|
195
|
+
run("__core__.system", cmd)
|
196
|
+
end
|
197
|
+
|
198
|
+
def parent(*args)
|
199
|
+
raise "No parent activity for #{@activity.name}" if __parent__.nil?
|
200
|
+
return __parent__.execute(args)
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_result(x)
|
204
|
+
Scope.current[:__result__] = x
|
205
|
+
end
|
206
|
+
|
207
|
+
def result
|
208
|
+
# gives result of a previous execution
|
209
|
+
x = Scope.current.get(:__result__, nil)
|
210
|
+
return x
|
211
|
+
end
|
212
|
+
|
213
|
+
def pass(value = nil)
|
214
|
+
# a tricky way to simulate 'return' inside activities
|
215
|
+
raise ReturnException.new(value)
|
216
|
+
end
|
217
|
+
|
218
|
+
def run(name, *args, &block)
|
219
|
+
r = ActivityRun.run_activity_block(name) do |activity|
|
220
|
+
activity.execute(args)
|
221
|
+
end
|
222
|
+
return r
|
223
|
+
end
|
224
|
+
|
225
|
+
def execute(*args)
|
226
|
+
return run(:"nodes.execute", *args)
|
227
|
+
end
|
228
|
+
|
229
|
+
def execute_one(*args)
|
230
|
+
return run(:"nodes.execute_one", *args)
|
231
|
+
end
|
232
|
+
|
233
|
+
def execute_many(*args)
|
234
|
+
return run(:"nodes.execute_many", *args)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
class BasicLibrary < Library
|
240
|
+
|
241
|
+
# library with initialized core functionality
|
242
|
+
|
243
|
+
attr_reader :runtime
|
244
|
+
attr_reader :test_lib
|
245
|
+
|
246
|
+
def initialize()
|
247
|
+
super()
|
248
|
+
inject_library('__data__', DataLibrary.new)
|
249
|
+
inject_library('__core__', CoreLibrary.new)
|
250
|
+
|
251
|
+
@runtime = RuntimeLibrary.new
|
252
|
+
import_library('runtime', @runtime)
|
253
|
+
|
254
|
+
@test_lib = TestLibrary.new
|
255
|
+
@getset_lib = GetSetLibrary.new
|
256
|
+
@collection_lib = CollectionLibrary.new
|
257
|
+
|
258
|
+
@getset_lib.set(:pool, 16) ## default parallelism
|
259
|
+
|
260
|
+
inject_library('__test__', @test_lib)
|
261
|
+
inject_library('__getset__', @getset_lib)
|
262
|
+
inject_library('collection', @collection_lib)
|
263
|
+
|
264
|
+
@nodes_lib = NodesLibrary.new
|
265
|
+
inject_library('nodes', @nodes_lib)
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
class Engine < BasicLibrary
|
271
|
+
|
272
|
+
attr_reader :dumper
|
273
|
+
|
274
|
+
attr_reader :nodes_manager
|
275
|
+
attr_reader :main_directory
|
276
|
+
attr_reader :opts
|
277
|
+
|
278
|
+
def initialize(conf = {})
|
279
|
+
super()
|
280
|
+
|
281
|
+
@conf = {
|
282
|
+
:experiment_class => Experiment,
|
283
|
+
:dumper_class => FileDumper
|
284
|
+
}.merge(conf)
|
285
|
+
|
286
|
+
if ENV.key?("TESTING") or conf[:testing] == true
|
287
|
+
@conf[:experiment_class] = ExperimentBlackHole
|
288
|
+
@conf[:dumper_class] = MemoryDumper
|
289
|
+
end
|
290
|
+
|
291
|
+
@lock = Mutex.new
|
292
|
+
@cv = ConditionVariable.new
|
293
|
+
|
294
|
+
@scope = Scope.push
|
295
|
+
|
296
|
+
@opts = nil
|
297
|
+
@config = Options.defaults
|
298
|
+
|
299
|
+
@activity_ids = {}
|
300
|
+
|
301
|
+
@inline_process_counter = 0
|
302
|
+
@inline_processes = {}
|
303
|
+
|
304
|
+
@error_handlers = []
|
305
|
+
@finish_handlers = []
|
306
|
+
@after_handlers = []
|
307
|
+
|
308
|
+
@logging = Logging.new
|
309
|
+
@logging.add($console, :console)
|
310
|
+
|
311
|
+
@dumper = @conf[:dumper_class].new
|
312
|
+
|
313
|
+
# username = ENV["USER"]
|
314
|
+
# @main_directory = DirectoryManager.new("/tmp/xpflow-#{username}") # TODO
|
315
|
+
@main_directory = DirectoryManager.new(nil)
|
316
|
+
@nodes_manager = NodesManager.new(@main_directory.subdir("nodes"))
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
def init_from_options(opts)
|
321
|
+
@config = opts.config
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
def getset
|
326
|
+
return @getset_lib
|
327
|
+
end
|
328
|
+
|
329
|
+
def console
|
330
|
+
return @logging.get(:console)
|
331
|
+
end
|
332
|
+
|
333
|
+
### CONCURRENCY
|
334
|
+
|
335
|
+
def synchronized
|
336
|
+
@lock.synchronize do
|
337
|
+
yield
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def wait
|
342
|
+
@cv.wait(@lock)
|
343
|
+
end
|
344
|
+
|
345
|
+
def broadcast
|
346
|
+
@cv.broadcast
|
347
|
+
end
|
348
|
+
|
349
|
+
def collected
|
350
|
+
return @test_lib.values
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
|
355
|
+
|
356
|
+
### HANDLERS
|
357
|
+
|
358
|
+
def call_error_handlers(e)
|
359
|
+
@error_handlers.each do |args, block|
|
360
|
+
block.call(e, *args)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def call_finish_handlers
|
365
|
+
hs = @finish_handlers
|
366
|
+
verbose("Running #{hs.length} finalizers") if hs.length > 0
|
367
|
+
hs.each do |args, block|
|
368
|
+
block.call(*args)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def call_after_handlers
|
373
|
+
@after_handlers.each do |args, block|
|
374
|
+
block.call(*args)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def on_error(*args, &block)
|
379
|
+
@error_handlers.push([args, block])
|
380
|
+
end
|
381
|
+
|
382
|
+
def on_finish(*args, &block)
|
383
|
+
@finish_handlers.push([args, block])
|
384
|
+
end
|
385
|
+
|
386
|
+
def on_after(*args, &block)
|
387
|
+
@after_handlers.push([args, block])
|
388
|
+
end
|
389
|
+
|
390
|
+
def _execute(cmd)
|
391
|
+
out = %x(#{cmd} 2> /dev/null).strip
|
392
|
+
return [ out, $?.exitstatus ]
|
393
|
+
end
|
394
|
+
|
395
|
+
def _get_git_tag
|
396
|
+
me = realize(__FILE__)
|
397
|
+
my_dir = File.dirname(me)
|
398
|
+
has_git = false
|
399
|
+
|
400
|
+
_, code = _execute("git --version")
|
401
|
+
return "(git is not installed)" if code != 0
|
402
|
+
|
403
|
+
status, code = _execute("cd #{my_dir} && git status --porcelain")
|
404
|
+
return "(not git repo)" if code != 0
|
405
|
+
|
406
|
+
tag, code = _execute("cd #{my_dir} && git rev-parse --short HEAD")
|
407
|
+
return "(no tag?)" if code != 0
|
408
|
+
|
409
|
+
if status == ""
|
410
|
+
return tag
|
411
|
+
else
|
412
|
+
return "#{tag} (with changes)"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def list_variables
|
417
|
+
log "Variable list follows:"
|
418
|
+
if $variables.nil? == false
|
419
|
+
$variables.each_pair do |k, v|
|
420
|
+
log " #{k} = #{v}"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
log "End of variable list."
|
424
|
+
end
|
425
|
+
|
426
|
+
### EXECUTION
|
427
|
+
|
428
|
+
def execute(name, *args)
|
429
|
+
# IO.write("./.xpflow-graph.yaml", self.traversal_graph.to_yaml) # TODO
|
430
|
+
r = nil
|
431
|
+
Scope.current[:__engine__] = self
|
432
|
+
Scope.current[:__experiment__] = @conf[:experiment_class].new("__root__", "./results").install()
|
433
|
+
Scope.current[:__library__] = self
|
434
|
+
Scope.current[:__namespace__] = []
|
435
|
+
begin
|
436
|
+
log("Execution started.")
|
437
|
+
log("Cmdline: " + $original_argv.inspect)
|
438
|
+
log("Git tag: " + _get_git_tag())
|
439
|
+
log("Temporary path is #{main_directory.path}")
|
440
|
+
list_variables()
|
441
|
+
t = Timer.measure do
|
442
|
+
r = ActivityRun.run_activity_block(name) do |activity|
|
443
|
+
activity.execute(args)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
log("Execution finished (#{t.with_ms} s).")
|
447
|
+
rescue RunError => e
|
448
|
+
log("Execution failed miserably.")
|
449
|
+
call_error_handlers(e)
|
450
|
+
raise
|
451
|
+
ensure
|
452
|
+
call_finish_handlers()
|
453
|
+
call_after_handlers()
|
454
|
+
end
|
455
|
+
return r
|
456
|
+
end
|
457
|
+
|
458
|
+
def execute_with_tb(*args)
|
459
|
+
ok = false
|
460
|
+
begin
|
461
|
+
v = execute(*args)
|
462
|
+
ok = true
|
463
|
+
rescue RunError => e
|
464
|
+
XPFlow::show_stacktrace(e)
|
465
|
+
end
|
466
|
+
return [ v, ok ]
|
467
|
+
end
|
468
|
+
|
469
|
+
def execute_quiet(*args)
|
470
|
+
ok = true
|
471
|
+
begin
|
472
|
+
execute(*args)
|
473
|
+
rescue RunError => e
|
474
|
+
ok = false
|
475
|
+
end
|
476
|
+
return ok
|
477
|
+
end
|
478
|
+
|
479
|
+
### LOGGING
|
480
|
+
|
481
|
+
def log(msg, label = :normal)
|
482
|
+
return if label == :none
|
483
|
+
@logging.log(msg, label) if @config[:labels].include?(label)
|
484
|
+
end
|
485
|
+
|
486
|
+
def debug(msg)
|
487
|
+
verbose(msg)
|
488
|
+
end
|
489
|
+
|
490
|
+
def verbose(msg, paranoic = false)
|
491
|
+
log(msg, paranoic ? :paranoic : :verbose)
|
492
|
+
end
|
493
|
+
|
494
|
+
def paranoic(msg)
|
495
|
+
log(msg, :paranoic)
|
496
|
+
end
|
497
|
+
|
498
|
+
### ACTIVITY TRACKING
|
499
|
+
|
500
|
+
def activity_id(name)
|
501
|
+
synchronized do
|
502
|
+
@activity_ids[name] = 0 unless @activity_ids.key?(name)
|
503
|
+
@activity_ids[name] += 1
|
504
|
+
@activity_ids[name]
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
def activity_period(name, opts = {}, &block)
|
509
|
+
label = opts[:log_level] || :paranoic
|
510
|
+
gantt = (opts[:gantt] == true)
|
511
|
+
log("Started activity %s." % [ name.green ], label)
|
512
|
+
begin
|
513
|
+
@runtime.invoke(:event, [ :start_activity, { :name => name, :time => Time.now } ]) if gantt
|
514
|
+
t = Timer.measure(&block)
|
515
|
+
log("Finished activity %s (%s s)." % [ name.green, t.with_ms ], label)
|
516
|
+
return t.value
|
517
|
+
rescue => e
|
518
|
+
verbose("Activity %s failed: %s" % [ name.green, e.to_s ])
|
519
|
+
raise
|
520
|
+
ensure
|
521
|
+
@runtime.invoke(:event, [ :finish_activity, { :name => name, :time => Time.now } ]) if gantt
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
### CONFIGURATION
|
526
|
+
|
527
|
+
def config(label)
|
528
|
+
value = @config[label]
|
529
|
+
yield(value) if (value && block_given?)
|
530
|
+
return value
|
531
|
+
end
|
532
|
+
|
533
|
+
|
534
|
+
### Command dispatching
|
535
|
+
|
536
|
+
def execute_run(filename, activity)
|
537
|
+
|
538
|
+
$entry_point = realpath(filename)
|
539
|
+
|
540
|
+
main_activity = get_activity_or_nil(activity)
|
541
|
+
|
542
|
+
if main_activity.nil?
|
543
|
+
Kernel.puts "There is no activity :#{activity} in the namespace. Quitting."
|
544
|
+
exit 1
|
545
|
+
end
|
546
|
+
|
547
|
+
Kernel.srand(var(:seed, :int, 31415926535))
|
548
|
+
|
549
|
+
res = execute_with_tb(activity)
|
550
|
+
|
551
|
+
@config[:after].each do |runinfo|
|
552
|
+
activity = @runtime.get_activity_or_nil(runinfo.name.to_s)
|
553
|
+
activity.execute(runinfo.args)
|
554
|
+
end
|
555
|
+
|
556
|
+
return res
|
557
|
+
|
558
|
+
end
|
559
|
+
|
560
|
+
def execute_workflow(filename, activity)
|
561
|
+
return RuntimeLibrary.save_workflow(@config[:output], self, activity)
|
562
|
+
end
|
563
|
+
|
564
|
+
end
|
565
|
+
|
566
|
+
class TestEngine < Engine
|
567
|
+
|
568
|
+
def initialize
|
569
|
+
super(:testing => true)
|
570
|
+
end
|
571
|
+
|
572
|
+
end
|
573
|
+
|
574
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
# This code is not used anywhere
|
3
|
+
# runs ensemble of processes in parallel
|
4
|
+
|
5
|
+
class Procee
|
6
|
+
|
7
|
+
attr_reader :pid
|
8
|
+
attr_accessor :ident
|
9
|
+
|
10
|
+
def initialize(cmd, out, err = nil)
|
11
|
+
@cmd = cmd
|
12
|
+
@out = File.open(out, "w")
|
13
|
+
@err = err
|
14
|
+
@pipe = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def spawn
|
18
|
+
r, w = @pipe = IO.pipe
|
19
|
+
opts = { :out => w, :in => "/dev/null" }
|
20
|
+
opts[2] = @err.nil? ? "/dev/null" : @err
|
21
|
+
@pid = Process.spawn(@cmd, opts) # r, w is closed in the child process.
|
22
|
+
w.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def handler
|
26
|
+
return @pipe.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def consume(bytes)
|
30
|
+
@out.write(bytes)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class Ensemble
|
36
|
+
|
37
|
+
def defaults
|
38
|
+
{
|
39
|
+
:timeout => 1.0,
|
40
|
+
:buffer => 256
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(arr = nil, opts = {})
|
45
|
+
arr = [] if arr.nil?
|
46
|
+
@procs = []
|
47
|
+
@opts = defaults.merge(opts)
|
48
|
+
arr.each { |x| add(*x) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def add(cmd, out, err = nil)
|
52
|
+
p = Procee.new(cmd, out, err)
|
53
|
+
p.ident = @procs.length
|
54
|
+
@procs.push(p)
|
55
|
+
end
|
56
|
+
|
57
|
+
def launch()
|
58
|
+
running = @procs.length
|
59
|
+
pipes = {}
|
60
|
+
@procs.each do |p|
|
61
|
+
p.spawn()
|
62
|
+
pipes[p.handler] = p
|
63
|
+
end
|
64
|
+
|
65
|
+
closed = []
|
66
|
+
|
67
|
+
while running > 0
|
68
|
+
arrays = IO.select(pipes.keys, [], [], @opts[:timeout])
|
69
|
+
break if arrays.nil?
|
70
|
+
ready = arrays.first
|
71
|
+
ready.each do |r|
|
72
|
+
p = pipes[r]
|
73
|
+
begin
|
74
|
+
bytes = r.sysread(@opts[:buffer])
|
75
|
+
p.consume(bytes)
|
76
|
+
rescue EOFError
|
77
|
+
running -= 1
|
78
|
+
pipes.delete(r)
|
79
|
+
closed.push(p.ident)
|
80
|
+
# puts "#{p.pid} finished."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# waiting for all of them to finish
|
86
|
+
|
87
|
+
pidmap = {}
|
88
|
+
@procs.each { |p| pidmap[p.pid] = p.ident }
|
89
|
+
pids = @procs.map { |p| p.pid }
|
90
|
+
|
91
|
+
status = {}
|
92
|
+
|
93
|
+
iterations = 0
|
94
|
+
delta = 0.5
|
95
|
+
while pids.length > 0 && iterations < @opts[:timeout]
|
96
|
+
p = Process.waitpid(-1, Process::WNOHANG)
|
97
|
+
if p.nil?
|
98
|
+
Kernel.sleep(delta)
|
99
|
+
iterations += delta
|
100
|
+
next
|
101
|
+
end
|
102
|
+
status[pidmap[p]] = $?
|
103
|
+
pids.delete(p)
|
104
|
+
end
|
105
|
+
|
106
|
+
pids.each do |p|
|
107
|
+
Process.kill(:KILL, p)
|
108
|
+
Process.waitpid(p)
|
109
|
+
status[pidmap[p]] = $?
|
110
|
+
end
|
111
|
+
|
112
|
+
result = {}
|
113
|
+
|
114
|
+
status.each_pair do |ident, s|
|
115
|
+
x = result[ident] = {
|
116
|
+
:status => s.exitstatus,
|
117
|
+
:blocked => !closed.include?(ident),
|
118
|
+
:signal => s.termsig
|
119
|
+
}
|
120
|
+
x[:ok] = !x[:blocked] && x[:status] == 0 && x[:signal].nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
puts result.inspect
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
if __FILE__ == $0
|
129
|
+
e = Ensemble.new([
|
130
|
+
[ "echo -n 456 1>&2", "/tmp/1", "/tmp/err" ],
|
131
|
+
[ "echo 123", "/tmp/2" ],
|
132
|
+
[ "echo EDF", "/tmp/3" ] ])
|
133
|
+
|
134
|
+
r = e.launch
|
135
|
+
end
|