ruby-cute 0.0.1 → 0.0.2

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.
@@ -0,0 +1,41 @@
1
+ require 'cute'
2
+
3
+ # This example tests two libraries for the execution of commands over several machines.
4
+ # These two libraries use two different approaches: 1) SSH, 1) TakTuk.
5
+ # This example uses Grid'5000 but it can be used with any set of machines that can be accessed through SSH.
6
+
7
+ g5k = Cute::G5K::API.new()
8
+ # We reuse a job if there is one available.
9
+ if g5k.get_my_jobs("grenoble").empty? then
10
+ job = g5k.reserve(:nodes => 5, :site => 'grenoble', :walltime => '00:30:00')
11
+ else
12
+ job =g5k.get_my_jobs("grenoble").first
13
+ end
14
+
15
+ nodes = job["assigned_nodes"]
16
+
17
+ results = {}
18
+
19
+ # please change user by your Grid'5000 user.
20
+ Net::SSH::Multi.start do |session|
21
+
22
+ nodes.each{ |node| session.use "user@#{node}" }
23
+ session.exec 'hostname'
24
+ session.loop
25
+ results = session.exec! 'df'
26
+ session.exec 'uptime'
27
+ end
28
+
29
+ puts results
30
+
31
+ Cute::TakTuk.start(nodes,:user => "user" ) do |tak|
32
+
33
+ results = tak.exec!("hostname")
34
+ tak.loop()
35
+ tak.exec("df")
36
+ tak.exec("uname -r")
37
+
38
+ end
39
+
40
+
41
+ puts results
@@ -0,0 +1,129 @@
1
+ # = Virtualization example on Grid'5000
2
+
3
+ # This script implements the example virtualization and subnet reservation in Grid'5000.
4
+ # In a nutshell, a machine and a sub network is reserved per site.
5
+ # Configuration files are generated to make kvm vms take the IPs addresses reserved, then several virtual
6
+ # machines are booted per machine (default 2). At the end all virtual machines are contacted via SSH.
7
+ # The example is described in https://www.grid5000.fr/mediawiki/index.php/Virtualization_on_Grid%275000
8
+
9
+ require 'grid5000/subnets'
10
+ require 'cute'
11
+ require 'net/scp'
12
+
13
+ g5k = Cute::G5K::API.new()
14
+ # We reuse a job if there is one available.
15
+ G5K_SITES = [:lille, :rennes, :lyon, :grenoble]
16
+
17
+ threads = []
18
+ jobs = {}
19
+ grid5000_opt = {:user => "oar", :keys => ["~/.ssh/id_rsa"], :port => 6667 }
20
+ num_vm = 2 # Number of vms per site
21
+
22
+ G5K_SITES.each{ |site|
23
+ threads << Thread.new {
24
+ if g5k.get_my_jobs(site).empty?
25
+ # As the platform could be busy and we could wait for a long time.
26
+ # here, we set wait_time to 200 seconds, like that we would just use the sites that are free.
27
+ begin
28
+ jobs[site] = g5k.reserve(:site => site, :resources => "slash_22=1+{virtual!='none'}/nodes=1",
29
+ :walltime =>"01:00:00",:keys => "~/.ssh/id_rsa", :wait_time => 200)
30
+ rescue Cute::G5K::EventTimeout
31
+ puts "We waited long enough, releasing job in site #{site}"
32
+ g5k.release(jobs[site]) # we release the job
33
+ jobs.delete(site)
34
+ end
35
+ else
36
+ jobs[site] = g5k.get_my_jobs(site).first
37
+ end
38
+ }
39
+ }
40
+
41
+ threads.each{ |t| t.join}
42
+
43
+ nodes = []
44
+ jobs.each{ |k,v| nodes+=v["assigned_nodes"]}
45
+
46
+ puts("Nodes reserved: #{nodes.inspect}")
47
+
48
+ # Creating vm configuration files
49
+ vm_dir = Dir.mktmpdir("vm_def")
50
+
51
+ system("wget -O #{vm_dir}/vm-template.xml http://public.nancy.grid5000.fr/~cruizsanabria/vm-template.xml")
52
+
53
+ template = ERB.new(File.read("#{vm_dir}/vm-template.xml"))
54
+
55
+ vms = []
56
+
57
+ G5K_SITES.each{ |site|
58
+ subnet = g5k.get_subnets(jobs[site]).first
59
+ ips = subnet.map{ |ip| ip.to_s }
60
+ num_vm.times{ |n|
61
+ @vm_name = "node#{n}.#{site}"
62
+ @vm_mac = ip2mac(ips[n+1])
63
+ vms.push(ips[n+1]) # avoiding .0 last octet
64
+ @tap_device = "tap#{n}"
65
+ File.open("#{vm_dir}/node_#{n}.#{site}.xml",'w+') do |f|
66
+ f.puts(template.result()) # ERB replaces @vm_name, @vm_mac and, @tap_device in the file.
67
+ end
68
+ }
69
+ }
70
+
71
+ puts("vm's ip assigned #{vms.inspect}")
72
+
73
+ # Setting up VMs
74
+ Cute::TakTuk.start(nodes, grid5000_opt) do |tak|
75
+
76
+ tak.exec!("mkdir -p ~/vm_definitions")
77
+
78
+ tak.exec("wget -q -O /tmp/wheezy-x64-base.qcow2 http://public.nancy.grid5000.fr/~cruizsanabria/wheezy-x64-base.qcow2")
79
+ puts("Transfering configuration files")
80
+ Dir.entries(vm_dir).each{ |vm_file|
81
+ next if vm_file[0] =="." # avoid . and .. files
82
+ puts File.join(vm_dir,vm_file)
83
+ tak.put(File.join(vm_dir,vm_file),"/tmp/#{vm_file}")
84
+ }
85
+ # Creates a number of tap devices number of vms/number of machines
86
+ puts("Creating TAP devices")
87
+ num_vm.times{ tak.exec!("sudo create_tap") }
88
+
89
+ # Creating contextualization script to copy our ssh key
90
+ tak.exec!("mkdir -p ~/kvm-context")
91
+ tak.exec!("cp ~/.ssh/id_rsa.pub ~/kvm-context/")
92
+ File.open("/tmp/post-install","w+") do |f|
93
+ f.puts("#!/bin/sh")
94
+ f.puts("mkdir -p /root/.ssh")
95
+ f.puts("cat /mnt/id_rsa.pub >> /root/.ssh/authorized_keys")
96
+ end
97
+ tak.put("/tmp/post-install","/tmp/post-install")
98
+ tak.exec!("cp /tmp/post-install ~/kvm-context/post-install")
99
+ tak.exec!("chmod 755 ~/kvm_context/post-install")
100
+ tak.exec!("genisoimage -r -o /tmp/kvm-context.iso ~/kvm-context/")
101
+ end
102
+
103
+ # Starting vms
104
+ Net::SSH::Multi.start do |session|
105
+ jobs.each{ |site,job|
106
+ # We create a group per site
107
+ session.group site do
108
+ job["assigned_nodes"].each{ |node|
109
+ session.use node, grid5000_opt
110
+ }
111
+ end
112
+ num_vm.times{ |n| puts session.with(site).exec!("virsh create /tmp/node_#{n}.#{site}.xml")}
113
+ }
114
+ end
115
+
116
+ puts("Waiting for the machines to start")
117
+ sleep 60
118
+
119
+ # Executing some commands on the vms
120
+
121
+ Net::SSH::Multi.start do |session|
122
+
123
+ vms.each{ |vm|
124
+ session.use("root@#{vm}")
125
+ }
126
+ session.exec("hostname")
127
+ session.exec("uptime")
128
+
129
+ end
@@ -1,2 +1,7 @@
1
- module Cute
2
- end
1
+ require 'cute/version'
2
+ require 'cute/bash'
3
+ require 'cute/execute'
4
+ require 'cute/taktuk'
5
+ require 'cute/g5k_api'
6
+ require 'cute/extensions'
7
+ require 'cute/net-ssh'
@@ -0,0 +1,337 @@
1
+
2
+ #
3
+ # Features a cool class to interface with Bash.
4
+ #
5
+
6
+ require 'digest'
7
+ require 'open3'
8
+
9
+ module Cute; module Bash
10
+
11
+ class BashError < StandardError; end
12
+ class BashTimeout < BashError; end
13
+ class BashPaddingError < BashError; end
14
+
15
+ class StatusError < BashError
16
+
17
+ attr_reader :status
18
+ attr_reader :output
19
+
20
+ def initialize(cmd, status, output)
21
+ super("'#{cmd}' returned with status = #{status}")
22
+ @status = status
23
+ @cmd = cmd
24
+ @output = output
25
+ end
26
+
27
+ end
28
+
29
+ class Bash
30
+
31
+ def initialize(stdin, stdout, debug = false)
32
+ @stdin = stdin
33
+ @stdout = stdout
34
+ @debug = debug
35
+ @buff = ''
36
+ end
37
+
38
+ def parse(&block)
39
+ return self.instance_exec(&block)
40
+ end
41
+
42
+ def _loop
43
+ while true do
44
+ x = IO::select([@stdout], [], [], 120.0)
45
+ raise BashTimeout.new if x.nil?
46
+ bytes = @stdout.sysread(1024)
47
+ $stderr.write("\nBASH IN: #{bytes}\n") if @debug
48
+ @buff << bytes
49
+ break if yield
50
+ end
51
+ end
52
+
53
+ def _nonce
54
+ randee = 4.times.map { rand().to_s }.join('|')
55
+ return Digest::SHA512.hexdigest(randee).to_s
56
+ end
57
+
58
+ def _run(cmd, opts)
59
+ # it's a kind of magic
60
+ $stderr.write("\nBASH CMD: #{cmd}\n") if @debug
61
+ nonce = _nonce()
62
+ @stdin.write("#{cmd}; printf '%04d#{nonce}' $?\n")
63
+ @stdin.flush
64
+ _loop do
65
+ @buff.include?(nonce)
66
+ end
67
+ raise BashPaddingError.new if !@buff.end_with?(nonce)
68
+ output = @buff
69
+ @buff = ''
70
+ # treat the output
71
+ output.slice!(-nonce.length..-1)
72
+ status = output.slice!(-4..-1)
73
+ raise "Status #{status} > 255?" if status.slice(0..0) != '0'
74
+ return output, status.to_i
75
+ end
76
+
77
+ def _run_block(cmd, opts)
78
+ @stdin.write("#{cmd}; printf '%04d#{nonce}' $?\n")
79
+ end
80
+
81
+ def _extend(path, suffix)
82
+ path = path.chomp('/') if path.end_with?('/')
83
+ return path + suffix
84
+ end
85
+
86
+ def _unlines(s)
87
+ return s.lines.map { |l| l.chomp("\n") }
88
+ end
89
+
90
+ def _escape(args)
91
+ return args.map { |x| "'#{x}'" }.join(' ')
92
+ end
93
+
94
+ # TESTING METHODS
95
+
96
+ def assert(condition, msg = 'Assertion error')
97
+ raise msg if condition != true
98
+ end
99
+
100
+ # PUBLIC METHODS
101
+
102
+ def export(name, value)
103
+ run("export #{name}=#{value}")
104
+ end
105
+
106
+ def run(cmd, opts = {})
107
+ out, status = _run(cmd, opts)
108
+ raise StatusError.new(cmd, status, out) if status != 0
109
+ return out
110
+ end
111
+
112
+ def run_status(cmd, opts = {})
113
+ out, status = _run(cmd, opts)
114
+ return status
115
+ end
116
+
117
+ def cd(path)
118
+ run("cd #{path}")
119
+ end
120
+
121
+ def ls
122
+ run("ls -1").lines.map { |line| line.chomp("\n") }
123
+ end
124
+
125
+ def pwd
126
+ run("pwd").strip
127
+ end
128
+
129
+ def untar(name, where = nil)
130
+ if where.nil?
131
+ run("tar xvf #{name}")
132
+ else
133
+ run("tar -C #{where} -xvf #{name}")
134
+ end
135
+ end
136
+
137
+ def echo(text)
138
+ run("echo #{text}")
139
+ end
140
+
141
+ def bc(text)
142
+ run("echo '#{text}' | bc").strip
143
+ end
144
+
145
+ def cp(a, b)
146
+ run("cp #{a} #{b}")
147
+ end
148
+
149
+ def mv(a, b)
150
+ run("mv #{a} #{b}")
151
+ end
152
+
153
+ def rm(*args)
154
+ run("rm #{_escape(args)}")
155
+ end
156
+
157
+ def remove_dirs(path)
158
+ rm "-rf", path
159
+ end
160
+
161
+ def build
162
+ # builds a standard Unix software
163
+ run("./configure")
164
+ run("make")
165
+ end
166
+
167
+ def abspath(path)
168
+ run("readlink -f #{path}").strip
169
+ end
170
+
171
+ def build_tarball(tarball, path)
172
+ # builds a tarball containing a std Unix software
173
+ tarball = abspath(tarball)
174
+ path = abspath(path)
175
+ remove_dirs(path)
176
+ tmp = _extend(path, '-tmp')
177
+ remove_dirs(tmp)
178
+ make_dirs(tmp)
179
+ untar(tarball, tmp)
180
+ cd tmp
181
+ # we are in the temp dir
182
+ if exists('./configure')
183
+ cd '/'
184
+ mv tmp, path
185
+ else
186
+ ds = dirs()
187
+ raise 'Too many dirs?' if ds.length != 1
188
+ mv ds.first, path
189
+ cd '/'
190
+ remove_dirs(tmp)
191
+ end
192
+ cd path
193
+ build
194
+ end
195
+
196
+ def tmp_file
197
+ return run('mktemp').strip
198
+ end
199
+
200
+ def save_machines(machines, path = nil)
201
+ path = tmp_file if path.nil?
202
+ append_lines(path, machines.map { |m| m.to_s })
203
+ return path
204
+ end
205
+
206
+ def mpirun(machines, params)
207
+ machines = save_machines(machines) if machines.is_a?(Array)
208
+ return run("mpirun --mca btl ^openib -machinefile #{machines} #{params}")
209
+ end
210
+
211
+ def join(*args)
212
+ return File.join(*args)
213
+ end
214
+
215
+ def exists(path)
216
+ run_status("[[ -e #{path} ]]") == 0
217
+ end
218
+
219
+ def make_dirs(path)
220
+ run("mkdir -p #{path}")
221
+ end
222
+
223
+ def mkdir(path)
224
+ run("mkdir #{path}") unless exists(path) # TODO: this changes semantics of mkdir...
225
+ end
226
+
227
+ def files(ignore = true, type = 'f')
228
+ fs = run("find . -maxdepth 1 -type #{type}")
229
+ fs = _unlines(fs).reject { |f| f == '.' }.map { |f| f[2..-1] }
230
+ fs = fs.reject { |f| f.end_with?('~') or f.start_with?('.') } if ignore
231
+ return fs
232
+ end
233
+
234
+ def which(prog)
235
+ return run("which #{prog}").strip
236
+ end
237
+
238
+ def dirs(ignore = true)
239
+ return files(ignore, 'd')
240
+ end
241
+
242
+ def get_type(name)
243
+ return :dir if run_status("[[ -d #{name} ]]") == 0
244
+ return :file if run_status("[[ -f #{name} ]]") == 0
245
+ raise "'#{name}' is neither file nor directory"
246
+ end
247
+
248
+ def expand_path(path)
249
+ return run("echo #{path}").strip
250
+ end
251
+
252
+ def append_line(path, line)
253
+ return run("echo '#{line}' >> #{path}")
254
+ end
255
+
256
+ def append_lines(path, lines)
257
+ lines.each { |line|
258
+ append_line(path, line)
259
+ }
260
+ end
261
+
262
+ def contents(name)
263
+ run("cat #{name}")
264
+ end
265
+
266
+ alias cat contents
267
+
268
+ def hostname
269
+ return run('hostname').strip
270
+ end
271
+
272
+ def touch(path)
273
+ run("touch #{path}")
274
+ end
275
+
276
+ # BELOW ARE SPECIFIC METHODS FOR XPFLOW
277
+
278
+
279
+ def packages
280
+ list = run("dpkg -l")
281
+ list = _unlines(list).map do |p|
282
+ s, n, v = p.split
283
+ { :status => s, :name => n, :version => v }
284
+ end
285
+ end
286
+
287
+ def aptget(*args)
288
+ raise 'Command not given' if args.length == 0
289
+ cmd = args.first.to_sym
290
+ args = args.map { |x| x.to_s }.join(' ')
291
+ status = run_status("DEBIAN_FRONTEND=noninteractive apt-get -y #{args}")
292
+ if cmd == :purge and status == 100
293
+ return # ugly hack for the case when the package is not installed
294
+ end
295
+ raise StatusError.new('aptget', status, 'none') if status != 0
296
+ end
297
+
298
+ def distribute(path, dest, *nodes)
299
+ # distributes a file to the given nodes
300
+ nodes.flatten.each { |node|
301
+ run("scp -o 'StrictHostKeyChecking no' #{path} #{node}:#{dest}")
302
+ }
303
+ end
304
+
305
+ def glob(pattern)
306
+ out, status = _run("ls -1 #{pattern}", {})
307
+ return [] if status != 0
308
+ return out.strip.lines.map { |x| x.strip }
309
+ end
310
+
311
+ end
312
+
313
+
314
+ def self.bash(cmd = 'bash', debug = false, &block)
315
+ if not block_given?
316
+ sin, sout, serr, thr = Open3.popen3(cmd)
317
+ return Bash.new(sin, sout, debug)
318
+ end
319
+ # run bash interpreter using this command
320
+ result = nil
321
+ Open3.popen3(cmd) do |sin, sout, serr, thr|
322
+ dsl = Bash.new(sin, sout, debug)
323
+ dsl.cd('~') # go to the home dir
324
+ result = dsl.parse(&block)
325
+ end
326
+ return result
327
+ end
328
+
329
+ end; end
330
+
331
+ if __FILE__ == $0
332
+ Cute::Bash.bash("ssh localhost bash") do
333
+ cd '/tmp'
334
+ puts files.inspect
335
+ run 'rm /'
336
+ end
337
+ end