ruby-cute 0.3 → 0.4
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.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/README.md +8 -7
- data/Rakefile +1 -0
- data/debian/changelog +2 -2
- data/examples/distem-bootstrap +113 -42
- data/examples/g5k-tutorial.md +999 -0
- data/lib/cute/extensions.rb +16 -0
- data/lib/cute/g5k_api.rb +82 -15
- data/lib/cute/synchronization.rb +1 -1
- data/lib/cute/version.rb +1 -1
- data/spec/g5k_api_spec.rb +5 -2
- data/spec/spec_helper.rb +6 -0
- metadata +4 -3
data/lib/cute/extensions.rb
CHANGED
@@ -36,3 +36,19 @@ class String
|
|
36
36
|
end
|
37
37
|
|
38
38
|
end
|
39
|
+
|
40
|
+
module Cute
|
41
|
+
|
42
|
+
class OARSSHopts < Hash
|
43
|
+
|
44
|
+
def initialize(opts={})
|
45
|
+
|
46
|
+
raise "The argument must be a Hash" unless opts.is_a?(Hash)
|
47
|
+
self.merge!({:user => "oar", :keys => ["~/my_ssh_jobkey"], :port => 6667 })
|
48
|
+
self.merge!(opts)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/cute/g5k_api.rb
CHANGED
@@ -211,7 +211,9 @@ module Cute
|
|
211
211
|
|
212
212
|
machine =`uname -ov`.chop
|
213
213
|
@user_agent = "ruby-cute/#{VERSION} (#{machine}) Ruby #{RUBY_VERSION}"
|
214
|
-
@api = RestClient::Resource.new(@endpoint, :timeout => 30)
|
214
|
+
@api = RestClient::Resource.new(@endpoint, :timeout => 30,:verify_ssl => false)
|
215
|
+
# some versions of restclient do not verify by default SSL certificates , :verify_ssl => true)
|
216
|
+
# SSL verify is disabled due to Grid'5000 API certificate problem
|
215
217
|
@on_error = on_error
|
216
218
|
test_connection
|
217
219
|
end
|
@@ -280,6 +282,12 @@ module Cute
|
|
280
282
|
|
281
283
|
# Issues a Cute::G5K exception according to the http status code
|
282
284
|
def handle_exception(e)
|
285
|
+
|
286
|
+
unless e.respond_to? :http_code
|
287
|
+
raise e
|
288
|
+
end
|
289
|
+
|
290
|
+
# Handling G5k API errors
|
283
291
|
case e.http_code
|
284
292
|
when 500
|
285
293
|
# This part deals with bug: https://intranet.grid5000.fr/bugzilla/show_bug.cgi?id=5912
|
@@ -484,8 +492,8 @@ module Cute
|
|
484
492
|
# @option params [String] :conf_file Path for configuration file
|
485
493
|
# @option params [String] :uri REST API URI to contact
|
486
494
|
# @option params [String] :version Version of the REST API to use
|
487
|
-
# @option params [String] :
|
488
|
-
# @option params [String] :
|
495
|
+
# @option params [String] :username Username to access the REST API
|
496
|
+
# @option params [String] :password Password to access the REST API
|
489
497
|
# @option params [Symbol] :on_error Set to :ignore if you want to ignore {Cute::G5K::RequestFailed ResquestFailed} exceptions.
|
490
498
|
def initialize(params={})
|
491
499
|
config = {}
|
@@ -495,16 +503,20 @@ module Cute
|
|
495
503
|
params[:conf_file] = default_file if File.exist?(default_file)
|
496
504
|
end
|
497
505
|
|
506
|
+
params[:username] ||= params[:user]
|
507
|
+
params[:password] ||= params[:pass] # backward compatibility
|
508
|
+
|
498
509
|
config = YAML.load(File.open(params[:conf_file],'r')) unless params[:conf_file].nil?
|
499
|
-
@user = params[:
|
500
|
-
@pass = params[:
|
510
|
+
@user = params[:username] || config["username"]
|
511
|
+
@pass = params[:password] || config["password"]
|
501
512
|
@uri = params[:uri] || config["uri"]
|
502
513
|
@api_version = params[:version] || config["version"] || "sid"
|
503
514
|
@logger = nil
|
504
515
|
|
505
516
|
begin
|
506
517
|
@g5k_connection = G5KRest.new(@uri,@api_version,@user,@pass,params[:on_error])
|
507
|
-
rescue
|
518
|
+
rescue => e
|
519
|
+
|
508
520
|
msg_create_file = ""
|
509
521
|
if (not File.exist?(default_file)) && params[:conf_file].nil? then
|
510
522
|
msg_create_file = "Please create the file: ~/.grid5000_api.yml and
|
@@ -512,7 +524,9 @@ module Cute
|
|
512
524
|
:conf_file to indicate another file for the credentials"
|
513
525
|
end
|
514
526
|
raise "Unable to authorize against the Grid'5000 API.
|
515
|
-
|
527
|
+
#{e.message}
|
528
|
+
#{msg_create_file}"
|
529
|
+
|
516
530
|
|
517
531
|
end
|
518
532
|
end
|
@@ -656,7 +670,8 @@ module Cute
|
|
656
670
|
.map { |x| "#{x}.#{site}.grid5000.fr"}
|
657
671
|
switch['nodes'] = nodes
|
658
672
|
}
|
659
|
-
|
673
|
+
|
674
|
+
return items
|
660
675
|
end
|
661
676
|
|
662
677
|
# @return [Hash] information of a specific switch available in a given Grid'5000 site.
|
@@ -668,6 +683,37 @@ module Cute
|
|
668
683
|
return s
|
669
684
|
end
|
670
685
|
|
686
|
+
# Returns information using the Metrology API.
|
687
|
+
#
|
688
|
+
# = Example
|
689
|
+
#
|
690
|
+
# You can get detailed information of available metrics in a given site:
|
691
|
+
# get_metric("rennes")
|
692
|
+
#
|
693
|
+
# If you are only interested in the names of the available metrics:
|
694
|
+
# get_metric("rennes").uids #=> ["cpu_nice", "boottime", "bytes_in", ...]
|
695
|
+
#
|
696
|
+
# Then, you can get information about the probes available for a specific metric:
|
697
|
+
# get_metric("rennes",:metric => "network_in")
|
698
|
+
#
|
699
|
+
# Finally, you can query on a specific probe:
|
700
|
+
# get_metric("rennes",:metric => "network_in",:query => {:from => 1450374553, :to => 1450374553, :only => "parasilo-11-eth0"})
|
701
|
+
#
|
702
|
+
# @return [Array] information of a specific metric in a given Grid'5000 site.
|
703
|
+
# @param site [String] a valid Grid'5000 site name
|
704
|
+
# @param [Hash] opts Options for metric query
|
705
|
+
# @option opts [String] :metric specific metric to query on
|
706
|
+
# @option opts [Hash] :query timeseries parameters (e.g. only, resolution, from, to)
|
707
|
+
|
708
|
+
def get_metric(site,opts ={})
|
709
|
+
params = opts[:metric].nil? ? "" : "/#{opts[:metric]}/timeseries"
|
710
|
+
if opts[:query]
|
711
|
+
params+="?"
|
712
|
+
opts[:query].each{ |k,v| params+="#{k}=#{v}&"}
|
713
|
+
end
|
714
|
+
@g5k_connection.get_json(api_uri("sites/#{site}/metrics#{params}")).items
|
715
|
+
end
|
716
|
+
|
671
717
|
# Returns information of all my jobs submitted in a given site.
|
672
718
|
# By default it only shows the jobs in state *running*.
|
673
719
|
# You can specify another state like this:
|
@@ -835,6 +881,7 @@ module Cute
|
|
835
881
|
# @option opts [String] :name Reservation name
|
836
882
|
# @option opts [String] :cmd The command to execute when the job starts (e.g. ./my-script.sh).
|
837
883
|
# @option opts [String] :cluster Valid Grid'5000 cluster
|
884
|
+
# @option opts [String] :queue A specific job queue
|
838
885
|
# @option opts [Array] :subnets 1) prefix_size, 2) number of subnets
|
839
886
|
# @option opts [String] :env Environment name for {http://kadeploy3.gforge.inria.fr/ Kadeploy}
|
840
887
|
# @option opts [Symbol] :vlan Vlan type: :routed, :local, :global
|
@@ -847,7 +894,8 @@ module Cute
|
|
847
894
|
|
848
895
|
# checking valid options
|
849
896
|
valid_opts = [:site, :cluster, :switches, :cpus, :cores, :nodes, :walltime, :cmd,
|
850
|
-
:type, :name, :subnets, :env, :vlan, :properties, :resources,
|
897
|
+
:type, :name, :subnets, :env, :vlan, :properties, :resources,
|
898
|
+
:reservation, :wait, :keys, :queue, :env_user]
|
851
899
|
unre_opts = opts.keys - valid_opts
|
852
900
|
raise ArgumentError, "Unrecognized option #{unre_opts}" unless unre_opts.empty?
|
853
901
|
|
@@ -868,6 +916,7 @@ module Cute
|
|
868
916
|
resources = opts.fetch(:resources, "")
|
869
917
|
type = :deploy if opts[:env]
|
870
918
|
keys = opts[:keys]
|
919
|
+
queue = opts[:queue]
|
871
920
|
|
872
921
|
vlan_opts = {:routed => "kavlan",:global => "kavlan-global",:local => "kavlan-local"}
|
873
922
|
vlan = nil
|
@@ -881,11 +930,11 @@ module Cute
|
|
881
930
|
|
882
931
|
raise 'At least nodes, time and site must be given' if [nodes, walltime, site].any? { |x| x.nil? }
|
883
932
|
|
933
|
+
raise 'nodes should be an integer or a string containing either ALL or BEST' unless (nodes.is_a?(Fixnum) or ["ALL","BEST"].include?(nodes))
|
934
|
+
|
884
935
|
secs = walltime.to_secs
|
885
936
|
walltime = walltime.to_time
|
886
937
|
|
887
|
-
raise 'Nodes must be an integer.' unless nodes.is_a?(Integer)
|
888
|
-
|
889
938
|
command = "sleep #{secs}" if command.nil?
|
890
939
|
type = type.to_sym unless type.nil?
|
891
940
|
|
@@ -911,6 +960,7 @@ module Cute
|
|
911
960
|
|
912
961
|
payload['properties'] = properties unless properties.nil?
|
913
962
|
payload['types'] = [ type.to_s ] unless type.nil?
|
963
|
+
payload['queue'] = queue if queue
|
914
964
|
|
915
965
|
if not type == :deploy
|
916
966
|
if opts[:keys]
|
@@ -930,8 +980,7 @@ module Cute
|
|
930
980
|
# The request has to be redirected to the OAR API given that Grid'5000 API
|
931
981
|
# does not support some OAR options.
|
932
982
|
if payload['import-job-key-from-file'] then
|
933
|
-
|
934
|
-
payload["resources"] = "\"#{payload["resources"]}\""
|
983
|
+
|
935
984
|
temp = @g5k_connection.post_json(api_uri("sites/#{site}/internal/oarapi/jobs"),payload)
|
936
985
|
sleep 1 # This is for being sure that our job appears on the list
|
937
986
|
r = get_my_jobs(site,nil).select{ |j| j["uid"] == temp["id"] }.first
|
@@ -1035,7 +1084,8 @@ module Cute
|
|
1035
1084
|
|
1036
1085
|
# checking valid options, same as reserve option even though some option dont make any sense
|
1037
1086
|
valid_opts = [:site, :cluster, :switches, :cpus, :cores, :nodes, :walltime, :cmd,
|
1038
|
-
:type, :name, :subnets, :env, :vlan, :properties, :resources,
|
1087
|
+
:type, :name, :subnets, :env, :vlan, :properties, :resources,
|
1088
|
+
:reservation, :wait, :keys, :queue, :env_user]
|
1039
1089
|
|
1040
1090
|
unre_opts = opts.keys - valid_opts
|
1041
1091
|
raise ArgumentError, "Unrecognized option #{unre_opts}" unless unre_opts.empty?
|
@@ -1069,6 +1119,8 @@ module Cute
|
|
1069
1119
|
info "Found VLAN with uid = #{vlan}"
|
1070
1120
|
end
|
1071
1121
|
|
1122
|
+
payload['user'] = opts[:env_user] unless opts[:env_user].nil?
|
1123
|
+
|
1072
1124
|
info "Creating deployment"
|
1073
1125
|
|
1074
1126
|
begin
|
@@ -1135,9 +1187,10 @@ module Cute
|
|
1135
1187
|
#
|
1136
1188
|
# g5k = Cute::G5K::API.new()
|
1137
1189
|
#
|
1138
|
-
# job = g5k.reserve(:nodes => 1, :site => 'lyon', :
|
1190
|
+
# job = g5k.reserve(:nodes => 1, :site => 'lyon', :type => :deploy)
|
1139
1191
|
#
|
1140
1192
|
# begin
|
1193
|
+
# g5k.deploy(job,:env => 'wheezy-x64-base')
|
1141
1194
|
# g5k.wait_for_deploy(job,:wait_time => 100)
|
1142
1195
|
# rescue Cute::G5K::EventTimeout
|
1143
1196
|
# puts "We waited too long let's release the job"
|
@@ -1171,6 +1224,20 @@ module Cute
|
|
1171
1224
|
|
1172
1225
|
end
|
1173
1226
|
|
1227
|
+
# It returns an array of machines that did not deploy successfully
|
1228
|
+
# = Example
|
1229
|
+
# It can be used to try a new deploy:
|
1230
|
+
#
|
1231
|
+
# badnodes = g5k.check_deployment(job["deploy"].last)
|
1232
|
+
# g5k.deploy(job,:nodes => badnodes, :env => 'wheezy-x64-base')
|
1233
|
+
# g5k.wait_for_deploy(job)
|
1234
|
+
#
|
1235
|
+
# @return [Array] machines that did not deploy successfully
|
1236
|
+
# @param deploy_info [Hash] deployment structure information
|
1237
|
+
def check_deployment(deploy_info)
|
1238
|
+
deploy_info["result"].select{ |p,v| v["state"] == "KO"}.keys
|
1239
|
+
end
|
1240
|
+
|
1174
1241
|
private
|
1175
1242
|
# Handles the output of messages within the module
|
1176
1243
|
# @param msg [String] message to show
|
data/lib/cute/synchronization.rb
CHANGED
data/lib/cute/version.rb
CHANGED
data/spec/g5k_api_spec.rb
CHANGED
@@ -73,6 +73,10 @@ describe Cute::G5K::API do
|
|
73
73
|
# expect{ subject.reserve(:site => @rand_site, :resources =>"{ib30g='YES'}/nodes=2")}.to raise_error(Cute::G5K::BadRequest)
|
74
74
|
end
|
75
75
|
|
76
|
+
it "raises a bad request using OAR API" do
|
77
|
+
expect{subject.reserve(:site => @rand_site, :resources =>"nodes=1",:keys => "~/jobkey_nonexisting")}.to raise_error(Cute::G5K::BadRequest)
|
78
|
+
end
|
79
|
+
|
76
80
|
it "raises an exception at deploying" do
|
77
81
|
expect{ subject.reserve(:site => @rand_site, :nodes => 1, :env => "nonsense")}.to raise_error(Cute::G5K::RequestFailed)
|
78
82
|
end
|
@@ -126,7 +130,7 @@ describe Cute::G5K::API do
|
|
126
130
|
end
|
127
131
|
|
128
132
|
it "performs an advanced reservation" do
|
129
|
-
time_schedule = Time.now + 60*
|
133
|
+
time_schedule = Time.now + 60*10
|
130
134
|
job =subject.reserve(:site => @rand_site, :nodes => 1, :reservation => time_schedule.strftime("%Y-%m-%d %H:%M:%S"))
|
131
135
|
subject.release(job)
|
132
136
|
end
|
@@ -137,7 +141,6 @@ describe Cute::G5K::API do
|
|
137
141
|
subject.release(job)
|
138
142
|
end
|
139
143
|
|
140
|
-
|
141
144
|
it "does not deploy immediately" do
|
142
145
|
job = subject.reserve(:site => @rand_site, :type => :deploy )
|
143
146
|
expect(job).to include("types" => ["deploy"])
|
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,8 @@ require 'webmock/rspec'
|
|
3
3
|
|
4
4
|
SimpleCov.start
|
5
5
|
# The SimpleCov.start must be issued before any of the application code is required!
|
6
|
+
# SimpleCov hacks $:, so it needs to be re-configured here, before cute is loaded.
|
7
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
6
8
|
require 'cute'
|
7
9
|
|
8
10
|
# Disabling all external requests
|
@@ -57,6 +59,10 @@ RSpec.configure do |config|
|
|
57
59
|
with(:body => hash_including("resources" => "/slash_22=1+{nonsense},walltime=01:00")).
|
58
60
|
to_return(:status => 400, :body => "Oarsub failed: please verify your request syntax")
|
59
61
|
|
62
|
+
stub_request(:post, /^https:\/\/.*\:.*@api.grid5000.fr\/.*/).
|
63
|
+
with(:body => hash_including("import-job-key-from-file" => [ File.expand_path("~/jobkey_nonexisting") ])).
|
64
|
+
to_return(:status => 400, :body => "Oarsub failed: please verify your request syntax")
|
65
|
+
|
60
66
|
stub_request(:post, /^https:\/\/.*\:.*@api.grid5000.fr\/.*/).
|
61
67
|
with(:body => hash_including("environment" => "nonsense")).
|
62
68
|
to_return(:status => 500, :body => "Invalid environment specification")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-cute
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.4'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Algorille team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -191,6 +191,7 @@ files:
|
|
191
191
|
- debian/source/format
|
192
192
|
- debian/watch
|
193
193
|
- examples/distem-bootstrap
|
194
|
+
- examples/g5k-tutorial.md
|
194
195
|
- examples/g5k_exp1.rb
|
195
196
|
- examples/g5k_exp_virt.rb
|
196
197
|
- lib/cute.rb
|
@@ -231,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
232
|
version: 1.3.6
|
232
233
|
requirements: []
|
233
234
|
rubyforge_project:
|
234
|
-
rubygems_version: 2.
|
235
|
+
rubygems_version: 2.4.5.1
|
235
236
|
signing_key:
|
236
237
|
specification_version: 4
|
237
238
|
summary: Critically Useful Tools for Experiments
|