right_api_client 1.5.14 → 1.5.15

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # CHANGELOG.md
2
2
 
3
+ ## 1.5.15
4
+ - \#50 Ivory 14 02 acu148161 Harden client against spotty networks
5
+
3
6
  ## 1.5.14
4
7
  - \#51 Add type aliases for some Exception subclasses whose name changed; restores interface compatibility with 1.5.9
8
+
5
9
  ## 1.5.13
6
10
  - \#44 Charcoal 13 18 account id in header acu103549
7
11
  - \#42 Salmon 13 17 acu135785 add current url
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- right_api_client (1.5.14)
4
+ right_api_client (1.5.15)
5
5
  json
6
6
  rest-client (~> 1.6)
7
7
 
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require File.expand_path('../lib/right_api_client', __FILE__)
2
+ require 'rubygems'
2
3
  require 'rake'
3
- require 'spec/rake/spectask'
4
+ require 'rspec/core/rake_task'
4
5
 
5
6
  task :build do
6
7
  system "gem build right_api_client.gemspec"
@@ -10,8 +11,7 @@ task :release => :build do
10
11
  system "gem push right_api_client-#{RightApi::Client::VERSION}.gem"
11
12
  end
12
13
 
13
- Spec::Rake::SpecTask.new('spec') do |t|
14
- t.spec_files = Dir.glob('spec/*_spec.rb')
15
- t.spec_opts << '--format nested'
16
- t.spec_opts << '--colour'
17
- end
14
+ RSpec::Core::RakeTask.new('spec') do |t|
15
+ t.pattern= 'spec/**/*_spec.rb'
16
+ t.rspec_opts = ['--format nested','--colour']
17
+ end
@@ -16,6 +16,10 @@ module RightApi
16
16
  class Client
17
17
  include Helper
18
18
 
19
+ DEFAULT_OPEN_TIMEOUT = nil
20
+ DEFAULT_TIMEOUT = -1
21
+ DEFAULT_MAX_ATTEMPTS = 5
22
+
19
23
  ROOT_RESOURCE = '/api/session'
20
24
  ROOT_INSTANCE_RESOURCE = '/api/session/instance'
21
25
  DEFAULT_API_URL = 'https://my.rightscale.com'
@@ -23,10 +27,10 @@ module RightApi
23
27
  # permitted parameters for initializing
24
28
  AUTH_PARAMS = %w[
25
29
  email password_base64 password account_id api_url api_version
26
- cookies instance_token
30
+ cookies instance_token timeout open_timeout max_attempts enable_retry
27
31
  ]
28
32
 
29
- attr_reader :cookies, :instance_token, :last_request
33
+ attr_reader :cookies, :instance_token, :last_request, :timeout, :open_timeout, :max_attempts, :enable_retry
30
34
  attr_accessor :account_id, :api_url
31
35
 
32
36
  def initialize(args)
@@ -34,6 +38,8 @@ module RightApi
34
38
  raise 'This API client is only compatible with Ruby 1.8.7 and upwards.' if (RUBY_VERSION < '1.8.7')
35
39
 
36
40
  @api_url, @api_version = DEFAULT_API_URL, API_VERSION
41
+ @open_timeout, @timeout, @max_attempts = DEFAULT_OPEN_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_MAX_ATTEMPTS
42
+ @enable_retry = false
37
43
 
38
44
  # Initializing all instance variables from hash
