openstack 2.2.0 → 3.0.0
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/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
|
-
[](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
|
-
|