ruby-cute 0.0.1 → 0.0.2

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