xpflow 0.1b

Sign up to get free protection for your applications and to get access to all the features.
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'