right_api_client 1.5.14 → 1.5.15

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.
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