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,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