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,369 @@
1
+ # encoding: UTF-8
2
+
3
+ #
4
+ # Various structures/classes used everywhere.
5
+ #
6
+
7
+ module XPFlow
8
+
9
+ module Operations
10
+ def ==(x); XPFlow::BinaryOp.new('==', self, x) end
11
+ def +(x); XPFlow::BinaryOp.new('+', self, x) end
12
+ def *(x); XPFlow::BinaryOp.new('*', self, x) end
13
+ def /(x); XPFlow::BinaryOp.new('/', self, x) end
14
+ def -(x); XPFlow::BinaryOp.new('-', self, x) end
15
+ def <(x); XPFlow::BinaryOp.new('<', self, x) end
16
+ def >(x); XPFlow::BinaryOp.new('>', self, x) end
17
+ def <=(x); XPFlow::BinaryOp.new('<=', self, x) end
18
+ def >=(x); XPFlow::BinaryOp.new('>=', self, x) end
19
+ def -@; XPFlow::UnaryOp.new('-@', self) end
20
+ def &(x); XPFlow::BinaryOp.new('&', self, x) end
21
+ def |(x); XPFlow::BinaryOp.new('|', self, x) end
22
+ def not; XPFlow::NegOp.new(self) end
23
+
24
+ def index(i); XPFlow::IndexOp.new(self, i) end
25
+
26
+ end
27
+
28
+ module Meta
29
+
30
+ def meta
31
+ if @frame
32
+ return @frame
33
+ else
34
+ return Frame.new('<unknown>', '<unknown>')
35
+ end
36
+ end
37
+
38
+ def collect_meta(skip = 0)
39
+ original = caller(skip).first
40
+ x = /^(.+):(\d+)/.match(original)
41
+ file, line = x.captures
42
+ @frame = Frame.new(file, line.to_i, self)
43
+ end
44
+
45
+ end
46
+
47
+ class Visiter
48
+
49
+ attr_reader :values
50
+
51
+ def initialize
52
+ @values = []
53
+ end
54
+
55
+ def collect(x)
56
+ @values.push(x)
57
+ end
58
+ end
59
+
60
+ module Traverse
61
+
62
+ def _visit(ctx, &block)
63
+ # puts self.class
64
+ results = __children__.map { |x| x._visit(ctx, &block) }
65
+ return ctx.instance_exec(self, results, &block)
66
+ end
67
+
68
+ def visit(&block)
69
+ ctx = Visiter.new
70
+ _visit(ctx, &block)
71
+ return ctx.values
72
+ end
73
+
74
+ def object_key
75
+ return @key
76
+ end
77
+
78
+ def _workflow(o)
79
+ h = { :type => o.class.to_s.split("::").last }
80
+ key = o.object_key()
81
+ h[:key] = key unless key.nil?
82
+ if o.respond_to?(:workflow_value)
83
+ return o.workflow_value
84
+ end
85
+ o.__children_hash__.each do |k, v|
86
+ if h.key?(k)
87
+ raise "Child '#{k}' exists in workflow. Please change it."
88
+ end
89
+ if v.respond_to?(:workflow)
90
+ h[k] = v.workflow
91
+ elsif v.is_a?(Array)
92
+ h[k] = v.map { |x| _workflow(x) }
93
+ else
94
+ h[k] = "Undefined for class #{v.class}"
95
+ end
96
+ end
97
+ return h
98
+ end
99
+
100
+ def workflow
101
+ return _workflow(self)
102
+ end
103
+
104
+ def vars_uses
105
+ # returns a hash mapping a variable in the workflow
106
+ # to list of nodes that use it
107
+ uses = visit do |node, children|
108
+ vs = children.select { |x| x.is_a?(XPVariable) }
109
+ vs = Hash[vs.map { |x| [ x.key, node ] }]
110
+ collect(vs) if vs.length > 0
111
+ node
112
+ end
113
+ # uses contains an array of hashes that must be merged
114
+ # every [k,v] is [variable, node that uses it]
115
+ summary = {}
116
+ uses.each do |h|
117
+ h.each do |key, node|
118
+ summary[key] = [] unless summary.key?(key)
119
+ summary[key].push(node)
120
+ end
121
+ end
122
+ summary
123
+ end
124
+
125
+ def vars
126
+ # returns variables used in that workflow
127
+ return vars_uses.keys
128
+ end
129
+
130
+ def declarations
131
+ # returns a hash that maps all variables
132
+ # to the nodes that define them
133
+ decls = visit do |node, children|
134
+ collect({ node.key => node }) if node.is_a?(AbstractRun)
135
+ collect(node.__declarations__) if node.respond_to?(:__declarations__)
136
+ end
137
+ return decls.reduce({}) { |x, y| x.merge(y) }
138
+ end
139
+
140
+ def vars_uses_declarations
141
+ # like 'declarations' but only shows
142
+ # declarations of variables that are used in that workflow
143
+ vs = vars()
144
+ ds = declarations()
145
+ # iteration variables will show up as nils
146
+ return Hash[ vs.map { |x| [x, ds[x]] } ]
147
+ end
148
+
149
+ end
150
+
151
+ class ActivityList
152
+
153
+ include Traverse
154
+ children :activities
155
+ constructor :activities
156
+
157
+ end
158
+
159
+ class Frame
160
+
161
+ attr_reader :file
162
+ attr_reader :line
163
+ attr_reader :obj
164
+
165
+ def initialize(file, line, obj = nil)
166
+ @file = file
167
+ @line = line
168
+ @obj = obj
169
+ end
170
+
171
+ def location
172
+ return '%s:%s' % [ @file, @line ]
173
+ end
174
+
175
+ def location_long
176
+ return '%s at line %s' % [ @file, @line ]
177
+ end
178
+
179
+ def to_s
180
+ return "<Frame: #{location}>"
181
+ end
182
+ end
183
+
184
+ class FakeScope
185
+ # a faked scope for evaluate_offline
186
+ end
187
+
188
+ class XPValue
189
+
190
+ include Operations
191
+ include Traverse
192
+
193
+ def self.flatten(obj)
194
+ return obj.flatten if obj.is_a?(XPOp)
195
+ return obj if obj.is_a?(XPValue)
196
+ raise if obj.is_a?(AbstractRun)
197
+ return XPList.new(obj.map { |x| self.flatten(x) }) if obj.is_a?(Array)
198
+ return XPHash.new(obj.map { |k, v| [ k, self.flatten(v) ] }) if obj.is_a?(Hash)
199
+ return XPVariable.new(obj.key) if obj.is_a?(DSLVariable)
200
+ return XPConst.new(obj)
201
+ end
202
+
203
+ def evaluate(scope); raise end
204
+
205
+ def evaluate_offline
206
+ begin
207
+ return self.evaluate(FakeScope.new)
208
+ rescue NoMethodError => e
209
+ return nil
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+
216
+ class XPConst < XPValue
217
+
218
+ constructor :obj
219
+ children
220
+
221
+ def evaluate(scope)
222
+ o = @obj
223
+ if o.is_a?(String)
224
+ o = DSLVariable.replace(o, scope)
225
+ o = XPOp.replace(o, scope)
226
+ end
227
+ return o
228
+ end
229
+
230
+ def workflow_value # TODO, handle strings
231
+ return @obj
232
+ end
233
+
234
+ end
235
+
236
+ class XPList < XPValue
237
+
238
+ constructor :obj
239
+ children :obj
240
+
241
+ def evaluate(scope)
242
+ @obj.map { |x| x.evaluate(scope) }
243
+ end
244
+
245
+ end
246
+
247
+ class XPHash < XPValue
248
+
249
+ constructor :obj
250
+
251
+ def evaluate(scope)
252
+ o = Hash[@obj]
253
+ Hash[o.map { |k, v| [k, v.evaluate(scope) ]}]
254
+ end
255
+
256
+ def __children__
257
+ return @obj.map { |k, v| v }
258
+ end
259
+
260
+ end
261
+
262
+ class XPVariable < XPValue
263
+
264
+ attr_reader :key
265
+ constructor :key
266
+ children
267
+
268
+ def evaluate(scope)
269
+ scope[@key]
270
+ end
271
+
272
+ def workflow_value
273
+ return "var(#{@key})"
274
+ end
275
+
276
+ end
277
+
278
+
279
+
280
+ class XPOp < XPValue
281
+
282
+ @@instances = {} # TODO: this may consume memory
283
+
284
+ def flatten
285
+ raise
286
+ end
287
+
288
+ def self.replace(s, scope)
289
+ out = s.gsub(/Op\[-@@@@-(\d+)-@@@@-\]/) do |m|
290
+ identifier = $1.to_i
291
+ @@instances[identifier].evaluate(scope)
292
+ end
293
+ return out
294
+ end
295
+
296
+ def to_s
297
+ @@instances[object_id()] = self
298
+ return "Op[-@@@@-#{object_id()}-@@@@-]"
299
+ end
300
+
301
+ end
302
+
303
+ class NegOp < XPOp
304
+
305
+ constructor :arg
306
+ children :arg
307
+
308
+ def evaluate(scope)
309
+ return !@arg.evaluate(scope)
310
+ end
311
+
312
+ def flatten
313
+ return NegOp.new(XPValue.flatten(@arg))
314
+ end
315
+
316
+ end
317
+
318
+ class UnaryOp < XPOp
319
+
320
+ constructor :type, :arg
321
+ children :arg
322
+
323
+ def evaluate(scope)
324
+ x = @arg.evaluate(scope)
325
+ return x.send(@type)
326
+ end
327
+
328
+ def flatten
329
+ return UnaryOp.new(@type, XPValue.flatten(@arg))
330
+ end
331
+
332
+ end
333
+
334
+ class BinaryOp < XPOp
335
+
336
+ constructor :type, :left, :right
337
+ children :left, :right
338
+
339
+ def evaluate(scope)
340
+ a = @left.evaluate(scope)
341
+ b = @right.evaluate(scope)
342
+ return a.send(@type, b)
343
+ end
344
+
345
+ def flatten
346
+ return BinaryOp.new(@type, XPValue.flatten(@left), XPValue.flatten(@right))
347
+ end
348
+
349
+ end
350
+
351
+ class IndexOp < XPOp
352
+
353
+ constructor :arg, :index
354
+ children :arg
355
+
356
+ def evaluate(scope)
357
+ v = @arg.evaluate(scope)
358
+ i = @index.evaluate(scope)
359
+ return v[i]
360
+ end
361
+
362
+ def flatten
363
+ return IndexOp.new(XPValue.flatten(@arg), XPValue.flatten(@index))
364
+ end
365
+
366
+ end
367
+
368
+ end
369
+
@@ -0,0 +1,193 @@
1
+
2
+ require 'thread'
3
+ require 'securerandom'
4
+
5
+ module XPFlow
6
+
7
+ class TakTukRun
8
+
9
+ @@mutex = Mutex.new
10
+ @@counter = 0
11
+ @@hash = SecureRandom.hex(16)
12
+
13
+ attr_reader :filename
14
+
15
+ def initialize(taktuk, nodes, opts = {})
16
+ @taktuk = taktuk
17
+ @nodes = nodes
18
+ @opts = { :escape => 1 }.merge(opts)
19
+ _write_run_file()
20
+ end
21
+
22
+ def self.get_uniq
23
+ @@mutex.synchronize do
24
+ @@counter += 1
25
+ @@counter
26
+ end
27
+ end
28
+
29
+ def _ensure_file(key)
30
+ @opts[key] = %(mktemp).strip unless @opts.key?(key)
31
+ return @opts[key]
32
+ end
33
+
34
+ def filename
35
+ return _ensure_file(:filename)
36
+ end
37
+
38
+ def stdout
39
+ return _ensure_file(:stdout)
40
+ end
41
+
42
+ def stderr
43
+ return _ensure_file(:stderr)
44
+ end
45
+
46
+ def grouped_nodes
47
+ # gives a hash of lists that contain all nodes
48
+ # merged together at each key - they will be together in taktuk execution
49
+ # for better locality etc.
50
+ h = Hash.new { |h, k| h[k] = [] }
51
+ @nodes.each { |x| h[x.group].push(x) }
52
+ return h
53
+ end
54
+
55
+ def nodes_mapping
56
+ # gives a mapping from .userhost to a list of nodes
57
+ # this handles the duplicated nodes
58
+ h = Hash.new { |h, k| h[k] = [] }
59
+ @nodes.each { |x| h[x.userhost].push(x) }
60
+ return h
61
+ end
62
+
63
+ def _write_run_file
64
+ File.open(filename, "w") do |f|
65
+ f.puts("#!/bin/bash")
66
+ f.puts("set -eu")
67
+ f.puts("TAKTUK=${TAKTUK:-#{@taktuk}}")
68
+ f.puts("$TAKTUK #{@opts[:propagate] ? '-s' : ''} \\")
69
+ grouped_nodes().each_pair do |group, ns|
70
+ # f.puts("-b \\") # -b and -e cause bugs!!! grrrh!! TakTuk hangs!
71
+ ns.each { |x| f.puts(" -m #{x.userhost} \\") }
72
+ # f.puts("-e \\")
73
+ end
74
+ f.puts('"$@"')
75
+ end
76
+ return self
77
+ end
78
+
79
+ def execute_raw(cmd, opts = {})
80
+ # execute the command at the lowest level
81
+ # return stdout & stderr, but throw an exception if taktuk failed
82
+ original_cmd = cmd
83
+ opts = @opts.merge(opts)
84
+ opts[:escape].times { cmd = Shellwords.escape(cmd) }
85
+
86
+ real_command = "bash #{filename} synchronize broadcast exec [ #{cmd} ]"
87
+
88
+ output = %x(#{real_command} < /dev/null 1> #{stdout} 2> #{stderr})
89
+
90
+ raise ExecutionError.new("Command '#{original_cmd}' returned error (see #{stdout} and #{stderr})!") if $?.exitstatus != 0
91
+
92
+ return [ stdout, stderr ]
93
+ end
94
+
95
+ def execute_shell(cmd, opts = {})
96
+ # executes 'cmd' in a shell, so shell variables and redirections are possible
97
+
98
+ out, err = execute_raw(cmd, opts)
99
+
100
+ # parsing of the output
101
+ # EXAMPLE: root@172.16.0.3-2: hostname > /tmp/nazwa (3825): status > Exited with status 0
102
+
103
+ status_exp = /^(\S+)-(\d+): .+\((\d+)\): status > Exited with status (\d+)$/
104
+ output_exp = /^(\S+)-(\d+): .+\((\d+)\): (output|error) > (.+)$/
105
+
106
+ outputs = Hash.new { |h, k| h[k] = [] }
107
+ errputs = Hash.new { |h, k| h[k] = [] }
108
+ results = []
109
+
110
+ File.open(out).each_line do |line|
111
+ m = line.strip.match(output_exp)
112
+ if m
113
+ _, rank, _, stream, text = m.captures
114
+ (stream == "output" ? outputs : errputs )[rank.to_i].push(text)
115
+ next
116
+ end
117
+ m = line.strip.match(status_exp)
118
+ if m
119
+ node, rank, ident, status = m.captures
120
+ results.push({ :name => node, :rank => rank.to_i, :ident => ident.to_i, :status => status.to_i })
121
+ next
122
+ end
123
+ end
124
+
125
+ names = nodes_mapping()
126
+ results.each do |r|
127
+ r[:stdout] = outputs[r[:rank]].join("\n")
128
+ r[:stderr] = errputs[r[:rank]].join("\n")
129
+ r[:node] = names[r[:name]].pop()
130
+ raise "Fatal error" if r[:node].nil?
131
+ end
132
+
133
+ return results
134
+ end
135
+
136
+ def execute(cmd, opts = {})
137
+ original_cmd = cmd
138
+ results = execute_shell(cmd, opts)
139
+ return _split_results(results)
140
+ end
141
+
142
+ def execute_remote(cmd, opts = {})
143
+ # executes a command which results will be stored remotely
144
+ original_cmd = cmd
145
+ prefix = "/tmp/.taktuk--#{@@hash}--#{TakTukRun.get_uniq}"
146
+ out_file, err_file = proc { |x| "#{prefix}--out--#{x}" }, proc { |x| "#{prefix}--err--#{x}" }
147
+ out, err = out_file.call("$TAKTUK_RANK"), err_file.call("$TAKTUK_RANK")
148
+
149
+ real_cmd = "#{cmd} 1> #{out} 2> #{err}"
150
+ results = execute_shell(real_cmd, opts)
151
+
152
+ results.each do |r|
153
+ rank = r[:rank]
154
+ r[:stdout_file] = out_file.call(rank)
155
+ r[:stderr_file] = err_file.call(rank)
156
+ end
157
+
158
+ return _split_results(results)
159
+ end
160
+
161
+ def put(src, dest, opts = {})
162
+ # distributes a file over all nodes
163
+ # returns md5's for all nodes
164
+
165
+ real_command = "bash #{filename} synchronize broadcast put [ #{src} ] [ #{dest} ]"
166
+ output = %x(#{real_command} 1> /dev/null 2> /dev/null)
167
+
168
+ succ, fail = execute("md5sum #{dest}")
169
+
170
+ if fail.length != 0
171
+ raise "Some nodes failed (try #{real_command})."
172
+ end
173
+
174
+ succ.each { |x| x[:hash] = x[:stdout].split.first }
175
+
176
+ return succ
177
+ end
178
+
179
+ def _split_results(results)
180
+ return [
181
+ results.select { |r| r[:status] == 0 },
182
+ results.select { |r| r[:status] != 0 }
183
+ ]
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+
190
+
191
+ if $0 == __FILE__
192
+
193
+ end
@@ -0,0 +1,14 @@
1
+
2
+ # Basic template
3
+
4
+ LogLevel quiet
5
+ UserKnownHostsFile /dev/null
6
+ StrictHostKeyChecking no
7
+ BatchMode yes
8
+ ConnectTimeout 15
9
+
10
+ Host this
11
+ User <%= user %>
12
+ Hostname <%= host %>
13
+ # ControlMaster auto
14
+ # ControlPath <%= path %>/control
@@ -0,0 +1,18 @@
1
+
2
+ # Grid5000 template
3
+
4
+ LogLevel quiet
5
+ UserKnownHostsFile /dev/null
6
+ StrictHostKeyChecking no
7
+ BatchMode yes
8
+ ConnectTimeout 15
9
+ ForwardAgent yes
10
+
11
+ Host proxy
12
+ User <%= g5k_user %>
13
+ Hostname <%= gw %>
14
+
15
+ Host this
16
+ User <%= user %>
17
+ Hostname <%= host %>
18
+ ProxyCommand ssh -F <%= path %>/ssh-config proxy 'nc <%= host %> 22'
@@ -0,0 +1,13 @@
1
+
2
+ # Proxy template
3
+
4
+ LogLevel quiet
5
+ UserKnownHostsFile /dev/null
6
+ StrictHostKeyChecking no
7
+ BatchMode yes
8
+ ConnectTimeout 15
9
+
10
+ Host this
11
+ User <%= user %>
12
+ Hostname <%= host %>
13
+ ProxyCommand <%= proxy %> 'nc <%= host %> 22'