ruby-cute 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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] :user Username to access the REST API
488
- # @option params [String] :pass Password to access the REST API
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[:user] || config["username"]
500
- @pass = params[:pass] || config["password"]
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
- #{msg_create_file}"
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
- return items.select { |it| it.key?('nodes') }
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, :reservation, :wait, :keys]
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
- # Adding double quotes otherwise we have a syntax error from OAR API
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, :reservation, :wait, :keys]
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', :env => 'wheezy-x64-base')
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
@@ -34,7 +34,7 @@ module Cute
34
34
  def release(n = 1)
35
35
  @lock.synchronize {
36
36
  @used -= n
37
- @cond.signal
37
+ @cond.broadcast
38
38
  }
39
39
  end
40
40
  end
data/lib/cute/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cute
2
- VERSION = "0.3"
2
+ VERSION = "0.4"
3
3
  end
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*20
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.3'
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: 2015-03-13 00:00:00.000000000 Z
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.2.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