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,3 @@
1
+ module Cute
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'cute/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ruby-cute"
7
+ s.version = Cute::VERSION
8
+ s.authors = ["Algorille team"]
9
+ s.email = "ruby-cute-staff@lists.gforge.inria.fr"
10
+ s.homepage = "http://ruby-cute.gforge.inria.fr/"
11
+ s.summary = "Critically Useful Tools for Experiments"
12
+ s.description = "Ruby library for controlling experiments"
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+ s.add_development_dependency "bundler", "~> 1.7"
19
+ s.add_development_dependency "rake", "~> 10.0"
20
+ s.add_development_dependency "rspec", "~> 3.1"
21
+ s.add_development_dependency "pry", "~> 0.10"
22
+ s.add_development_dependency "webmock", "~> 1.20"
23
+ s.add_development_dependency "yard", "~> 0.8"
24
+
25
+ s.add_dependency 'rest-client', '1.6.7'
26
+ s.add_dependency 'json', '~> 1.8'
27
+ s.add_dependency 'ipaddress', '~>0.8'
28
+ s.add_dependency 'net-ssh-multi', '~>1.2'
29
+
30
+ s.extra_rdoc_files = ['README.md', 'LICENSE']
31
+ s.license = 'CeCILL-B'
32
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe "Monkey patch to class String" do
5
+
6
+ it "converts walltime format into seconds" do
7
+ digits = []
8
+
9
+ (0..9).each{ |x| digits.push(x.to_s)}
10
+ time = digits.combination(2).to_a.map{ |x| x.join("")}.select{ |h| h.to_i < 60 }
11
+ walltime = time.combination(3).to_a.map{ |x| x.join(":")}
12
+
13
+ expect {walltime.each{ |t| t.to_secs}}.not_to raise_error
14
+ end
15
+
16
+
17
+ end
@@ -0,0 +1,192 @@
1
+ require 'spec_helper'
2
+ # These tests can be executed either real or using mocking through WebMock
3
+ # Real tests can be activated by setting the shell variable TEST_REAL
4
+ describe Cute::G5K::API do
5
+
6
+ subject { g5k = ENV['TEST_REAL'].nil?? Cute::G5K::API.new(:user => "test") : Cute::G5K::API.new() }
7
+
8
+ let(:sites) { subject.site_uids}
9
+
10
+ before :each do
11
+ # Choosing a random site based on the date
12
+ day = Time.now.day
13
+ index = ((day/31.to_f)*sites.length).to_i
14
+ index = 1 if index == 0
15
+ @rand_site = sites[index]
16
+ @env = subject.environment_uids(@rand_site).first
17
+ if ENV['TEST_REAL']
18
+ WebMock.disable!
19
+ puts "Testing in real Grid'5000 using site: #{@rand_site}"
20
+ puts "Warning G5K_USER environment variable has to be defined for some tests" if ENV['G5K_USER'].nil?
21
+ end
22
+ end
23
+
24
+ it "checks initialization of G5K::API" do
25
+ expect(subject.rest).to be_an_instance_of(Cute::G5K::G5KRest)
26
+ end
27
+
28
+
29
+ it "returns an array with the site ids" do
30
+ expect(sites.length).to be > 0
31
+ end
32
+
33
+ it "returns an array with the clusters ids" do
34
+ clusters = subject.cluster_uids(@rand_site)
35
+ expect(clusters.length).to be > 0
36
+ end
37
+
38
+ it "returns a JSON Hash with the status of a site" do
39
+ expect(subject.site_status(@rand_site)).to be_an_instance_of(Cute::G5K::G5KJSON)
40
+ end
41
+
42
+ it "returns a Hash with nodes status" do
43
+ expect(subject.nodes_status(@rand_site)).to be_an_instance_of(Hash)
44
+ expect(subject.nodes_status(@rand_site).length).to be > 0
45
+ end
46
+
47
+ it "returns an array with site status" do
48
+ expect(subject.sites).to be_an_instance_of(Cute::G5K::G5KArray)
49
+ end
50
+
51
+ it "returns jobs in the site" do
52
+ expect(subject.get_jobs(@rand_site)).to be_an_instance_of(Array)
53
+ end
54
+
55
+ it "return my_jobs in the site" do
56
+ expect(subject.get_my_jobs(@rand_site)).to be_an_instance_of(Array)
57
+ end
58
+
59
+ it "returns all deployments" do
60
+ expect(subject.get_deployments(@rand_site)).to be_an_instance_of(Cute::G5K::G5KArray)
61
+ end
62
+
63
+ it "raises an authentication error" do
64
+ expect{Cute::G5K::API.new(:user => "fake", :pass => "fake") }.to raise_error
65
+ end
66
+
67
+ it "raises a non found error" do
68
+ expect{subject.get_jobs("non-found")}.to raise_error(Cute::G5K::NotFound)
69
+ end
70
+
71
+ it "raises a bad request error" do
72
+ expect{ subject.reserve(:site => @rand_site, :resources =>"/slash_22=1+{nonsense}")}.to raise_error(Cute::G5K::BadRequest)
73
+ # expect{ subject.reserve(:site => @rand_site, :resources =>"{ib30g='YES'}/nodes=2")}.to raise_error(Cute::G5K::BadRequest)
74
+ end
75
+
76
+ it "raises an exception at deploying" do
77
+ expect{ subject.reserve(:site => @rand_site, :nodes => 1, :env => "nonsense")}.to raise_error(Cute::G5K::RequestFailed)
78
+ end
79
+
80
+ it "raises argument errors" do
81
+ job = Cute::G5K::G5KJSON.new
82
+ expect {subject.deploy(:env => "env")}.to raise_error(ArgumentError)
83
+ expect {subject.deploy(job)}.to raise_error(ArgumentError)
84
+ expect {subject.reserve(:non_existing => "site")}.to raise_error(ArgumentError)
85
+ end
86
+
87
+ it "raises invalid nodes format" do
88
+ job = Cute::G5K::G5KJSON.new
89
+ expect{subject.deploy(job,:env => "env")}.to raise_error(RuntimeError,"Unrecognized nodes format, use an Array")
90
+ end
91
+
92
+ it "raises error vlan" do
93
+ expect {subject.reserve(:site => @rand_site, :vlan => :nonsense)}.to raise_error(ArgumentError,'Option for vlan not recognized')
94
+ end
95
+
96
+
97
+ it "reserves and returns a valid job" do
98
+ job = subject.reserve(:site => @rand_site)
99
+ expect(job).to be_an_instance_of(Cute::G5K::G5KJSON)
100
+ subject.release(job)
101
+ end
102
+
103
+
104
+ it "reserves with vlan and get vlan hostnames" do
105
+ job = subject.reserve(:site => @rand_site, :nodes => 1, :type => :deploy, :vlan => :routed)
106
+ expect(subject.get_vlan_nodes(job)).to be_an_instance_of(Array)
107
+ subject.release(job)
108
+ end
109
+
110
+ it "vlan returns nil" do
111
+ job = Cute::G5K::G5KJSON.new
112
+ expect(subject.get_vlan_nodes(job)).to be_nil
113
+ end
114
+
115
+ it "performs an advanced reservation" do
116
+ time_schedule = Time.now + 60*30
117
+ job =subject.reserve(:site => @rand_site, :nodes => 1, :reservation => time_schedule.strftime("%Y-%m-%d %H:%M:%S"))
118
+ subject.release(job)
119
+ end
120
+
121
+ it "gets subnets from job" do
122
+ job = subject.reserve(:site => @rand_site, :nodes => 1, :subnets => [22,2])
123
+ expect(subject.get_subnets(job).first).to be_an_instance_of(IPAddress::IPv4)
124
+ subject.release(job)
125
+ end
126
+
127
+
128
+ it "does not deploy immediately" do
129
+ job = subject.reserve(:site => @rand_site, :type => :deploy )
130
+ expect(job).to include("types" => ["deploy"])
131
+ expect(job).to_not have_key("deploy")
132
+ end
133
+
134
+ it "tests deploy with keys option" do
135
+ # Getting a deploy job
136
+ job = subject.get_my_jobs(@rand_site).select{ |j| j.has_value?(["deploy"])}.first
137
+ fake_key = Tempfile.new(["keys",".pub"])
138
+ fake_key_path = fake_key.path.split(".pub").first
139
+ expect(subject.deploy(job,:env => @env,:wait => true, :keys => fake_key_path)).to be_an_instance_of(Cute::G5K::G5KJSON)
140
+ fake_key.delete
141
+ end
142
+
143
+ it "waits for a deploy" do
144
+ job = subject.get_my_jobs(@rand_site).select{ |j| j.has_value?(["deploy"])}.first
145
+ # It verifies that the job has been submitted with deploy
146
+ expect(subject.deploy_status(job)).to be_an_instance_of(Array)
147
+ subject.wait_for_deploy(job)
148
+ expect(subject.deploy_status(job)).to be_an_instance_of(Array)
149
+
150
+ #deploying again
151
+ # subject.deploy(job, :env => @env)
152
+ # subject.wait_for_deploy(job)
153
+ # expect(subject.deploy_status(job).uniq).to eq(["terminated"])
154
+ end
155
+
156
+
157
+ it "submits a job and then deploy" do
158
+ expect(subject.reserve(:site => @rand_site, :env => @env)).to have_key("deploy")
159
+ end
160
+
161
+
162
+ it "returns the same information" do
163
+ #low level REST access
164
+ jobs_running = subject.rest.get_json("sid/sites/#{@rand_site}/jobs/?state=running").items.length
165
+ expect(subject.get_jobs(@rand_site,nil,"running").length).to eq(jobs_running)
166
+ end
167
+
168
+ it "submit and does not wait for the reservation" do
169
+ cluster = subject.cluster_uids(@rand_site).first
170
+ job = subject.reserve(:site => @rand_site, :wait => false)
171
+ job = subject.wait_for_job(job, :wait_time => 600)
172
+ expect(job).to include('state' => "running")
173
+ end
174
+
175
+
176
+ it "should submit a job with OAR hierarchy" do
177
+
178
+ job1 = subject.reserve(:site => @rand_site, :switches => 2, :nodes=>1, :cpus => 1, :cores => 1,
179
+ :keys => "/home/#{ENV['G5K_USER']}/.ssh/id_rsa",:walltime => '00:10:00')
180
+ job2 = subject.reserve(:site => @rand_site, :resources => "/switch=2/nodes=1/cpu=1/core=1",
181
+ :keys => "/home/#{ENV['G5K_USER']}/.ssh/id_rsa",:walltime => '00:10:00')
182
+
183
+ expect(job1).to be_an_instance_of(Cute::G5K::G5KJSON)
184
+ expect(job2).to be_an_instance_of(Cute::G5K::G5KJSON)
185
+
186
+ end
187
+
188
+ it "releases all jobs in a site" do
189
+ expect(subject.release_all(@rand_site)).to be true
190
+ end
191
+
192
+ end
@@ -0,0 +1,66 @@
1
+ require 'simplecov'
2
+ require 'webmock/rspec'
3
+
4
+ SimpleCov.start
5
+ # The SimpleCov.start must be issued before any of the application code is required!
6
+ require 'cute'
7
+
8
+ # Disabling all external requests
9
+ WebMock.disable_net_connect!(allow_localhost: true)
10
+
11
+
12
+ class FakeG5KResponse < Hash
13
+ MEDIA_TYPE = {'uid' => "1",
14
+ 'id' => "1",
15
+ 'user' => "test",
16
+ 'items' => [{'uid' => "item1"},{'uid' => "item2"}],
17
+ 'total' => [2],
18
+ 'offset' => 2,
19
+ 'links' => [{"rel" => "self","href" => "path", "type"=>"application/vnd.grid5000.collection+json"},
20
+ {"rel" => "parent","href" => "path", "type"=>"application/vnd.grid5000.collection+json"}],
21
+ 'state' => "running",
22
+ 'started_at' => Time.now,
23
+ 'created_at' => Time.now,
24
+ 'status' => "terminated",
25
+ 'types' => ["deploy"],
26
+ 'assigned_nodes' => ["node1","node2"],
27
+ 'resources_by_type' => {"res" => "val1","subnets" => ["10.140.0.0/22"], "vlans"=>["4"]},
28
+ 'nodes' => {"node1" => {"hard"=> "alive", "soft"=>"busy"}}
29
+ }
30
+ def initialize(num_items = 2)
31
+ MEDIA_TYPE.each { |key,value| self[key] = value}
32
+ self['items'] = []
33
+ num_items.times.each{ self['items'].push(MEDIA_TYPE) }
34
+ end
35
+
36
+ end
37
+
38
+
39
+ RSpec.configure do |config|
40
+ config.fail_fast = true
41
+
42
+ g5k_media_type = FakeG5KResponse.new
43
+ # Example using addressable templates
44
+ # uri_sites = Addressable::Template.new "https://{user}:{password}@api.grid5000.fr/{version}/sites"
45
+ config.before(:each) do
46
+
47
+ stub_request(:any,/^https:\/\/.*\:.*@api.grid5000.fr\/.*/).
48
+ to_return(:status => 200, :body => g5k_media_type.to_json, :headers => {})
49
+
50
+ stub_request(:any,/^https:\/\/fake:fake@api.grid5000.fr\.*/).
51
+ to_return(:status => 401)
52
+
53
+ stub_request(:any,/^https:\/\/.*\:.*@api.grid5000.fr\/...\/sites\/non-found\/.*/).
54
+ to_return(:status => 404)
55
+
56
+ stub_request(:post, /^https:\/\/.*\:.*@api.grid5000.fr\/.*/).
57
+ with(:body => hash_including("resources" => "/slash_22=1+{nonsense},walltime=01:00")).
58
+ to_return(:status => 400, :body => "Oarsub failed: please verify your request syntax")
59
+
60
+ stub_request(:post, /^https:\/\/.*\:.*@api.grid5000.fr\/.*/).
61
+ with(:body => hash_including("environment" => "nonsense")).
62
+ to_return(:status => 500, :body => "Invalid environment specification")
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cute::TakTuk::Stream do
4
+
5
+ subject { Cute::TakTuk::Stream.new([:output, :error, :status ]) }
6
+
7
+ def random_string(length)
8
+ valid_chars = []
9
+ (32..126).each{ |x| valid_chars.push(x.chr)}
10
+ (1..length).map{ valid_chars[rand(valid_chars.length-1)]}.join
11
+ end
12
+
13
+ it "creates an stream" do
14
+ stream = Cute::TakTuk::Stream.new
15
+ expect(stream.types.empty?).to be true
16
+ end
17
+
18
+ it "checking types" do
19
+ stream = Cute::TakTuk::Stream.new([:output, :error, :status ])
20
+ expect(stream.types.length).to be 3
21
+ end
22
+
23
+ it "returns empty result" do
24
+ string = random_string(50)
25
+ expect(subject.parse(string).empty?).to be true
26
+ end
27
+
28
+ it "returns empty value" do
29
+ string ="machine.fr/output/aaaaaaaaaa"
30
+ result = subject.parse(string)
31
+ expect(result.values.first[:output].length).to be 0
32
+ end
33
+
34
+ it "parses simple string" do
35
+ value = random_string(50)
36
+ string ="machine.fr/output/123:#{value}\n"
37
+ expect(subject.parse(string).empty?).to be false
38
+ end
39
+
40
+ it "parses string with number" do
41
+ string = "machine/output/1:1\n"
42
+ expect(subject.parse(string).empty?).to be false
43
+ end
44
+
45
+ it "parses the same line twice" do
46
+ string = "machine/output/1:1\n output/machine/1:2\n"
47
+ expect(subject.parse(string).empty?).to be false
48
+ end
49
+
50
+ it "has just one key" do
51
+ string = "machine/output/1:1\n"
52
+ expect(subject.parse(string).keys.length).to be 1
53
+ end
54
+
55
+ it "has two keys" do
56
+ string = "machine1/output/1:1\nmachine2/output/1:2\n"
57
+ expect(subject.parse(string).keys.length).to be 2
58
+ end
59
+
60
+ it "returns the same output" do
61
+ value = random_string(100)
62
+ stdout = "1:"+value
63
+ string = "machine.fr/output/#{stdout}"
64
+ result = subject.parse(string)
65
+ expect(result.values.first[:output]).to eq(value)
66
+ end
67
+
68
+ it "parses very long output" do
69
+ value = random_string(1000)
70
+ stdout = "1:"+value
71
+ string = "machine.fr/output/#{stdout}"
72
+ result = subject.parse(string)
73
+ expect(result.values.first[:output]).to eq(value)
74
+ end
75
+
76
+ it "parses hostname and ip" do
77
+ value = random_string(1000)
78
+ stdout = "1:"+value
79
+ machines =["machine.fr","192.168.101.56"]
80
+ string = "#{machines[0]}/output/#{stdout} \n#{machines[1]}/output/#{stdout}"
81
+ result = subject.parse(string)
82
+ expect(result.keys).to eq(machines)
83
+ end
84
+
85
+ it "concatenates stdouts" do
86
+ value = random_string(100)
87
+ stdout = "1:"+value # by default streams are formated /machine_name/stream/something:output
88
+ # the "something:" it is just for making the execution time of the regex shorter.
89
+ string = "machine.fr/output/#{stdout}\nmachine.fr/output/#{stdout}\n"
90
+ expect(subject.parse(string).values.first[:output].length).to be 2*value.length + 1
91
+ end
92
+
93
+ end
94
+
95
+ describe "TakTuk" do
96
+ it "raises an argument error" do
97
+ expect{Cute::TakTuk::TakTuk.new()}.to raise_error(ArgumentError)
98
+ end
99
+
100
+ it "raises an argument error" do
101
+ expect{Cute::TakTuk::TakTuk.new("aaa","aaa")}.to raise_error(ArgumentError)
102
+ end
103
+
104
+ it "does not raise error" do
105
+ # TakTuk validate options at the beginning of the execution.
106
+ expect{Cute::TakTuk::TakTuk.new("aaa",{:aaa => "aaa"})}.not_to raise_error
107
+ end
108
+
109
+ it "raises an argu" do
110
+ tak = Cute::TakTuk::TakTuk.new("aaa",{:aaa => "aaa"})
111
+ expect{ tak.exec!("haha")}.to raise_error(ArgumentError)
112
+ end
113
+
114
+ it "raises error due to a non existing file " do
115
+ tak = Cute::TakTuk::TakTuk.new("aaa",{:user => "aaa"})
116
+ expect{ tak.exec!("haha")}.to raise_error
117
+ end
118
+
119
+ it "raises er" do
120
+ tak = Cute::TakTuk::TakTuk.new(["aaa"],{:user => "aaa"})
121
+ expect{ tak.exec!("haha")}.not_to raise_error
122
+ end
123
+
124
+ it "raises er" do
125
+ tak = Cute::TakTuk::TakTuk.new(["aaa"],{:user => "aaa", :config => "conf_ssh_vagrant"})
126
+ expect{ tak.exec!("haha")}.not_to raise_error
127
+ end
128
+
129
+ end
@@ -0,0 +1,71 @@
1
+
2
+ require 'test/unit'
3
+ require 'cute/bash'
4
+
5
+ class TestBash < Test::Unit::TestCase
6
+
7
+ def with_bash(cmd = 'bash', &block)
8
+ return Cute::Bash.bash(cmd, &block)
9
+ end
10
+
11
+ def assert_status_error(&block)
12
+ throws = false
13
+ begin
14
+ with_bash(&block)
15
+ rescue Cute::Bash::StatusError
16
+ throws = true
17
+ end
18
+ assert_equal throws, true
19
+ end
20
+
21
+ def test_files_dirs
22
+ with_bash do
23
+ cd '/'
24
+ assert pwd == '/'
25
+ assert dirs.include?('bin')
26
+ cd 'tmp'
27
+ assert pwd == '/tmp'
28
+ run 'rm -rf /tmp/bash_tests'
29
+ mkdir 'bash_tests'
30
+ cd 'bash_tests'
31
+ assert ls == []
32
+ touch 'file'
33
+ assert ls == [ 'file' ]
34
+ assert ls == files
35
+ mv 'file', 'backup'
36
+ assert ls == [ 'backup' ]
37
+ mkdir 'subdir'
38
+ assert ls.length == 2
39
+ assert dirs == [ 'subdir' ]
40
+ cp 'backup', 'subdir/whatever'
41
+ cd 'subdir'
42
+ assert ls == [ 'whatever' ]
43
+ cd '..'
44
+ assert abspath('subdir/hmmm') == '/tmp/bash_tests/subdir/hmmm'
45
+
46
+ f = tmp_file()
47
+ assert exists(f)
48
+ assert get_type(f) == :file
49
+ assert get_type('/var') == :dir
50
+ append_line f, "1st"
51
+ append_lines f, [ "2nd", "3rd" ]
52
+ assert cat(f) == contents(f)
53
+ lines = cat(f)
54
+ assert lines == "1st\n2nd\n3rd\n"
55
+ end
56
+ end
57
+
58
+ def test_utils
59
+ with_bash do
60
+ assert (echo 'anybody?') == ("anybody?\n")
61
+ assert (bc '2 + 2 * 2') == '6'
62
+ end
63
+ end
64
+
65
+ def test_error
66
+ assert_status_error do
67
+ rm '/'
68
+ end
69
+ end
70
+
71
+ end