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,349 @@
1
+ # encoding: UTF-8
2
+
3
+ #
4
+ # Implementation of libraries, i.e., sets of activities
5
+ # that can be imported or injected into a namespace.
6
+ #
7
+
8
+ require 'monitor'
9
+
10
+ module XPFlow
11
+
12
+ class ResolutionError < StandardError
13
+
14
+ end
15
+
16
+ class Library
17
+ # implements .activity and .process methods
18
+ # + some kind of activity management
19
+
20
+ def initialize
21
+ @names = { }
22
+ @lock = Mutex.new
23
+ end
24
+
25
+ def synchronize
26
+ @lock.synchronize do
27
+ yield
28
+ end
29
+ end
30
+
31
+ def [](key)
32
+ return @names[key]
33
+ end
34
+
35
+ def []=(key, value)
36
+ key = stringize(key)
37
+ raise "'#{key}' already exists here" if @names.key?(key)
38
+ raise "'#{key}' contains a dot" if key.include?(".")
39
+ @names[key] = value
40
+ end
41
+
42
+ def set_force(key, value)
43
+ @names[key] = value
44
+ end
45
+
46
+ def into_parts(name)
47
+ array = name.split(".")
48
+ return [ array[0...-1], array.last ]
49
+ end
50
+
51
+ def resolve_name(name)
52
+ name = stringize(name)
53
+ return resolve_parts(*into_parts(name))
54
+ end
55
+
56
+ def resolve_libs(libs)
57
+ libs = libs + [] # copy
58
+ path = libs.join(".")
59
+ current = self
60
+ while libs.length > 0
61
+ name = libs.shift
62
+ current = current[name]
63
+ if !current.is_a?(Library)
64
+ raise ResolutionError.new("No such library '#{path}'")
65
+ end
66
+ end
67
+ return current
68
+ end
69
+
70
+ def resolve_parts(libs, name)
71
+ library = resolve_libs(libs)
72
+ object = library[name]
73
+ if object.nil?
74
+ path = libs.join(".")
75
+ raise ResolutionError.new("No such object '#{name}' in '#{path}'")
76
+ end
77
+ return object
78
+ end
79
+
80
+ def resolve_activity(libs, name)
81
+ activity = resolve_parts(libs, name)
82
+ if activity.is_a?(Library)
83
+ path = (libs + [ name ]).join(".")
84
+ raise ResolutionError.new("'#{path}' is not an activity")
85
+ end
86
+ end
87
+
88
+ def create_libraries(libs)
89
+ # creates all intermediate libraries if they don't exist
90
+ current = self
91
+ nesting = []
92
+ while libs.length > 0
93
+ name = libs.shift
94
+ nesting.push(name)
95
+ object = current[name]
96
+ if object.nil?
97
+ current[name] = BasicLibrary.new
98
+ elsif !object.is_a?(Library)
99
+ activity = nesting.join(".")
100
+ raise ResolutionError.new("Overwriting an activity '#{activity}'")
101
+ end
102
+ current = current[name]
103
+ end
104
+ return current
105
+ end
106
+
107
+ def get_library_or_nil(name)
108
+ object = @names[name]
109
+ return object
110
+ end
111
+
112
+ def get_activity_or_error(name)
113
+ object = @names[name]
114
+ if object.is_a?(Library)
115
+ raise ResolutionError.new("No such activity '#{name}'")
116
+ end
117
+ return object
118
+ end
119
+
120
+ def get_activity_or_nil(name)
121
+ previous = nil
122
+ begin
123
+ previous = get_activity_or_error(name)
124
+ rescue ResolutionError => e
125
+ previous = nil # there is no previous activity
126
+ end
127
+ return previous
128
+ end
129
+
130
+
131
+ def activity_alias(full_name, old_name)
132
+ # creates an alias between full_name and another_name
133
+ # please don't make it cyclic! :D
134
+ object = get_object(old_name)
135
+ raise "Object '#{old_name}' does not exist" if object.nil?
136
+ libs, name = into_parts(full_name)
137
+ library = resolve_libs(libs)
138
+ library[name] = object
139
+ end
140
+
141
+ def traversal_graph
142
+ g = {}
143
+ @names.each do |k, v|
144
+ r = case
145
+ when v.is_a?(Library) then v.traversal_graph
146
+ when v.is_a?(XPFlow::BlockActivity) then "block"
147
+ when v.is_a?(XPFlow::ProcessActivity) then "process"
148
+ else
149
+ raise "Error: #{k} #{v} (#{v.class})"
150
+ end
151
+ g[k] = r
152
+ end
153
+ return g
154
+ end
155
+
156
+ def stringize(o)
157
+ raise "Wrong name type: #{o.class}" if !(o.is_a?(String) || o.is_a?(Symbol))
158
+ return o.to_s
159
+ end
160
+
161
+ # ===PUBLIC API=== #
162
+
163
+ def activity(full_name, opts = {}, &block)
164
+ name, library = _create_entry(full_name)
165
+
166
+ opts[:__parent__] = library.get_activity_or_nil(name)
167
+
168
+ block_activity = XPFlow::BlockActivity.new(name, opts, block)
169
+ block_activity.doc = opts[:doc]
170
+
171
+ library.set_force(name, block_activity)
172
+
173
+ return block_activity
174
+ end
175
+
176
+ def constant(full_name, value)
177
+ activity full_name do
178
+ value
179
+ end
180
+ end
181
+
182
+ def _create_entry(full_name)
183
+ full_name = stringize(full_name)
184
+ libs, name = into_parts(full_name)
185
+ library = create_libraries(libs)
186
+ return [ name, library ]
187
+ end
188
+
189
+ def process(full_name, opts = {}, &block)
190
+ name, library = _create_entry(full_name)
191
+
192
+ parent = opts[:__parent__] = library.get_activity_or_nil(name)
193
+
194
+ process_activity = XPFlow::ProcessDSL.new(name, self, &block).as_process
195
+ process_activity.doc = parent.doc unless parent.nil?
196
+ process_activity.doc = opts[:doc] if opts[:doc]
197
+ process_activity.opts = opts
198
+ process_activity.collect_meta(2)
199
+
200
+ library.set_force(name, process_activity)
201
+
202
+ return process_activity
203
+ end
204
+
205
+ def macro(full_name, opts = {}, &block)
206
+ name, library = _create_entry(full_name)
207
+
208
+ m = XPFlow::MacroDSL.new(name, self, block)
209
+ library.set_force(name, m)
210
+ return m
211
+ end
212
+
213
+ def get_object(name)
214
+ begin
215
+ return resolve_name(name)
216
+ rescue ResolutionError => e
217
+ return nil
218
+ end
219
+ end
220
+
221
+ def get_activity_object(name)
222
+ object = get_object(name)
223
+ if object.is_a?(Library)
224
+ raise "'#{name}' is a library"
225
+ end
226
+ return object
227
+ end
228
+
229
+ def get_names
230
+ return @names.keys
231
+ end
232
+
233
+ def get_libraries
234
+ h = @names.select { |k, v| v.is_a?(Library) }
235
+ return h
236
+ end
237
+
238
+ def import_library(name, library)
239
+ self[name] = library
240
+ end
241
+
242
+ def inject_library(name, library)
243
+ # this will inject all activities from 'library' into this library
244
+ # this may overwrite some of them
245
+ import_library(name, library)
246
+ library.get_names.each do |object|
247
+ activity_alias(object, "#{name}.#{object}")
248
+ end
249
+ end
250
+
251
+ def setup
252
+ # empty
253
+ end
254
+
255
+ end
256
+
257
+ if __FILE__ == $0
258
+ require 'pp'
259
+ require 'xpflow'
260
+ l = Library.new
261
+ l.constant :a1, 1
262
+ l.process :"czesc.siema" do; end
263
+ g = Library.new
264
+ g.activity :cze
265
+ l.activity_alias("alejazda", "czesc")
266
+ pp l.traversal_graph
267
+ end
268
+
269
+ # a library that does some nice tricks
270
+
271
+ class ActivityLibrary < Library
272
+
273
+ def initialize
274
+ super
275
+ setup()
276
+ this = self
277
+ __activities__.each_pair do |name, realname|
278
+ activity(name, get_option(name)) do |*args|
279
+ this.invoke(realname, args, self, &self.__block__)
280
+ end
281
+ end
282
+ end
283
+
284
+ def invoke(method, args, proxy = nil, &block)
285
+ Thread.current[:__proxy__] = proxy
286
+ return self.send(method, *args, &block)
287
+ end
288
+
289
+ def proxy
290
+ return Thread.current[:__proxy__]
291
+ end
292
+
293
+ def get_option(name)
294
+ return { }
295
+ end
296
+
297
+ end
298
+
299
+ class HiddenActivityLibrary < ActivityLibrary
300
+
301
+ def get_option(name)
302
+ return { :log_level => :none }
303
+ end
304
+
305
+ end
306
+
307
+ class SyncedActivityLibrary < ActivityLibrary
308
+
309
+ def invoke(method, args, proxy = nil, &block)
310
+ synchronize do
311
+ super
312
+ end
313
+ end
314
+
315
+ end
316
+
317
+ class MonitoredActivityLibrary < ActivityLibrary
318
+
319
+ # with reentrant lock
320
+ def initialize
321
+ super
322
+ @lock = Monitor.new
323
+ end
324
+
325
+ end
326
+
327
+ IGNORED_LIBRARY_VARIABLES = Library.new.instance_variables
328
+
329
+ module SerializableLibrary
330
+
331
+ def checkpoint
332
+ vars = self.instance_variables - IGNORED_LIBRARY_VARIABLES
333
+ state = {}
334
+ vars.each do |v|
335
+ value = self.instance_variable_get(v)
336
+ state[v] = value
337
+ end
338
+ return state
339
+ end
340
+
341
+ def restore(state)
342
+ state.each_pair do |k, v|
343
+ self.instance_variable_set(k, v)
344
+ end
345
+ end
346
+
347
+ end # Serializable
348
+
349
+ end
@@ -0,0 +1,153 @@
1
+
2
+ require 'thread'
3
+
4
+ module XPFlow
5
+
6
+ class AbstractLog
7
+
8
+ def initialize
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ def open(*args)
13
+ raise "Not implemented!"
14
+ end
15
+
16
+ def log(*args)
17
+ raise "Not implemented!"
18
+ end
19
+
20
+ def close(*args)
21
+ raise "Not implemented!"
22
+ end
23
+
24
+ def synchronize(&block)
25
+ return @mutex.synchronize(&block)
26
+ end
27
+
28
+ end
29
+
30
+ class ConsoleLog < AbstractLog
31
+
32
+ def initialize
33
+ super
34
+ end
35
+
36
+ def log(*msgs)
37
+ synchronize do
38
+ msgs.each do |x|
39
+ x = x.plain unless STDOUT.tty?
40
+ puts x
41
+ end
42
+ STDOUT.flush
43
+ end
44
+ end
45
+
46
+ def open; end
47
+ def close; end
48
+
49
+ end
50
+
51
+ class FileLog < AbstractLog
52
+
53
+ def initialize(fname)
54
+ super()
55
+ @fname = fname
56
+ @f = nil
57
+ end
58
+
59
+ def open
60
+ @f = File.open(@fname, "w")
61
+ return self
62
+ end
63
+
64
+ def log(*msgs)
65
+ synchronize do
66
+ msgs.each do |x|
67
+ @f.write("#{x.plain}\n")
68
+ end
69
+ @f.flush
70
+ end
71
+ end
72
+
73
+ def close
74
+ @f.close
75
+ @f = nil
76
+ end
77
+
78
+ end
79
+
80
+ class Logging
81
+
82
+ def initialize
83
+ @loggers = {}
84
+ end
85
+
86
+ def add(logger, label)
87
+ @loggers[label] = logger
88
+ end
89
+
90
+ def prefix
91
+ t = Time.now
92
+ s = t.strftime('%Y-%m-%d %H:%M:%S.%L')
93
+ ms = t.usec / 1000
94
+ return s.gsub('%L', "%03d" % ms) # Ruby 1.8 compat
95
+ end
96
+
97
+ def colorize(s, label)
98
+ colors = {
99
+ :normal => :green,
100
+ :verbose => :yellow,
101
+ :paranoic => :red,
102
+ :default => :red!
103
+ }
104
+ c = colors[:default]
105
+ c = colors[label] if colors.key?(label)
106
+ return s.send(c)
107
+ end
108
+
109
+ def log(msg, label = :normal)
110
+ pre = colorize(prefix, label)
111
+ msg = "[ %s ] %s" % [ pre, msg ]
112
+ @loggers.each_pair do |k, v|
113
+ v.log(msg)
114
+ end
115
+ # TODO: kind of ugly
116
+ Scope.current[:__experiment__].log(msg) # log to the experiment
117
+ end
118
+
119
+ def get(label)
120
+ return @loggers[label]
121
+ end
122
+
123
+ def using(&block)
124
+ ls = @loggers.values
125
+ opened = []
126
+ x = nil?
127
+ begin
128
+ ls.each { |x| x.open(); opened.push(x) }
129
+ x = block.call
130
+ ensure
131
+ opened.each { |x| x.close } # TODO
132
+ end
133
+ return x
134
+ end
135
+
136
+ end
137
+
138
+ $console = ConsoleLog.new # globally accessible, should be used by everybody
139
+
140
+ end
141
+
142
+ if __FILE__ == $0
143
+ require 'colorado'
144
+ s = "cze".green + "yo".red
145
+ x = XPFlow::Logging.new
146
+ x.add(XPFlow::ConsoleLog.new, :console)
147
+ x.add(XPFlow::FileLog.new("test.log"), :file)
148
+
149
+ x.using do
150
+ x.log(s)
151
+ end
152
+
153
+ end
@@ -0,0 +1,147 @@
1
+
2
+ # this is a temporary directory that contains
3
+ # files generated on-the-fly to simplify things
4
+
5
+ require 'tmpdir'
6
+ require 'thread'
7
+ require 'fileutils'
8
+ require 'monitor'
9
+ require 'erb'
10
+ require 'ostruct'
11
+ require 'yaml'
12
+
13
+ module XPFlow
14
+
15
+ class ExecutionError < StandardError
16
+
17
+ end
18
+
19
+ class LocalExecutionResult
20
+
21
+ def initialize(cmd, stdout, stderr)
22
+ @cmd = cmd
23
+ @stdout = stdout
24
+ @stderr = stderr
25
+ end
26
+
27
+ def stdout
28
+ return IO.read(@stdout)
29
+ end
30
+
31
+ def stderr
32
+ return IO.read(@stderr)
33
+ end
34
+
35
+ def stdout_file
36
+ return @stdout
37
+ end
38
+
39
+ def stderr_file
40
+ return @stderr
41
+ end
42
+
43
+ def command
44
+ return @cmd
45
+ end
46
+
47
+ def to_s
48
+ return "LocalExecutionResult(#{@cmd}, out => #{@stdout}, err => #{@stderr})"
49
+ end
50
+
51
+ end
52
+
53
+ class DirectoryManager
54
+
55
+ attr_reader :path
56
+
57
+ def initialize(path)
58
+ @mutex = Monitor.new
59
+ if path.nil?
60
+ @path = Dir.mktmpdir() # TODO: remove while exiting
61
+ # use remove_entry_secure
62
+ else
63
+ if File.directory?(path)
64
+ FileUtils.remove_entry_secure(path)
65
+ end
66
+ Dir.mkdir(path)
67
+ @path = path
68
+ end
69
+ @counter = 0
70
+ end
71
+
72
+ def synchronize(&block)
73
+ return @mutex.synchronize(&block)
74
+ end
75
+
76
+ def mktemp(&block)
77
+ fname = synchronize do
78
+ @counter += 1
79
+ File.join(@path, "tmpfile-#{@counter}")
80
+ end
81
+ if block_given?
82
+ File.open(fname, "wb", &block)
83
+ end
84
+ return fname
85
+ end
86
+
87
+ def join(name)
88
+ return File.join(@path, name)
89
+ end
90
+
91
+ def open(fname, flags, &block)
92
+ path = File.join(@path, fname)
93
+ return synchronize { File.open(path, flags, &block) }
94
+ end
95
+
96
+ def run_with_files(name, stdout, stderr, opts = {})
97
+ # assumes that name is properly escaped!!!
98
+ name = join(name)
99
+ out = `#{name} 2> #{stderr} > #{stdout}`
100
+ raise ExecutionError.new("Command #{name} returned error (see #{stdout} and #{stderr})!") if $?.exitstatus != 0
101
+ return LocalExecutionResult.new(name, stdout, stderr)
102
+ end
103
+
104
+ def run(name, opts = {})
105
+ return run_with_files(name, self.mktemp(), self.mktemp(), opts)
106
+ end
107
+
108
+ def run_ssh(cmd, opts = {})
109
+ # runs a command remotely
110
+ # the form is ".../ssh 'ENV cmd args' "
111
+ # TODO: Escaping yo!
112
+
113
+ stdout = (opts[:out].nil?) ? self.mktemp() : opts[:out]
114
+ stderr = (opts[:err].nil?) ? self.mktemp() : opts[:err]
115
+ stdin = opts.fetch(:in, "/dev/null")
116
+
117
+ if opts[:env] and opts[:env] != {}
118
+ prefix = opts[:env].each_pair.map { |k, v| "#{k}=#{v}" }.join(" ")
119
+ cmd = "#{prefix} #{cmd}"
120
+ end
121
+
122
+ if opts[:wd]
123
+ cmd = "cd #{opts[:wd]}; #{cmd}"
124
+ end
125
+
126
+ cmd = Shellwords.escape(cmd)
127
+ cmd = "ssh #{cmd}"
128
+ real_cmd = join(cmd)
129
+ out = `#{real_cmd} < #{stdin} 2> #{stderr} 1> #{stdout}`
130
+
131
+ raise ExecutionError.new("Command #{real_cmd} failed (see #{stdout} and #{stderr})") if $?.exitstatus != 0
132
+ return LocalExecutionResult.new(cmd, stdout, stderr)
133
+ end
134
+
135
+ def _subdir(name)
136
+ path = join(name)
137
+ Dir.mkdir(path) # TODO: what if exists?
138
+ return DirectoryManager.new(path)
139
+ end
140
+
141
+ def subdir(name)
142
+ synchronize { _subdir(name) }
143
+ end
144
+
145
+ end
146
+
147
+ end # module