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.
Files changed (74) hide show
  1. data/bin/xpflow +96 -0
  2. data/lib/colorado.rb +198 -0
  3. data/lib/json/add/core.rb +243 -0
  4. data/lib/json/add/rails.rb +8 -0
  5. data/lib/json/common.rb +423 -0
  6. data/lib/json/editor.rb +1369 -0
  7. data/lib/json/ext.rb +28 -0
  8. data/lib/json/pure/generator.rb +442 -0
  9. data/lib/json/pure/parser.rb +320 -0
  10. data/lib/json/pure.rb +15 -0
  11. data/lib/json/version.rb +8 -0
  12. data/lib/json.rb +62 -0
  13. data/lib/mime/types.rb +881 -0
  14. data/lib/mime-types.rb +3 -0
  15. data/lib/restclient/abstract_response.rb +106 -0
  16. data/lib/restclient/exceptions.rb +193 -0
  17. data/lib/restclient/net_http_ext.rb +55 -0
  18. data/lib/restclient/payload.rb +235 -0
  19. data/lib/restclient/raw_response.rb +34 -0
  20. data/lib/restclient/request.rb +316 -0
  21. data/lib/restclient/resource.rb +169 -0
  22. data/lib/restclient/response.rb +24 -0
  23. data/lib/restclient.rb +174 -0
  24. data/lib/xpflow/bash.rb +341 -0
  25. data/lib/xpflow/bundle.rb +113 -0
  26. data/lib/xpflow/cmdline.rb +249 -0
  27. data/lib/xpflow/collection.rb +122 -0
  28. data/lib/xpflow/concurrency.rb +79 -0
  29. data/lib/xpflow/data.rb +393 -0
  30. data/lib/xpflow/dsl.rb +816 -0
  31. data/lib/xpflow/engine.rb +574 -0
  32. data/lib/xpflow/ensemble.rb +135 -0
  33. data/lib/xpflow/events.rb +56 -0
  34. data/lib/xpflow/experiment.rb +65 -0
  35. data/lib/xpflow/exts/facter.rb +30 -0
  36. data/lib/xpflow/exts/g5k.rb +931 -0
  37. data/lib/xpflow/exts/g5k_use.rb +50 -0
  38. data/lib/xpflow/exts/gui.rb +140 -0
  39. data/lib/xpflow/exts/model.rb +155 -0
  40. data/lib/xpflow/graph.rb +1603 -0
  41. data/lib/xpflow/graph_xpflow.rb +251 -0
  42. data/lib/xpflow/import.rb +196 -0
  43. data/lib/xpflow/library.rb +349 -0
  44. data/lib/xpflow/logging.rb +153 -0
  45. data/lib/xpflow/manager.rb +147 -0
  46. data/lib/xpflow/nodes.rb +1250 -0
  47. data/lib/xpflow/runs.rb +773 -0
  48. data/lib/xpflow/runtime.rb +125 -0
  49. data/lib/xpflow/scope.rb +168 -0
  50. data/lib/xpflow/ssh.rb +186 -0
  51. data/lib/xpflow/stat.rb +50 -0
  52. data/lib/xpflow/stdlib.rb +381 -0
  53. data/lib/xpflow/structs.rb +369 -0
  54. data/lib/xpflow/taktuk.rb +193 -0
  55. data/lib/xpflow/templates/ssh-config.basic +14 -0
  56. data/lib/xpflow/templates/ssh-config.inria +18 -0
  57. data/lib/xpflow/templates/ssh-config.proxy +13 -0
  58. data/lib/xpflow/templates/taktuk +6590 -0
  59. data/lib/xpflow/templates/utils/batch +4 -0
  60. data/lib/xpflow/templates/utils/bootstrap +12 -0
  61. data/lib/xpflow/templates/utils/hostname +3 -0
  62. data/lib/xpflow/templates/utils/ping +3 -0
  63. data/lib/xpflow/templates/utils/rsync +12 -0
  64. data/lib/xpflow/templates/utils/scp +17 -0
  65. data/lib/xpflow/templates/utils/scp_many +8 -0
  66. data/lib/xpflow/templates/utils/ssh +3 -0
  67. data/lib/xpflow/templates/utils/ssh-interactive +4 -0
  68. data/lib/xpflow/templates/utils/taktuk +19 -0
  69. data/lib/xpflow/threads.rb +187 -0
  70. data/lib/xpflow/utils.rb +569 -0
  71. data/lib/xpflow/visual.rb +230 -0
  72. data/lib/xpflow/with_g5k.rb +7 -0
  73. data/lib/xpflow.rb +349 -0
  74. 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