openstack 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -5
- data/VERSION +1 -1
- data/lib/openstack/compute/connection.rb +11 -3
- data/lib/openstack/connection.rb +417 -254
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 019f3ab58603e2185aa4558273454869205efb90
|
4
|
+
data.tar.gz: d6a1995446cc59cf255d03354552738e81d3efeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cde951d618983f94dc1e808d60240647ef5ead95d6d345fbd9e5b25929a0ad89742f279e5d2d9e00082ec6c71bce816ba4573fb1b5b8d9f9c24d9d6d94c519e0
|
7
|
+
data.tar.gz: 2eab3097fbd3de0458141ecbd06a4d55cb949369ef46df26220c9e3e07eac1c82b7dafca2cd04a9ee9779b983c07d1cb019ad34b4be4a918e38d5bcd0debce1c
|
data/README.md
CHANGED
@@ -1,18 +1,33 @@
|
|
1
1
|
# Ruby OpenStack
|
2
2
|
|
3
|
-
Ruby OpenStack Compute, Object-Store, Block Storage and Network bindings for the OpenStack API. It supports Keystone Authentication (v1.0 and
|
3
|
+
Ruby OpenStack Compute, Object-Store, Block Storage and Network bindings for the OpenStack API. It supports Keystone Authentication (v1.0, v2.0 and v3.0), Nova and Swift. See the [getting started](https://github.com/ruby-openstack/ruby-openstack/wiki) for documentation.
|
4
4
|
|
5
|
-
[![Build Status](https://travis-ci.org/ruby-openstack/ruby-openstack.svg?branch=
|
5
|
+
[![Build Status](https://travis-ci.org/ruby-openstack/ruby-openstack.svg?branch=master)](https://travis-ci.org/ruby-openstack/ruby-openstack)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add the Ruby OpenStack as dependency to your project Gemfile:
|
10
|
+
|
11
|
+
```
|
12
|
+
source 'https://rubygems.org'
|
13
|
+
gem 'openstack'
|
14
|
+
```
|
15
|
+
|
16
|
+
## Maintainer
|
17
|
+
|
18
|
+
The library is currently maintained by [Nitrado](http://nitrado.net/).
|
19
|
+
The people behind the OpenStack Team from Nitrado are Alexander Birkner and Aaron Fischer.
|
6
20
|
|
7
21
|
## Authors
|
8
22
|
|
23
|
+
* Alexander Birkner (alexander.birkner@marbis.net)
|
24
|
+
* Aaron Fischer (aaron.fischer@marbis.net)
|
9
25
|
* Marios Andreou (marios@redhat.com)
|
10
26
|
* Dan Prince (dprince@redhat.com)
|
11
27
|
* Naveed Massjouni (naveedm9@gmail.com)
|
12
|
-
* Aaron Fischer (aaron.fischer@marbis.net)
|
13
|
-
* Alexander Birkner (alexander.birkner@marbis.net)
|
14
28
|
|
15
|
-
Initial code checkin on May 23rd 2012 - code refactored from and based on the Rackspace [Cloud Servers gem](https://github.com/rackspace/ruby-openstack-compute) and [Rackspace Cloud Files gem](https://github.com/rackspace/ruby-cloudfiles).
|
29
|
+
Initial code checkin on May 23rd 2012 - code refactored from and based on the Rackspace [Cloud Servers gem](https://github.com/rackspace/ruby-openstack-compute) and [Rackspace Cloud Files gem](https://github.com/rackspace/ruby-cloudfiles).
|
30
|
+
Since Nov 2015 the library is maintained by [Nitrado](http://nitrado.net/).
|
16
31
|
|
17
32
|
## License
|
18
33
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.0
|
@@ -117,7 +117,9 @@ module Compute
|
|
117
117
|
# => "NewServerSHMGpvI"
|
118
118
|
def create_server(options)
|
119
119
|
raise OpenStack::Exception::MissingArgument, "Server name, flavorRef, and imageRef, must be supplied" unless (options[:name] && options[:flavorRef] && options[:imageRef])
|
120
|
-
|
120
|
+
if options[:personality]
|
121
|
+
options[:personality] = Personalities.get_personality(options[:personality])
|
122
|
+
end
|
121
123
|
options[:security_groups] = (options[:security_groups] || []).inject([]){|res, c| res << {"name"=>c} ;res}
|
122
124
|
data = JSON.generate(:server => options)
|
123
125
|
response = @connection.csreq("POST",@connection.service_host,"#{@connection.service_path}/servers",@connection.service_port,@connection.service_scheme,{'content-type' => 'application/json'},data)
|
@@ -125,7 +127,7 @@ module Compute
|
|
125
127
|
server_info = JSON.parse(response.body)['server']
|
126
128
|
server = OpenStack::Compute::Server.new(self,server_info['id'])
|
127
129
|
server.adminPass = server_info['adminPass']
|
128
|
-
|
130
|
+
server
|
129
131
|
end
|
130
132
|
|
131
133
|
# Returns an array of hashes listing available server images that you have access too,
|
@@ -508,12 +510,18 @@ module Compute
|
|
508
510
|
true
|
509
511
|
end
|
510
512
|
|
511
|
-
def
|
513
|
+
def get_floating_ip_pools
|
512
514
|
check_extension 'os-floating-ip-pools'
|
513
515
|
response = @connection.req('GET', '/os-floating-ip-pools')
|
514
516
|
JSON.parse(response.body)['floating_ip_pools']
|
515
517
|
end
|
516
518
|
|
519
|
+
#deprecated - please do not use this typo method :)
|
520
|
+
def get_floating_ip_polls
|
521
|
+
puts "get_floating_ip_polls() is DEPRECATED: Please use get_floating_ip_pools() without typo!"
|
522
|
+
self.get_floating_ip_pools
|
523
|
+
end
|
524
|
+
|
517
525
|
def get_floating_ips_bulk
|
518
526
|
check_extension 'os-floating-ips-bulk'
|
519
527
|
response = @connection.req('GET', '/os-floating-ips-bulk')
|
data/lib/openstack/connection.rb
CHANGED
@@ -22,6 +22,14 @@ class Connection
|
|
22
22
|
attr_reader :auth_path
|
23
23
|
attr_reader :service_name
|
24
24
|
attr_reader :service_type
|
25
|
+
attr_reader :user_domain
|
26
|
+
attr_reader :user_domain_id
|
27
|
+
attr_reader :project_id
|
28
|
+
attr_reader :project_name
|
29
|
+
attr_reader :project_domain_name
|
30
|
+
attr_reader :project_domain_id
|
31
|
+
attr_reader :domain_name
|
32
|
+
attr_reader :domain_id
|
25
33
|
attr_reader :proxy_host
|
26
34
|
attr_reader :proxy_port
|
27
35
|
attr_reader :ca_cert
|
@@ -45,14 +53,23 @@ class Connection
|
|
45
53
|
#
|
46
54
|
# options hash:
|
47
55
|
#
|
48
|
-
# :auth_method - Type of authentication - 'password', 'key', 'rax-kskey' - defaults to 'password'
|
49
56
|
# :username - Your OpenStack username or public key, depending on auth_method. *required*
|
57
|
+
# :auth_method - Type of authentication - 'password', 'key', 'rax-kskey', 'token' - defaults to 'password'.
|
58
|
+
# For auth v3.0 valid options are 'password', 'token', 'password_user_id'
|
50
59
|
# :authtenant_name OR :authtenant_id - Your OpenStack tenant name or id *required*. Defaults to username.
|
51
60
|
# passing :authtenant will default to using that parameter as tenant name.
|
52
61
|
# :api_key - Your OpenStack API key *required* (either private key or password, depending on auth_method)
|
53
62
|
# :auth_url - Configurable auth_url endpoint.
|
54
63
|
# :service_name - (Optional for v2.0 auth only). The optional name of the compute service to use.
|
55
64
|
# :service_type - (Optional for v2.0 auth only). Defaults to "compute"
|
65
|
+
# :user_domain - (Optional for v3.0 auth only). The optional name of the user domain.
|
66
|
+
# :user_domain_id - (Optional for v3.0 auth only). Defaults to "default"
|
67
|
+
# :project_id - (Optional for v3.0 auth only). For authorization scoping
|
68
|
+
# :project_name - (Optional for v3.0 auth only). For authorization scoping
|
69
|
+
# :project_domain_name - (Optional for v3.0 auth only). For authorization scoping
|
70
|
+
# :project_domain_id - (Optional for v3.0 auth only). For authorization scoping
|
71
|
+
# :domain_name - (Optional for v3.0 auth only). For authorization scoping
|
72
|
+
# :domain_id - (Optional for v3.0 auth only). For authorization scoping
|
56
73
|
# :region - (Optional for v2.0 auth only). The specific service region to use. Defaults to first returned region.
|
57
74
|
# :retry_auth - Whether to retry if your auth token expires (defaults to true)
|
58
75
|
# :proxy_host - If you need to connect through a proxy, supply the hostname here
|
@@ -60,6 +77,7 @@ class Connection
|
|
60
77
|
# :ca_cert - path to a CA chain in PEM format
|
61
78
|
# :ssl_version - explicitly set an version (:SSLv3 etc, see OpenSSL::SSL::SSLContext::METHODS)
|
62
79
|
# :is_debug - Only for development purpose for debug output
|
80
|
+
# :endpoint_type - Type of endpoint. Optional. 'publicURL', 'internalURL', 'adminURL'
|
63
81
|
#
|
64
82
|
# The options hash is used to create a new OpenStack::Connection object
|
65
83
|
# (private constructor) and this is passed to the constructor of OpenStack::Compute::Connection
|
@@ -98,6 +116,14 @@ class Connection
|
|
98
116
|
@auth_method = options[:auth_method] || "password"
|
99
117
|
@service_name = options[:service_name] || nil
|
100
118
|
@service_type = options[:service_type] || "compute"
|
119
|
+
@user_domain_id = options[:user_domain_id] || "default"
|
120
|
+
@user_domain = options[:user_domain] || nil
|
121
|
+
@project_id = options[:project_id] || nil
|
122
|
+
@project_name = options[:project_name] || nil
|
123
|
+
@project_domain_name = options[:project_domain_name] || nil
|
124
|
+
@project_domain_id = options[:project_domain_id] || nil
|
125
|
+
@domain_name = options[:domain_name] || nil
|
126
|
+
@domain_id = options[:domain_id] || nil
|
101
127
|
@region = options[:region] || @region = nil
|
102
128
|
@regions_list = {} # this is populated during authentication - from the returned service catalogue
|
103
129
|
@is_debug = options[:is_debug]
|
@@ -144,10 +170,10 @@ class Connection
|
|
144
170
|
start_http(server,path,port,scheme,hdrhash)
|
145
171
|
response = @http[server].request(request)
|
146
172
|
if @is_debug
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
173
|
+
puts "REQUEST: PUT => #{path}"
|
174
|
+
puts data if data
|
175
|
+
puts "RESPONSE: #{response.body}"
|
176
|
+
puts '----------------------------------------'
|
151
177
|
end
|
152
178
|
raise OpenStack::Exception::ExpiredAuthToken if response.code == "401"
|
153
179
|
response
|
@@ -185,10 +211,10 @@ class Connection
|
|
185
211
|
response = @http[server].request(request)
|
186
212
|
end
|
187
213
|
if @is_debug
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
214
|
+
puts "REQUEST: #{method.to_s} => #{path}"
|
215
|
+
puts data if data
|
216
|
+
puts "RESPONSE: #{response.body}"
|
217
|
+
puts '----------------------------------------'
|
192
218
|
end
|
193
219
|
raise OpenStack::Exception::ExpiredAuthToken if response.code == "401"
|
194
220
|
response
|
@@ -264,64 +290,119 @@ class Connection
|
|
264
290
|
end
|
265
291
|
end
|
266
292
|
|
267
|
-
end #end class Connection
|
293
|
+
end #end class Connection
|
268
294
|
|
269
|
-
#============================
|
270
|
-
# OpenStack::Authentication
|
271
|
-
#============================
|
295
|
+
#============================
|
296
|
+
# OpenStack::Authentication
|
297
|
+
#============================
|
272
298
|
|
273
|
-
class Authentication
|
299
|
+
class Authentication
|
274
300
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
301
|
+
# Performs an authentication to the OpenStack auth server.
|
302
|
+
# If it succeeds, it sets the service_host, service_path, service_port,
|
303
|
+
# service_scheme, authtoken, and authok variables on the connection.
|
304
|
+
# If it fails, it raises an exception.
|
279
305
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
306
|
+
def self.init(conn)
|
307
|
+
if conn.auth_path =~ /.*v3\/?$/
|
308
|
+
AuthV30.new(conn)
|
309
|
+
elsif conn.auth_path =~ /.*v2.0\/?$/
|
310
|
+
AuthV20.new(conn)
|
311
|
+
else
|
312
|
+
AuthV10.new(conn)
|
313
|
+
end
|
285
314
|
end
|
286
315
|
end
|
287
316
|
|
288
|
-
|
317
|
+
private
|
318
|
+
|
319
|
+
class AuthV10
|
320
|
+
def initialize(connection)
|
321
|
+
tries = connection.retries
|
322
|
+
time = 3
|
289
323
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
# use the ca_cert if were given one, and make sure we verify!
|
306
|
-
if !connection.ca_cert.nil?
|
307
|
-
server.ca_file = connection.ca_cert
|
308
|
-
server.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
324
|
+
hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
|
325
|
+
begin
|
326
|
+
server = Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new(connection.auth_host, connection.auth_port)
|
327
|
+
if connection.auth_scheme == "https"
|
328
|
+
server.use_ssl = true
|
329
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
330
|
+
|
331
|
+
# use the ca_cert if were given one, and make sure to verify!
|
332
|
+
if !connection.ca_cert.nil?
|
333
|
+
server.ca_file = connection.ca_cert
|
334
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
335
|
+
end
|
336
|
+
|
337
|
+
# explicitly set the SSL version to use
|
338
|
+
server.ssl_version = connection.ssl_version if !connection.ssl_version.nil?
|
309
339
|
end
|
340
|
+
server.start
|
341
|
+
rescue
|
342
|
+
puts "Can't connect to the server: #{tries} tries to reconnect" if connection.is_debug
|
343
|
+
sleep time += 1
|
344
|
+
retry unless (tries -= 1) <= 0
|
345
|
+
raise OpenStack::Exception::Connection, "Unable to connect to #{server}"
|
346
|
+
end
|
310
347
|
|
311
|
-
|
312
|
-
|
348
|
+
response = server.get(connection.auth_path, hdrhash)
|
349
|
+
|
350
|
+
if (response.code =~ /^20./)
|
351
|
+
connection.authtoken = response["x-auth-token"]
|
352
|
+
case connection.service_type
|
353
|
+
when "compute"
|
354
|
+
uri = URI.parse(response["x-server-management-url"])
|
355
|
+
when "object-store"
|
356
|
+
uri = URI.parse(response["x-storage-url"])
|
357
|
+
end
|
358
|
+
raise OpenStack::Exception::Authentication, "Unexpected Response from #{connection.auth_host} - couldn't get service URLs: \"x-server-management-url\" is: #{response["x-server-management-url"]} and \"x-storage-url\" is: #{response["x-storage-url"]}" if (uri.host.nil? || uri.host=="")
|
359
|
+
connection.service_host = uri.host
|
360
|
+
connection.service_path = uri.path
|
361
|
+
connection.service_port = uri.port
|
362
|
+
connection.service_scheme = uri.scheme
|
363
|
+
connection.authok = true
|
364
|
+
else
|
365
|
+
connection.authok = false
|
366
|
+
raise OpenStack::Exception::Authentication, "Authentication failed with response code #{response.code}"
|
313
367
|
end
|
314
|
-
server.
|
315
|
-
rescue
|
316
|
-
puts "Can't connect to the server: #{tries} tries to reconnect" if connection.is_debug
|
317
|
-
sleep time += 1
|
318
|
-
retry unless (tries -= 1) <= 0
|
319
|
-
raise OpenStack::Exception::Connection, "Unable to connect to #{server}"
|
368
|
+
server.finish
|
320
369
|
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class AuthV20
|
373
|
+
attr_reader :uri
|
374
|
+
attr_reader :version
|
375
|
+
def initialize(connection)
|
376
|
+
|
377
|
+
tries = connection.retries
|
378
|
+
time = 3
|
379
|
+
|
380
|
+
begin
|
381
|
+
server = Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new(connection.auth_host, connection.auth_port)
|
382
|
+
if connection.auth_scheme == "https"
|
383
|
+
server.use_ssl = true
|
384
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
385
|
+
|
386
|
+
# use the ca_cert if were given one, and make sure we verify!
|
387
|
+
if !connection.ca_cert.nil?
|
388
|
+
server.ca_file = connection.ca_cert
|
389
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
390
|
+
end
|
391
|
+
|
392
|
+
# explicitly set the SSL version to use
|
393
|
+
server.ssl_version = connection.ssl_version if !connection.ssl_version.nil?
|
394
|
+
end
|
395
|
+
server.start
|
396
|
+
rescue
|
397
|
+
puts "Can't connect to the server: #{tries} tries to reconnect" if connection.is_debug
|
398
|
+
sleep time += 1
|
399
|
+
retry unless (tries -= 1) <= 0
|
400
|
+
raise OpenStack::Exception::Connection, "Unable to connect to #{server}"
|
401
|
+
end
|
321
402
|
|
322
|
-
|
403
|
+
@uri = String.new
|
323
404
|
|
324
|
-
|
405
|
+
case connection.auth_method
|
325
406
|
when "password"
|
326
407
|
auth_data = JSON.generate({ "auth" => { "passwordCredentials" => { "username" => connection.authuser, "password" => connection.authkey }, connection.authtenant[:type] => connection.authtenant[:value]}})
|
327
408
|
when "rax-kskey"
|
@@ -330,248 +411,330 @@ class AuthV20
|
|
330
411
|
auth_data = JSON.generate({"auth" => { "apiAccessKeyCredentials" => {"accessKey" => connection.authuser, "secretKey" => connection.authkey}, connection.authtenant[:type] => connection.authtenant[:value]}})
|
331
412
|
else
|
332
413
|
raise Exception::InvalidArgument, "Unrecognized auth method #{connection.auth_method}"
|
333
|
-
|
414
|
+
end
|
334
415
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
end
|
347
|
-
if connection.service_name
|
348
|
-
check_service_name = connection.service_name
|
349
|
-
else
|
350
|
-
check_service_name = service['name']
|
351
|
-
end
|
352
|
-
if service['type'] == connection.service_type and service['name'] == check_service_name
|
353
|
-
endpoints = service["endpoints"]
|
354
|
-
if connection.region
|
355
|
-
endpoints.each do |ep|
|
356
|
-
if ep["region"] and ep["region"].upcase == connection.region.upcase
|
357
|
-
@uri = URI.parse(ep[connection.endpoint_type])
|
358
|
-
end
|
359
|
-
end
|
360
|
-
else
|
361
|
-
@uri = URI.parse(endpoints[0][connection.endpoint_type])
|
416
|
+
response = server.post(connection.auth_path.chomp("/")+"/tokens", auth_data, {'Content-Type' => 'application/json'})
|
417
|
+
if (response.code =~ /^20./)
|
418
|
+
resp_data=JSON.parse(response.body)
|
419
|
+
connection.authtoken = resp_data['access']['token']['id']
|
420
|
+
implemented_services = resp_data["access"]["serviceCatalog"].inject([]){|res, current| res << current["type"] ;res}
|
421
|
+
raise OpenStack::Exception::NotImplemented.new("The requested service: \"#{connection.service_type}\" is not present " +
|
422
|
+
"in the returned service catalogue.", 501, "#{resp_data["access"]["serviceCatalog"]}") unless implemented_services.include?(connection.service_type)
|
423
|
+
resp_data['access']['serviceCatalog'].each do |service|
|
424
|
+
service["endpoints"].each do |endpoint|
|
425
|
+
connection.regions_list[endpoint["region"]] ||= []
|
426
|
+
connection.regions_list[endpoint["region"]] << {:service=>service["type"], :versionId => endpoint["versionId"]}
|
362
427
|
end
|
363
|
-
if
|
364
|
-
|
428
|
+
if connection.service_name
|
429
|
+
check_service_name = connection.service_name
|
365
430
|
else
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
431
|
+
check_service_name = service['name']
|
432
|
+
end
|
433
|
+
if service['type'] == connection.service_type and service['name'] == check_service_name
|
434
|
+
endpoints = service["endpoints"]
|
435
|
+
if connection.region
|
436
|
+
endpoints.each do |ep|
|
437
|
+
if ep["region"] and ep["region"].upcase == connection.region.upcase
|
438
|
+
@uri = URI.parse(ep[connection.endpoint_type])
|
439
|
+
end
|
440
|
+
end
|
441
|
+
else
|
442
|
+
@uri = URI.parse(endpoints[0][connection.endpoint_type])
|
443
|
+
end
|
444
|
+
if @uri == ""
|
445
|
+
raise OpenStack::Exception::Authentication, "No API endpoint for region #{connection.region}"
|
446
|
+
else
|
447
|
+
if @version #already got one version of endpoints
|
448
|
+
current_version = get_version_from_response(service,connection.endpoint_type)
|
449
|
+
if @version.to_f > current_version.to_f
|
450
|
+
next
|
451
|
+
end
|
370
452
|
end
|
453
|
+
#grab version to check next time round for multi-version deployments
|
454
|
+
@version = get_version_from_response(service,connection.endpoint_type)
|
455
|
+
connection.service_host = @uri.host
|
456
|
+
connection.service_path = @uri.path
|
457
|
+
connection.service_port = @uri.port
|
458
|
+
connection.service_scheme = @uri.scheme
|
459
|
+
connection.authok = true
|
371
460
|
end
|
372
|
-
#grab version to check next time round for multi-version deployments
|
373
|
-
@version = get_version_from_response(service,connection.endpoint_type)
|
374
|
-
connection.service_host = @uri.host
|
375
|
-
connection.service_path = @uri.path
|
376
|
-
connection.service_port = @uri.port
|
377
|
-
connection.service_scheme = @uri.scheme
|
378
|
-
connection.authok = true
|
379
461
|
end
|
380
462
|
end
|
463
|
+
else
|
464
|
+
connection.authtoken = false
|
465
|
+
raise OpenStack::Exception::Authentication, "Authentication failed with response code #{response.code}"
|
381
466
|
end
|
382
|
-
|
383
|
-
connection.authtoken = false
|
384
|
-
raise OpenStack::Exception::Authentication, "Authentication failed with response code #{response.code}"
|
467
|
+
server.finish if server.started?
|
385
468
|
end
|
386
|
-
server.finish if server.started?
|
387
|
-
end
|
388
469
|
|
389
|
-
|
390
|
-
|
391
|
-
|
470
|
+
def get_version_from_response(service,endpoint_type)
|
471
|
+
service["endpoints"].first["versionId"] || parse_version_from_endpoint(service["endpoints"].first[endpoint_type])
|
472
|
+
end
|
392
473
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
474
|
+
#IN --> https://az-2.region-a.geo-1.compute.hpcloudsvc.com/v1.1/46871569847393
|
475
|
+
#OUT --> "1.1"
|
476
|
+
def parse_version_from_endpoint(endpoint)
|
477
|
+
endpoint.match(/\/v(\d).(\d)/).to_s.sub("/v", "")
|
478
|
+
end
|
397
479
|
end
|
398
480
|
|
399
|
-
|
481
|
+
# Implement the Identity Version 3.x API
|
482
|
+
# http://developer.openstack.org/api-ref-identity-v3.html
|
483
|
+
# This is still experimental, so don't use in production.
|
484
|
+
class AuthV30
|
485
|
+
attr_reader :uri, :version
|
486
|
+
# TODO: Parse the version for an endpoint
|
400
487
|
|
401
|
-
|
488
|
+
def initialize(connection)
|
489
|
+
tries = connection.retries
|
490
|
+
time = 3
|
402
491
|
|
403
|
-
|
492
|
+
begin
|
493
|
+
server = Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new(connection.auth_host, connection.auth_port)
|
494
|
+
if connection.auth_scheme == "https"
|
495
|
+
server.use_ssl = true
|
496
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
497
|
+
|
498
|
+
# use the ca_cert if were given one, and make sure we verify!
|
499
|
+
unless connection.ca_cert.nil?
|
500
|
+
server.ca_file = connection.ca_cert
|
501
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
502
|
+
end
|
404
503
|
|
405
|
-
|
406
|
-
|
504
|
+
# explicitly set the SSL version to use
|
505
|
+
server.ssl_version = connection.ssl_version unless connection.ssl_version.nil?
|
506
|
+
end
|
507
|
+
server.start
|
508
|
+
rescue
|
509
|
+
puts "Can't connect to the server: #{tries} tries to reconnect" if connection.is_debug
|
510
|
+
sleep time += 1
|
511
|
+
retry unless (tries -= 1) <= 0
|
512
|
+
raise OpenStack::Exception::Connection, "Unable to connect to #{server}"
|
513
|
+
end
|
407
514
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
515
|
+
# Build Auth JSON
|
516
|
+
auth = { "auth" => { "identity" => {} } }
|
517
|
+
|
518
|
+
case connection.auth_method.to_sym
|
519
|
+
when :password
|
520
|
+
auth["auth"]["identity"]["methods"] = ["password"]
|
521
|
+
auth["auth"]["identity"]["password"] = { "user" => { "name" => connection.authuser, "password" => connection.authkey } }
|
522
|
+
auth["auth"]["identity"]["password"]["user"]["domain"] = connection.user_domain ? { "name" => connection.user_domain } : { "id" => connection.user_domain_id }
|
523
|
+
when :password_user_id
|
524
|
+
auth["auth"]["identity"]["methods"] = ["password"]
|
525
|
+
auth["auth"]["identity"]["password"] = { "user" => { "id" => connection.authuser, "password" => connection.authkey } }
|
526
|
+
when :token
|
527
|
+
auth["auth"]["identity"]["methods"] = ["token"]
|
528
|
+
auth["auth"]["identity"]["token"] = { "id" => connection.authkey }
|
529
|
+
else
|
530
|
+
raise Exception::InvalidArgument, "Unrecognized auth method #{connection.auth_method}."
|
531
|
+
end
|
414
532
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
533
|
+
# handle project authentication scope
|
534
|
+
if (connection.project_id || connection.project_name) && (connection.project_domain_name || connection.project_domain_id)
|
535
|
+
auth["auth"]["scope"] = { "project" => { "domain" => {} } }
|
536
|
+
auth["auth"]["scope"]["project"]["name"] = connection.project_name if connection.project_name
|
537
|
+
auth["auth"]["scope"]["project"]["id"] = connection.project_id if connection.project_id
|
538
|
+
auth["auth"]["scope"]["project"]["domain"]["name"] = connection.project_domain_name if connection.project_domain_name
|
539
|
+
auth["auth"]["scope"]["project"]["domain"]["id"] = connection.project_domain_id if connection.project_domain_id
|
540
|
+
end
|
420
541
|
|
421
|
-
|
422
|
-
|
542
|
+
# handle domain authentication scope
|
543
|
+
if connection.domain_name || connection.domain_id
|
544
|
+
auth["auth"]["scope"] = { "domain" => {} }
|
545
|
+
auth["auth"]["scope"]["domain"]["name"] = connection.domain_name if connection.domain_name
|
546
|
+
auth["auth"]["scope"]["domain"]["id"] = connection.domain_id if connection.domain_id
|
423
547
|
end
|
424
|
-
server.start
|
425
|
-
rescue
|
426
|
-
puts "Can't connect to the server: #{tries} tries to reconnect" if connection.is_debug
|
427
|
-
sleep time += 1
|
428
|
-
retry unless (tries -= 1) <= 0
|
429
|
-
raise OpenStack::Exception::Connection, "Unable to connect to #{server}"
|
430
|
-
end
|
431
548
|
|
432
|
-
|
549
|
+
response = server.post(connection.auth_path.chomp('/') + '/auth/tokens',
|
550
|
+
JSON.generate(auth),
|
551
|
+
{'Content-Type' => 'application/json'})
|
433
552
|
|
434
|
-
|
435
|
-
connection.
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
when "object-store"
|
440
|
-
uri = URI.parse(response["x-storage-url"])
|
553
|
+
# debugging
|
554
|
+
if connection.is_debug
|
555
|
+
puts "REQUEST: POST => #{connection.auth_path.chomp('/') + '/auth/tokens'}"
|
556
|
+
puts "RESPONSE: #{response.body}"
|
557
|
+
puts '----------------------------------------'
|
441
558
|
end
|
442
|
-
raise OpenStack::Exception::Authentication, "Unexpected Response from #{connection.auth_host} - couldn't get service URLs: \"x-server-management-url\" is: #{response["x-server-management-url"]} and \"x-storage-url\" is: #{response["x-storage-url"]}" if (uri.host.nil? || uri.host=="")
|
443
|
-
connection.service_host = uri.host
|
444
|
-
connection.service_path = uri.path
|
445
|
-
connection.service_port = uri.port
|
446
|
-
connection.service_scheme = uri.scheme
|
447
|
-
connection.authok = true
|
448
|
-
else
|
449
|
-
connection.authok = false
|
450
|
-
raise OpenStack::Exception::Authentication, "Authentication failed with response code #{response.code}"
|
451
|
-
end
|
452
|
-
server.finish
|
453
|
-
end
|
454
559
|
|
455
|
-
|
560
|
+
if response.code =~ /^20./
|
561
|
+
connection.authtoken = response['X-Subject-Token']
|
456
562
|
|
563
|
+
resp_data=JSON.parse(response.body)
|
457
564
|
|
458
|
-
|
459
|
-
|
460
|
-
|
565
|
+
catalog = resp_data["token"]["catalog"]
|
566
|
+
unless catalog
|
567
|
+
raise OpenStack::Exception::Authentication, "No service catalog returned. Maybe your auth request is unscoped. Please check if your selected user has a default project."
|
568
|
+
end
|
461
569
|
|
462
|
-
|
570
|
+
# Check if the used service is available
|
571
|
+
implemented_services = resp_data["token"]["catalog"].map {|service| service['type']}
|
572
|
+
raise OpenStack::Exception::NotImplemented.new("The requested service: \"#{connection.service_type}\" is not present " +
|
573
|
+
"in the returned service catalogue.", 501, "#{resp_data["access"]["serviceCatalog"]}") unless implemented_services.include?(connection.service_type)
|
574
|
+
catalog.each do |service|
|
575
|
+
service["endpoints"].each do |endpoint|
|
576
|
+
connection.regions_list[endpoint["region"]] ||= []
|
577
|
+
connection.regions_list[endpoint["region"]] << {:service=>service["type"], :versionId => endpoint["versionId"]}
|
578
|
+
end
|
463
579
|
|
464
|
-
|
580
|
+
# set use preset service name if set, otherwise ignore.
|
581
|
+
if connection.service_name
|
582
|
+
check_service_name = connection.service_name
|
583
|
+
else
|
584
|
+
check_service_name = service['name']
|
585
|
+
end
|
465
586
|
|
466
|
-
|
467
|
-
|
587
|
+
if service['type'] == connection.service_type and service['name'] == check_service_name
|
588
|
+
endpoints = service["endpoints"]
|
468
589
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
super(message)
|
473
|
-
end
|
590
|
+
# filter endpoints by interfacetype
|
591
|
+
interface_type = connection.endpoint_type.gsub('URL','')
|
592
|
+
endpoints = endpoints.select {|ep| ep['interface'] == interface_type}
|
474
593
|
|
475
|
-
|
594
|
+
# Select endpoint based on region
|
595
|
+
if connection.region
|
596
|
+
endpoints.each do |ep|
|
597
|
+
if ep["region"] and ep["region"].upcase == connection.region.upcase
|
598
|
+
@uri = URI.parse(ep['url'])
|
599
|
+
end
|
600
|
+
end
|
601
|
+
else
|
602
|
+
@uri = URI.parse(endpoints[0]['url'])
|
603
|
+
end
|
476
604
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
end
|
495
|
-
class ServerCapacityUnavailable < ComputeError # :nodoc:
|
496
|
-
end
|
497
|
-
class BackupOrResizeInProgress < ComputeError # :nodoc:
|
498
|
-
end
|
499
|
-
class ResizeNotAllowed < ComputeError # :nodoc:
|
500
|
-
end
|
501
|
-
class NotImplemented < ComputeError # :nodoc:
|
502
|
-
end
|
503
|
-
class Other < ComputeError # :nodoc:
|
504
|
-
end
|
505
|
-
class ResourceStateConflict < ComputeError # :nodoc:
|
506
|
-
end
|
507
|
-
class QuantumError < ComputeError # :nodoc:
|
605
|
+
if @uri == ""
|
606
|
+
raise OpenStack::Exception::Authentication, "No API endpoint for region #{connection.region}"
|
607
|
+
else
|
608
|
+
connection.service_host = @uri.host
|
609
|
+
connection.service_path = @uri.path
|
610
|
+
connection.service_port = @uri.port
|
611
|
+
connection.service_scheme = @uri.scheme
|
612
|
+
connection.authok = true
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
else
|
617
|
+
connection.authtoken = false
|
618
|
+
raise OpenStack::Exception::Authentication, "Authentication failed with response code #{response.code}"
|
619
|
+
end
|
620
|
+
server.finish if server.started?
|
621
|
+
end
|
508
622
|
end
|
509
623
|
|
510
|
-
# Plus some others that we define here
|
511
624
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
end
|
516
|
-
class InvalidArgument < StandardError # :nodoc:
|
517
|
-
end
|
518
|
-
class TooManyPersonalityItems < StandardError # :nodoc:
|
519
|
-
end
|
520
|
-
class PersonalityFilePathTooLong < StandardError # :nodoc:
|
521
|
-
end
|
522
|
-
class PersonalityFileTooLarge < StandardError # :nodoc:
|
523
|
-
end
|
524
|
-
class Authentication < StandardError # :nodoc:
|
525
|
-
end
|
526
|
-
class Connection < StandardError # :nodoc:
|
527
|
-
end
|
625
|
+
#============================
|
626
|
+
# OpenStack::Exception
|
627
|
+
#============================
|
528
628
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
exception_class = self.const_get("ItemNotFound")
|
539
|
-
raise exception_class.new("The resource could not be found", "404", "")
|
540
|
-
else
|
541
|
-
JSON.parse(response.body).each_pair do |key, val|
|
542
|
-
fault=key
|
543
|
-
info=val
|
544
|
-
end
|
545
|
-
exception_class = self.const_get(fault[0,1].capitalize+fault[1,fault.length])
|
546
|
-
raise exception_class.new((info["message"] || info), response.code, response.body)
|
629
|
+
class Exception
|
630
|
+
class ComputeError < StandardError
|
631
|
+
attr_reader :response_body
|
632
|
+
attr_reader :response_code
|
633
|
+
|
634
|
+
def initialize(message, code, response_body)
|
635
|
+
@response_code=code
|
636
|
+
@response_body=response_body
|
637
|
+
super(message)
|
547
638
|
end
|
548
|
-
|
639
|
+
end
|
640
|
+
|
641
|
+
class ComputeFault < ComputeError # :nodoc:
|
642
|
+
end
|
643
|
+
class ServiceUnavailable < ComputeError # :nodoc:
|
644
|
+
end
|
645
|
+
class Unauthorized < ComputeError # :nodoc:
|
646
|
+
end
|
647
|
+
class BadRequest < ComputeError # :nodoc:
|
648
|
+
end
|
649
|
+
class OverLimit < ComputeError # :nodoc:
|
650
|
+
end
|
651
|
+
class BadMediaType < ComputeError # :nodoc:
|
652
|
+
end
|
653
|
+
class BadMethod < ComputeError # :nodoc:
|
654
|
+
end
|
655
|
+
class ItemNotFound < ComputeError # :nodoc:
|
656
|
+
end
|
657
|
+
class BuildInProgress < ComputeError # :nodoc:
|
658
|
+
end
|
659
|
+
class ServerCapacityUnavailable < ComputeError # :nodoc:
|
660
|
+
end
|
661
|
+
class BackupOrResizeInProgress < ComputeError # :nodoc:
|
662
|
+
end
|
663
|
+
class ResizeNotAllowed < ComputeError # :nodoc:
|
664
|
+
end
|
665
|
+
class NotImplemented < ComputeError # :nodoc:
|
666
|
+
end
|
667
|
+
class Other < ComputeError # :nodoc:
|
668
|
+
end
|
669
|
+
class ResourceStateConflict < ComputeError # :nodoc:
|
670
|
+
end
|
671
|
+
class QuantumError < ComputeError # :nodoc:
|
672
|
+
end
|
673
|
+
|
674
|
+
# Plus some others that we define here
|
675
|
+
|
676
|
+
class ExpiredAuthToken < StandardError # :nodoc:
|
677
|
+
end
|
678
|
+
class MissingArgument < StandardError # :nodoc:
|
679
|
+
end
|
680
|
+
class InvalidArgument < StandardError # :nodoc:
|
681
|
+
end
|
682
|
+
class TooManyPersonalityItems < StandardError # :nodoc:
|
683
|
+
end
|
684
|
+
class PersonalityFilePathTooLong < StandardError # :nodoc:
|
685
|
+
end
|
686
|
+
class PersonalityFileTooLarge < StandardError # :nodoc:
|
687
|
+
end
|
688
|
+
class Authentication < StandardError # :nodoc:
|
689
|
+
end
|
690
|
+
class Connection < StandardError # :nodoc:
|
691
|
+
end
|
692
|
+
|
693
|
+
# In the event of a non-200 HTTP status code, this method takes the HTTP response, parses
|
694
|
+
# the JSON from the body to get more information about the exception, then raises the
|
695
|
+
# proper error. Note that all exceptions are scoped in the OpenStack::Compute::Exception namespace.
|
696
|
+
def self.raise_exception(response)
|
697
|
+
return if response.code =~ /^20.$/
|
698
|
+
begin
|
699
|
+
fault = nil
|
700
|
+
info = nil
|
701
|
+
if response.body.nil? && response.code == "404" #HEAD ops no body returned
|
702
|
+
exception_class = self.const_get("ItemNotFound")
|
703
|
+
raise exception_class.new("The resource could not be found", "404", "")
|
704
|
+
else
|
705
|
+
JSON.parse(response.body).each_pair do |key, val|
|
706
|
+
fault=key
|
707
|
+
info=val
|
708
|
+
end
|
709
|
+
exception_class = self.const_get(fault[0,1].capitalize+fault[1,fault.length])
|
710
|
+
raise exception_class.new((info["message"] || info), response.code, response.body)
|
711
|
+
end
|
712
|
+
rescue JSON::ParserError => parse_error
|
549
713
|
deal_with_faulty_error(response, parse_error)
|
550
|
-
|
551
|
-
|
714
|
+
rescue NameError
|
715
|
+
raise OpenStack::Exception::Other.new("The server returned status #{response.code}", response.code, response.body)
|
716
|
+
end
|
552
717
|
end
|
553
|
-
end
|
554
718
|
|
555
|
-
|
719
|
+
private
|
556
720
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
721
|
+
#e.g. os.delete("non-existant") ==> response.body is:
|
722
|
+
# "404 Not Found\n\nThe resource could not be found.\n\n "
|
723
|
+
# which doesn't parse. Deal with such cases here if possible (JSON::ParserError)
|
724
|
+
def self.deal_with_faulty_error(response, parse_error)
|
725
|
+
case response.code
|
726
|
+
when "404"
|
727
|
+
klass = self.const_get("ItemNotFound")
|
728
|
+
msg = "The resource could not be found"
|
729
|
+
when "409"
|
730
|
+
klass = self.const_get("ResourceStateConflict")
|
731
|
+
msg = "There was a conflict with the state of the resource"
|
732
|
+
else
|
733
|
+
klass = self.const_get("Other")
|
734
|
+
msg = "Oops - not sure what happened: #{parse_error}"
|
735
|
+
end
|
736
|
+
raise klass.new(msg, response.code.to_s, response.body)
|
737
|
+
end
|
573
738
|
end
|
574
739
|
end
|
575
740
|
|
576
|
-
end
|
577
|
-
|