39
45
  args.each { |key,value|
@@ -42,7 +48,7 @@ module RightApi
42
48
 
43
49
  raise 'This API client is only compatible with the RightScale API 1.5 and upwards.' if (Float(@api_version) < 1.5)
44
50
 
45
- @rest_client = RestClient::Resource.new(@api_url, :timeout => -1)
51
+ @rest_client = RestClient::Resource.new(@api_url, :open_timeout => @open_timeout, :timeout => @timeout)
46
52
  @last_request = {}
47
53
 
48
54
  # There are three options for login: credentials, instance token,
@@ -114,6 +120,44 @@ module RightApi
114
120
  end
115
121
 
116
122
  protected
123
+ # Users shouldn't need to call the following methods directly
124
+
125
+ def retry_request(is_read_only = false)
126
+ attempts = 0
127
+ begin
128
+ yield
129
+ rescue OpenSSL::SSL::SSLError => e
130
+ raise e unless @enable_retry
131
+ # These errors pertain to the SSL handshake. Since no data has been
132
+ # exchanged its always safe to retry
133
+ raise e if attempts >= @max_attempts
134
+ attempts += 1
135
+ retry
136
+ rescue Errno::ECONNRESET, RestClient::ServerBrokeConnection, RestClient::RequestTimeout => e
137
+ raise e unless @enable_retry
138
+ # Packetloss related.
139
+ # There are two timeouts on the ssl negotiation and data read with different
140
+ # times. Unfortunately the standard timeout class is used for both and the
141
+ # exceptions are caught and reraised so you can't distinguish between them.
142
+ # Unfortunate since ssl negotiation timeouts should always be retryable
143
+ # whereas data may not.
144
+ if is_read_only
145
+ raise e if attempts >= @max_attempts
146
+ attempts += 1
147
+ retry
148
+ else
149
+ raise e
150
+ end
151
+ rescue ApiError => e
152
+ if re_login?(e)
153
+ # Session cookie is expired or invalid
154
+ login()
155
+ retry
156
+ else
157
+ raise e
158
+ end
159
+ end
160
+ end
117
161
 
118
162
  def login
119
163
  params, path = if @instance_token
@@ -128,13 +172,22 @@ module RightApi
128
172
  end
129
173
  params['account_href'] = "/api/accounts/#{@account_id}"
130
174
 
131
- response = @rest_client[path].post(params, 'X_API_VERSION' => @api_version) do |response, request, result, &block|
132
- if response.code == 302
133
- update_api_url(response)
134
- response.follow_redirection(request, result, &block)
135
- else
136
- response.return!(request, result)
175
+ response = nil
176
+ attempts = 0
177
+ begin
178
+ response = @rest_client[path].post(params, 'X_API_VERSION' => @api_version) do |response, request, result, &block|
179
+ if response.code == 302
180
+ update_api_url(response)
181
+ response.follow_redirection(request, result, &block)
182
+ else
183
+ response.return!(request, result)
184
+ end
137
185
  end
186
+ rescue Errno::ECONNRESET, RestClient::RequestTimeout, OpenSSL::SSL::SSLError, RestClient::ServerBrokeConnection
187
+ raise unless @enable_retry
188
+ raise if attempts >= @max_attempts
189
+ attempts += 1
190
+ retry
138
191
  end
139
192
 
140
193
  update_cookies(response)
@@ -157,43 +210,37 @@ module RightApi
157
210
  # Resource id is a special param as it needs to be added to the path
158
211
  path = add_id_and_params_to_path(path, params)
159
212
 
160
- req, res = nil
213
+ req, res, resource_type, body = nil
161
214
 
162
215
  begin
163
- # Return content type so the resulting resource object knows what kind of resource it is.
164
- resource_type, body = @rest_client[path].get(headers) do |response, request, result, &block|
165
- req, res = request, response
166
- update_cookies(response)
167
- update_last_request(request, response)
168
-
169
- case response.code
170
- when 200
171
- # Get the resource_type from the content_type, the resource_type
172
- # will be used later to add relevant methods to relevant resources
173
- type = if result.content_type.index('rightscale')
174
- get_resource_type(result.content_type)
216
+ retry_request(true) do
217
+ # Return content type so the resulting resource object knows what kind of resource it is.
218
+ resource_type, body = @rest_client[path].get(headers) do |response, request, result, &block|
219
+ req, res = request, response
220
+ update_cookies(response)
221
+ update_last_request(request, response)
222
+
223
+ case response.code
224
+ when 200
225
+ # Get the resource_type from the content_type, the resource_type
226
+ # will be used later to add relevant methods to relevant resources
227
+ type = if result.content_type.index('rightscale')
228
+ get_resource_type(result.content_type)
229
+ else
230
+ ''
231
+ end
232
+
233
+ [type, response.body]
234
+ when 301, 302
235
+ update_api_url(response)
236
+ response.follow_redirection(request, result, &block)
237
+ when 404
238
+ raise UnknownRouteError.new(request, response)
175
239
  else
176
- ''
240
+ raise ApiError.new(request, response)
177
241
  end
178
-
179
- [type, response.body]
180
- when 301, 302
181
- update_api_url(response)
182
- response.follow_redirection(request, result, &block)
183
- when 404
184
- raise UnknownRouteError.new(request, response)
185
- else
186
- raise ApiError.new(request, response)
187
242
  end
188
243
  end
189
- rescue ApiError => e
190
- if re_login?(e)
191
- # session cookie is expired or invalid
192
- login()
193
- retry
194
- else
195
- raise wrap(e, :get, path, params, req, res)
196
- end
197
244
  rescue => e
198
245
  raise wrap(e, :get, path, params, req, res)
199
246
  end
@@ -211,58 +258,52 @@ module RightApi
211
258
  def do_post(path, params={})
212
259
  params = fix_array_of_hashes(params)
213
260
 
214
- req, res = nil
261
+ req, res, resource_type, body = nil
215
262
 
216
263
  begin
217
- @rest_client[path].post(params, headers) do |response, request, result|
218
- req, res = request, response
219
- update_cookies(response)
220
- update_last_request(request, response)
221
-
222
- case response.code
223
- when 201, 202
224
- # Create and return the resource
225
- href = response.headers[:location]
226
- relative_href = href.split(@api_url)[-1]
227
- # Return the resource that was just created
228
- # Determine the resource_type from the href (eg. api/clouds/id).
229
- # This is based on the assumption that we can determine the resource_type without doing a do_get
230
- resource_type = get_singular(relative_href.split('/')[-2])
231
- RightApi::Resource.process(self, resource_type, relative_href)
232
- when 204
233
- nil
234
- when 200..299
235
- # This is needed for the tags Resource -- which returns a 200 and has a content type
236
- # therefore, ResourceDetail objects needs to be returned
237
- if response.code == 200 && response.headers[:content_type].index('rightscale')
238
- resource_type = get_resource_type(response.headers[:content_type])
239
- data = JSON.parse(response)
240
- # Resource_tag is returned after querying tags.by_resource or tags.by_tags.
241
- # You cannot do a show on a resource_tag, but that is basically what we want to do
242
- data.map { |obj|
243
- RightApi::ResourceDetail.new(self, resource_type, path, obj)
244
- }
264
+ retry_request do
265
+ @rest_client[path].post(params, headers) do |response, request, result|
266
+ req, res = request, response
267
+ update_cookies(response)
268
+ update_last_request(request, response)
269
+
270
+ case response.code
271
+ when 201, 202
272
+ # Create and return the resource
273
+ href = response.headers[:location]
274
+ relative_href = href.split(@api_url)[-1]
275
+ # Return the resource that was just created
276
+ # Determine the resource_type from the href (eg. api/clouds/id).
277
+ # This is based on the assumption that we can determine the resource_type without doing a do_get
278
+ resource_type = get_singular(relative_href.split('/')[-2])
279
+ RightApi::Resource.process(self, resource_type, relative_href)
280
+ when 204
281
+ nil
282
+ when 200..299
283
+ # This is needed for the tags Resource -- which returns a 200 and has a content type
284
+ # therefore, ResourceDetail objects needs to be returned
285
+ if response.code == 200 && response.headers[:content_type].index('rightscale')
286
+ resource_type = get_resource_type(response.headers[:content_type])
287
+ data = JSON.parse(response)
288
+ # Resource_tag is returned after querying tags.by_resource or tags.by_tags.
289
+ # You cannot do a show on a resource_tag, but that is basically what we want to do
290
+ data.map { |obj|
291
+ RightApi::ResourceDetail.new(self, resource_type, path, obj)
292
+ }
293
+ else
294
+ response.return!(request, result)
295
+ end
296
+ when 301, 302
297
+ update_api_url(response)
298
+ do_post(path, params)
299
+ when 404
300
+ raise UnknownRouteError.new(request, response)
245
301
  else
246
- response.return!(request, result)
302
+ raise ApiError.new(request, response)
247
303
  end
248
- when 301, 302
249
- update_api_url(response)
250
- do_post(path, params)
251
- when 404
252
- raise UnknownRouteError.new(request, response)
253
- else
254
- raise ApiError.new(request, response)
255
304
  end
256
305
  end
257
-
258
306
  rescue ApiError => e
259
- if re_login?(e)
260
- login()
261
- retry
262
- else
263
- raise wrap(e, :post, path, params, req, res)
264
- end
265
- rescue => e
266
307
  raise wrap(e, :post, path, params, req, res)
267
308
  end
268
309
  end
@@ -272,34 +313,29 @@ module RightApi
272
313
  # Resource id is a special param as it needs to be added to the path
273
314
  path = add_id_and_params_to_path(path, params)
274
315
 
275
- req, res = nil
316
+ req, res, resource_type, body = nil
276
317
 
277
318
  begin
278
- @rest_client[path].delete(headers) do |response, request, result|
279
- req, res = request, response
280
- update_cookies(response)
281
- update_last_request(request, response)
282
-
283
- case response.code
284
- when 200
285
- when 204
286
- nil
287
- when 301, 302
288
- update_api_url(response)
289
- do_delete(path, params)
290
- when 404
291
- raise UnknownRouteError.new(request, response)
292
- else
293
- raise ApiError.new(request, response)
319
+ retry_request do
320
+ @rest_client[path].delete(headers) do |response, request, result|
321
+ req, res = request, response
322
+ update_cookies(response)
323
+ update_last_request(request, response)
324
+
325
+ case response.code
326
+ when 200
327
+ when 204
328
+ nil
329
+ when 301, 302
330
+ update_api_url(response)
331
+ do_delete(path, params)
332
+ when 404
333
+ raise UnknownRouteError.new(request, response)
334
+ else
335
+ raise ApiError.new(request, response)
336
+ end
294
337
  end
295
338
  end
296
- rescue ApiError => e
297
- if re_login?(e)
298
- login()
299
- retry
300
- else
301
- raise wrap(e, :delete, path, params, req, res)
302
- end
303
339
  rescue => e
304
340
  raise wrap(e, :delete, path, params, req, res)
305
341
  end
@@ -309,33 +345,28 @@ module RightApi
309
345
  def do_put(path, params={})
310
346
  params = fix_array_of_hashes(params)
311
347
 
312
- req, res = nil
348
+ req, res, resource_type, body = nil
313
349
 
314
350
  begin
315
- @rest_client[path].put(params, headers) do |response, request, result|
316
- req, res = request, response
317
- update_cookies(response)
318
- update_last_request(request, response)
319
-
320
- case response.code
321
- when 204
322
- nil
323
- when 301, 302
324
- update_api_url(response)
325
- do_put(path, params)
326
- when 404
327
- raise UnknownRouteError.new(request, response)
328
- else
329
- raise ApiError.new(request, response)
351
+ retry_request do
352
+ @rest_client[path].put(params, headers) do |response, request, result|
353
+ req, res = request, response
354
+ update_cookies(response)
355
+ update_last_request(request, response)
356
+
357
+ case response.code
358
+ when 204
359
+ nil
360
+ when 301, 302
361
+ update_api_url(response)
362
+ do_put(path, params)
363
+ when 404
364
+ raise UnknownRouteError.new(request, response)
365
+ else
366
+ raise ApiError.new(request, response)
367
+ end
330
368
  end
331
369
  end
332
- rescue ApiError => e
333
- if re_login?(e)
334
- login()
335
- retry
336
- else
337
- raise wrap(e, :put, path, params, req, res)
338
- end
339
370
  rescue => e
340
371
  raise wrap(e, :put, path, params, req, res)
341
372
  end
@@ -2,7 +2,7 @@
2
2
  module RightApi
3
3
  class Client
4
4
  API_VERSION = '1.5'
5
- CLIENT_VERSION = '14'
5
+ CLIENT_VERSION = '15'
6
6
  VERSION = "#{API_VERSION}.#{CLIENT_VERSION}"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_api_client
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 5
9
- - 14
10
- version: 1.5.14
9
+ - 15
10
+ version: 1.5.15
11
11
  platform: ruby
12
12
  authors:
13
13
  - RightScale, Inc.
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2014-01-24 00:00:00 Z
18
+ date: 2014-01-30 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  requirement: &id001 !ruby/object:Gem::Requirement