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