right_rackspace 0.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,528 @@
1
+ #
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ #
24
+ module Rightscale
25
+ module Rackspace
26
+
27
+ class BenchmarkingBlock #:nodoc:
28
+ attr_accessor :parser, :service
29
+ def initialize
30
+ # Benchmark::Tms instance for service access benchmarking.
31
+ @service = Benchmark::Tms.new()
32
+ # Benchmark::Tms instance for parsing benchmarking.
33
+ @parser = Benchmark::Tms.new()
34
+ end
35
+ end
36
+
37
+ class Interface
38
+ DEFAULT_AUTH_ENDPOINT = "https://auth.api.rackspacecloud.com/v1.0"
39
+ DEFAULT_LIMIT = 1000
40
+
41
+ @@rackspace_problems = []
42
+ # A Regexps of errors say the Rackspace has issues and
43
+ # a request is likely to be repeated
44
+ def self.rackspace_problems
45
+ @@rackspace_problems
46
+ end
47
+
48
+ @@bench = Rightscale::Rackspace::BenchmarkingBlock.new
49
+ def self.bench
50
+ @@bench
51
+ end
52
+
53
+ @@params = {}
54
+ def self.params
55
+ @@params
56
+ end
57
+
58
+ def params
59
+ @params
60
+ end
61
+
62
+ def merged_params #:nodoc:
63
+ @@params.merge(@params)
64
+ end
65
+
66
+ @@caching = false
67
+
68
+ attr_accessor :username
69
+ attr_accessor :auth_key
70
+ attr_reader :logged_in
71
+ attr_reader :auth_headers
72
+ attr_reader :auth_token
73
+ attr_accessor :auth_endpoint
74
+ attr_accessor :service_endpoint
75
+ attr_accessor :last_request
76
+ attr_accessor :last_response
77
+ attr_accessor :last_error
78
+ attr_reader :logger
79
+ attr_reader :cache
80
+
81
+ # Parses an endpoint and returns a hash of data
82
+ def endpoint_to_host_data(endpoint)# :nodoc:
83
+ service = URI.parse(endpoint).path
84
+ service.chop! if service[/\/$/] # remove a trailing '/'
85
+ { :server => URI.parse(endpoint).host,
86
+ :service => service,
87
+ :protocol => URI.parse(endpoint).scheme,
88
+ :port => URI.parse(endpoint).port }
89
+ end
90
+
91
+ # Create new Rackspace interface handle.
92
+ #
93
+ # Params:
94
+ # :logger - a logger object
95
+ # :caching - enabled/disables RightRackspace caching on level (only for list_xxx calls)
96
+ # :verbose_errors - verbose error messages
97
+ # :auth_endpoint - an auth endpoint URL ()
98
+ # :service_endpoint - a service endpoint URL
99
+ # callbacks:
100
+ # :on_request(self, request_hash) - every time before the request
101
+ # :on_response(self) - every time the response comes
102
+ # :on_error(self, error_message) - every time the response is not 2xx | 304
103
+ # :on_success(self) - once after the successfull response
104
+ # :on_login(self) - before login
105
+ # :on_login_success(self) - when login is successfull
106
+ # :on_login_failure(self) - when login fails
107
+ # :on_failure(self) - once after the unsuccessfull response
108
+ #
109
+ # Handle creation:
110
+ #
111
+ # # Just pass your username and youe key
112
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d')
113
+ #
114
+ # # The username and the key are in ENV vars: RACKSPACE_USERNAME & RACKSPACE_AUTH_KEY
115
+ # rackspace = Rightscale::Rackspace::Interface::new
116
+ #
117
+ # # Specify the auth endpoint and the service endpoint explicitly. Make the error
118
+ # # messages as verbose as possible.
119
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d',
120
+ # :auth_endpoint => 'https://api.mosso.com/auth',
121
+ # :service_point => 'https://servers.api.rackspacecloud.com/v1.0/413609',
122
+ # :verbose_errors => true )
123
+ #
124
+ # # Fix params after the handle creation:
125
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d')
126
+ # rackspace.params[:verbose_errors] = true
127
+ #
128
+ # Calbacks handling:
129
+ #
130
+ # # On response calback
131
+ # on_response = Proc.new do |handle|
132
+ # puts ">> response headers: #{handle.last_response.to_hash.inspect}"
133
+ # end
134
+ #
135
+ # # On error calback
136
+ # on_error = Proc.new do |handle, error_message|
137
+ # puts ">> Error: #{error_message}"
138
+ # end
139
+ #
140
+ # # Create a handle
141
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d',
142
+ # :on_response => on_response,
143
+ # :on_error => on_error)
144
+ #
145
+ def initialize(username=nil, auth_key=nil, params={})
146
+ @params = params
147
+ # Auth data
148
+ @username = username || ENV['RACKSPACE_USERNAME']
149
+ @auth_key = auth_key || ENV['RACKSPACE_AUTH_KEY']
150
+ @logged_in = false
151
+ # Auth host
152
+ @auth_headers = {} # a set of headers is returned on authentification coplete
153
+ @auth_endpoint = ENV['RACKSPACE_AUTH_ENDPOINT'] || params[:auth_endpoint] || DEFAULT_AUTH_ENDPOINT
154
+ @auth_endpoint_data = endpoint_to_host_data(@auth_endpoint)
155
+ # Logger
156
+ @logger = @params[:logger] || (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER) || Logger.new(STDOUT)
157
+ # Request and response
158
+ @last_request = nil
159
+ @last_response = nil
160
+ # cache
161
+ @cache = {}
162
+ end
163
+
164
+ # Generate a request.
165
+ #
166
+ # opts:
167
+ # :body - String
168
+ # :endpoint_data - Hash
169
+ # :no_service_path - bool
170
+ # :headers - a hash or an array of HTTP headers
171
+ # :vars - a hash or an array of URL variables
172
+ #
173
+ def generate_request(verb, path='', opts={}) #:nodoc:
174
+ # Form a valid http verb: 'Get', 'Post', 'Put', 'Delete'
175
+ verb = verb.to_s.capitalize
176
+ raise "Unsupported HTTP verb #{verb.inspect}!" unless verb[/^(Get|Post|Put|Delete)$/]
177
+ # Select an endpoint
178
+ endpoint_data = (opts[:endpoint_data] || @service_endpoint_data).dup
179
+ # Fix a path
180
+ path = "/#{path}" if !path.blank? && !path[/^\//]
181
+ # Request variables
182
+ request_params = opts[:vars].to_a.map do |key, value|
183
+ key = key.to_s.downcase
184
+ # Make sure we do not pass a Time object instead of integer for 'changes-since'
185
+ value = value.to_i if key == 'changes-since'
186
+ "#{URI.escape(key)}=#{URI.escape(value.to_s)}"
187
+ end.join('&')
188
+ # Build a request final path
189
+ service = opts[:no_service_path] ? '' : endpoint_data[:service]
190
+ request_path = "#{service}#{path}"
191
+ request_path = '/' if request_path.blank?
192
+ request_path += "?#{request_params}" unless request_params.blank?
193
+ # Create a request
194
+ request = eval("Net::HTTP::#{verb}").new(request_path)
195
+ request.body = opts[:body] if opts[:body]
196
+ # Set headers
197
+ opts[:headers].to_a.each do |key, value|
198
+ key = key.to_s.downcase
199
+ # make sure 'if-modified-since' is always httpdated
200
+ if key == 'if-modified-since'
201
+ value = Time.at(value) if value.is_a?(Fixnum)
202
+ value = value.utc.httpdate if value.is_a?(Time)
203
+ end
204
+ request[key] = value.to_s
205
+ end
206
+ request['content-type'] ||= 'application/json'
207
+ request['accept'] = 'application/json'
208
+ # prepare output hash
209
+ endpoint_data.merge(:request => request)
210
+ end
211
+
212
+ # Just requests a remote end
213
+ def internal_request_info(request_hash) #:nodoc:
214
+ on_event(:on_request, request_hash)
215
+ @connection ||= Rightscale::HttpConnection.new(:exception => Error, :logger => @logger)
216
+ @last_request = request_hash[:request]
217
+ @@bench.service.add!{ @last_response = @connection.request(request_hash) }
218
+ on_event(:on_response)
219
+ end
220
+
221
+ # Request a remote end and process any errors is found
222
+ def request_info(request_hash) #:nodoc:
223
+ internal_request_info(request_hash)
224
+ result = nil
225
+ # check response for success...
226
+ case @last_response.code
227
+ when /^2..|304/ # SUCCESS
228
+ @error_handler = nil
229
+ on_event(:on_success)
230
+
231
+ # Cache hit: Cached
232
+ case @last_response.code
233
+ when '304' # 'changes-since' param
234
+ raise NoChange.new("NotModified: '#{simple_path(@last_request.path)}' has not changed since the requested time.")
235
+ when '203' # 'if-modified-since' header
236
+ # TODO: Mhhh... It seems Rackspace updates 'last-modified' header every 60 seconds or something even if nothing has changed
237
+ if @last_response.body.blank?
238
+ cached_path = cached_path(@last_request.path)
239
+ last_modified = @last_response['last-modified'].first
240
+ message_header = merged_params[:caching] &&
241
+ @cache[cached_path] &&
242
+ @cache[cached_path][:last_modified_at] == last_modified ? 'Cached' : 'NotModified'
243
+ # 203 + an empty response body means we asked whether the value did not change and it hit
244
+ raise NoChange.new("#{message_header}: '#{cached_path}' has not changed since #{last_modified}.")
245
+ end
246
+ end
247
+
248
+ # Parse a response body. If the body is empty the return +true+
249
+ @@bench.parser.add! do
250
+ result = if @last_response.body.blank? then true
251
+ else
252
+ case @last_response['content-type'].first
253
+ when 'application/json' then JSON::parse(@last_response.body)
254
+ else @last_response.body
255
+ end
256
+ end
257
+ end
258
+ else # ERROR
259
+ @last_error = HttpErrorHandler::extract_error_description(@last_response, merged_params[:verbose_errors])
260
+ on_event(:on_error, @last_error)
261
+ @error_handler ||= HttpErrorHandler.new(self, :errors_list => self.class.rackspace_problems)
262
+ result = @error_handler.check(request_hash)
263
+ @error_handler = nil
264
+ if result.nil?
265
+ on_event(:on_failure)
266
+ raise Error.new(@last_error)
267
+ end
268
+ end
269
+ result
270
+ rescue
271
+ @error_handler = nil
272
+ raise
273
+ end
274
+
275
+ # simple_path('/v1.0/123456/images/detail?var1=var2') #=> '/images/detail?var1=var2'
276
+ def simple_path(path) # :nodoc:
277
+ (path[/^#{@service_endpoint_data[:service]}(.*)/] && $1) || path
278
+ end
279
+
280
+ # simple_path('/v1.0/123456/images/detail?var1=var2') #=> '/images/detail'
281
+ def cached_path(path) # :nodoc:
282
+ simple_path(path)[/([^?]*)/] && $1
283
+ end
284
+
285
+ # detailed_path('/images', true) #=> '/images/detail'
286
+ def detailed_path(path, options) # :nodoc:
287
+ "#{path}#{options[:detail] ? '/detail' : ''}"
288
+ end
289
+
290
+ # Authenticate a user.
291
+ # Params: +soft+ is used for auto-authentication when auth_token expires. Soft auth
292
+ # do not overrides @last_request and @last_response attributes (are needed for a proper
293
+ # error handling) on success.
294
+ def authenticate(opts={}) # :nodoc:
295
+ @logged_in = false
296
+ @auth_headers = {}
297
+ opts = opts.dup
298
+ opts[:endpoint_data] = @auth_endpoint_data
299
+ (opts[:headers] ||= {}).merge!({ 'x-auth-user' => @username, 'x-auth-key' => @auth_key })
300
+ request_data = generate_request( :get, '', opts )
301
+ on_event(:on_login)
302
+ internal_request_info(request_data)
303
+ unless @last_response.is_a?(Net::HTTPSuccess)
304
+ @error_handler = nil
305
+ @last_error = HttpErrorHandler::extract_error_description(@last_response, merged_params[:verbose_errors])
306
+ on_event(:on_error, @last_error)
307
+ on_event(:on_login_failure)
308
+ on_event(:on_failure)
309
+ raise Error.new(@last_error)
310
+ end
311
+ # Store all auth response headers
312
+ @auth_headers = @last_response.to_hash
313
+ @auth_token = @auth_headers['x-auth-token'].first
314
+ # Service endpoint
315
+ @service_endpoint = merged_params[:service_endpoint] || @auth_headers['x-server-management-url'].first
316
+ @service_endpoint_data = endpoint_to_host_data(@service_endpoint)
317
+ @logged_in = true
318
+ on_event(:on_login_success)
319
+ true
320
+ end
321
+
322
+ # Incrementally lists something.
323
+ def incrementally_list_resources(verb, path, offset=nil, limit=nil, opts={}, &block) # :nodoc:
324
+ opts = opts.dup
325
+ opts[:vars] ||= {}
326
+ opts[:vars]['offset'] = offset || 0
327
+ opts[:vars]['limit'] = limit || DEFAULT_LIMIT
328
+ # Get a resource name by path:
329
+ # '/images' -> 'images'
330
+ # '/shared_ip_groups/detail' -> 'sharedIpGroups'
331
+ resource_name = ''
332
+ (path[%r{^/([^/]*)}] && $1).split('_').each_with_index do |w, i|
333
+ resource_name += (i==0 ? w.downcase : w.capitalize)
334
+ end
335
+ result = { resource_name => []}
336
+ loop do
337
+ # begin
338
+ response = api(verb, path, opts)
339
+ result[resource_name] += response[resource_name]
340
+ # rescue Rightscale::Rackspace::Error => e
341
+ # raise e unless e.message[/itemNotFound/]
342
+ # response = nil
343
+ # end
344
+ break if response.blank? ||
345
+ (response[resource_name].blank?) ||
346
+ (block && !block.call(response)) ||
347
+ (response[resource_name].size < opts[:vars]['limit'])
348
+ opts[:vars]['offset'] += opts[:vars]['limit']
349
+ end
350
+ result
351
+ end
352
+
353
+ # Call Rackspace. Caching is not used.
354
+ def api(verb, path='', options={}) # :nodoc:
355
+ login unless @logged_in
356
+ options[:headers] ||= {}
357
+ options[:headers]['x-auth-token'] = @auth_token
358
+ request_info(generate_request(verb, path, options))
359
+ end
360
+
361
+ # Call Rackspace. Use cache if possible
362
+ # opts:
363
+ # :incrementally - use incrementally list to get the whole list of items
364
+ # otherwise it will get max DEFAULT_LIMIT items (PS API call must support pagination)
365
+ #
366
+ def api_or_cache(verb, path, options={}) # :nodoc:
367
+ use_caching = merged_params[:caching] && options[:vars].blank?
368
+ cache_record = use_caching && @cache[path]
369
+ # Create a proc object to avoid a code duplication
370
+ proc = Proc.new do
371
+ if options[:incrementally]
372
+ incrementally_list_resources(verb, path, nil, nil, options)
373
+ else api(verb, path, options)
374
+ end
375
+ end
376
+ # The cache is not used or record is not found
377
+ unless cache_record
378
+ response = proc.call
379
+ if use_caching
380
+ last_modified_at = @last_response.to_hash['last-modified'].first
381
+ update_cache(path, last_modified_at, response)
382
+ end
383
+ response
384
+ else
385
+ # Record found - ask Rackspace whether it changed or not since last update
386
+ options = options.dup
387
+ options[:headers] ||= {}
388
+ options[:headers]['if-modified-since'] = cache_record[:last_modified_at]
389
+ proc.call
390
+ end
391
+ end
392
+
393
+ def update_cache(path, last_modified_at, data) #:nodoc:
394
+ @cache[path] ||= {}
395
+ @cache[path][:last_modified_at] = last_modified_at
396
+ @cache[path][:data] = data
397
+ end
398
+
399
+ # Events (callbacks) for logging and debugging features.
400
+ # These callbacks do not catch low level connection errors that are handled by RightHttpConnection but
401
+ # only HTTP errors.
402
+ def on_event(event, *params) #:nodoc:
403
+ self.merged_params[event].call(self, *params) if self.merged_params[event].kind_of?(Proc)
404
+ end
405
+
406
+ end
407
+
408
+ #------------------------------------------------------------
409
+ # Error handling
410
+ #------------------------------------------------------------
411
+
412
+ class NoChange < RuntimeError
413
+ end
414
+
415
+ class Error < RuntimeError
416
+ end
417
+
418
+ class HttpErrorHandler # :nodoc:
419
+
420
+ # Receiving these codes we have to reauthenticate at Rackspace
421
+ REAUTHENTICATE_ON = ['401']
422
+ # Some error are too ennoing to be logged: '404' comes very often when one calls
423
+ # incrementally_list_something
424
+ # SKIP_LOGGING_ON = ['404']
425
+ SKIP_LOGGING_ON = []
426
+
427
+ @@reiteration_start_delay = 0.2
428
+ def self.reiteration_start_delay
429
+ @@reiteration_start_delay
430
+ end
431
+ def self.reiteration_start_delay=(reiteration_start_delay)
432
+ @@reiteration_start_delay = reiteration_start_delay
433
+ end
434
+ @@reiteration_time = 5
435
+ def self.reiteration_time
436
+ @@reiteration_time
437
+ end
438
+ def self.reiteration_time=(reiteration_time)
439
+ @@reiteration_time = reiteration_time
440
+ end
441
+
442
+ # Format a response error message.
443
+ def self.extract_error_description(response, verbose=false) #:nodoc:
444
+ message = nil
445
+ Interface::bench.parser.add! do
446
+ message = begin
447
+ if response.body[/^<!DOCTYPE HTML PUBLIC/] then response.message
448
+ else
449
+ JSON::parse(response.body).to_a.map do |k,v|
450
+ "#{k}: #{v['message']}" + (verbose ? "\n#{v['details']}" : "")
451
+ end.join("\n")
452
+ end
453
+ rescue
454
+ response.message
455
+ end
456
+ end
457
+ "#{response.code}: #{message}"
458
+ end
459
+
460
+ # params:
461
+ # :reiteration_time
462
+ # :errors_list
463
+ def initialize(handle, params={}) #:nodoc:
464
+ @handle = handle # Link to RightEc2 | RightSqs | RightS3 instance
465
+ @started_at = Time.now
466
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
467
+ @errors_list = params[:errors_list] || []
468
+ @reiteration_delay = @@reiteration_start_delay
469
+ @retries = 0
470
+ end
471
+
472
+ # Process errored response
473
+ def check(request_hash) #:nodoc:
474
+ result = nil
475
+ error_found = false
476
+ response = @handle.last_response
477
+ error_message = @handle.last_error
478
+ # Log the error
479
+ logger = @handle.logger
480
+ unless SKIP_LOGGING_ON.include?(response.code)
481
+ logger.warn("##### #{@handle.class.name} returned an error: #{error_message} #####")
482
+ logger.warn("##### #{@handle.class.name} request: #{request_hash[:server]}:#{request_hash[:port]}#{request_hash[:request].path} ####")
483
+ end
484
+ # now - check the error
485
+ @errors_list.each do |error_to_find|
486
+ if error_message[/#{error_to_find}/i]
487
+ error_found = error_to_find
488
+ logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
489
+ break
490
+ end
491
+ end
492
+ # yep, we know this error and have to do a retry when it comes
493
+ if error_found || REAUTHENTICATE_ON.include?(@handle.last_response.code)
494
+ # check the time has gone from the first error come
495
+ # Close the connection to the server and recreate a new one.
496
+ # It may have a chance that one server is a semi-down and reconnection
497
+ # will help us to connect to the other server
498
+ if (Time.now < @stop_at)
499
+ @retries += 1
500
+ @handle.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
501
+ sleep @reiteration_delay
502
+ @reiteration_delay *= 2
503
+ # Always make sure that the fp is set to point to the beginning(?)
504
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
505
+ if request_hash[:request].body_stream && request_hash[:request].body_stream.respond_to?(:pos)
506
+ begin
507
+ request_hash[:request].body_stream.pos = 0
508
+ rescue Exception => e
509
+ logger.warn("Retry may fail due to unable to reset the file pointer -- #{self.class.name} : #{e.inspect}")
510
+ end
511
+ end
512
+ # Oops it seems we have been asked about reauthentication..
513
+ if REAUTHENTICATE_ON.include?(@handle.last_response.code)
514
+ @handle.authenticate
515
+ @handle.request_info(request_hash)
516
+ end
517
+ # Make another try
518
+ result = @handle.request_info(request_hash)
519
+ else
520
+ logger.warn("##### Ooops, time is over... ####")
521
+ end
522
+ end
523
+ result
524
+ end
525
+
526
+ end
527
+ end
528
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ require 'benchmark'
25
+ require 'net/https'
26
+ require 'uri'
27
+ require "base64"
28
+ require 'rubygems'
29
+ require 'json'
30
+ require 'right_http_connection'
31
+
32
+ $:.unshift(File.dirname(__FILE__))
33
+ require 'benchmark_fix'
34
+ require 'support'
35
+ require 'rackspace_base'
36
+ require 'rackspace'
37
+
38
+ module RightRackspace #:nodoc:
39
+ module VERSION #:nodoc:
40
+ MAJOR = 0
41
+ MINOR = 0
42
+ TINY = 0
43
+
44
+ STRING = [MAJOR, MINOR, TINY].join('.')
45
+ end
46
+ end
data/lib/support.rb ADDED
@@ -0,0 +1,124 @@
1
+ # If ActiveSupport is loaded, then great - use it. But we don't
2
+ # want a dependency on it, so if it's not present, define the few
3
+ # extensions that we want to use...
4
+ unless defined? ActiveSupport::CoreExtensions
5
+ # These are ActiveSupport-;like extensions to do a few handy things in the gems
6
+ # Derived from ActiveSupport, so the AS copyright notice applies:
7
+ #
8
+ #
9
+ #
10
+ # Copyright (c) 2005 David Heinemeier Hansson
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining
13
+ # a copy of this software and associated documentation files (the
14
+ # "Software"), to deal in the Software without restriction, including
15
+ # without limitation the rights to use, copy, modify, merge, publish,
16
+ # distribute, sublicense, and/or sell copies of the Software, and to
17
+ # permit persons to whom the Software is furnished to do so, subject to
18
+ # the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be
21
+ # included in all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+ #++
31
+ #
32
+ #
33
+ class String #:nodoc:
34
+
35
+ # Constantize tries to find a declared constant with the name specified
36
+ # in the string. It raises a NameError when the name is not in CamelCase
37
+ # or is not initialized.
38
+ #
39
+ # Examples
40
+ # "Module".constantize #=> Module
41
+ # "Class".constantize #=> Class
42
+ def constantize()
43
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
44
+ raise NameError, "#{self.inspect} is not a valid constant name!"
45
+ end
46
+
47
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
48
+ end
49
+
50
+ def camelize()
51
+ self.dup.split(/_/).map{ |word| word.capitalize }.join('')
52
+ end
53
+
54
+ end
55
+
56
+
57
+ class Object #:nodoc:
58
+ # "", " ", nil, [], and {} are blank
59
+ def blank?
60
+ if respond_to?(:empty?) && respond_to?(:strip)
61
+ empty? or strip.empty?
62
+ elsif respond_to?(:empty?)
63
+ empty?
64
+ else
65
+ !self
66
+ end
67
+ end
68
+ end
69
+
70
+ class NilClass #:nodoc:
71
+ def blank?
72
+ true
73
+ end
74
+ end
75
+
76
+ class FalseClass #:nodoc:
77
+ def blank?
78
+ true
79
+ end
80
+ end
81
+
82
+ class TrueClass #:nodoc:
83
+ def blank?
84
+ false
85
+ end
86
+ end
87
+
88
+ class Array #:nodoc:
89
+ alias_method :blank?, :empty?
90
+ end
91
+
92
+ class Hash #:nodoc:
93
+ alias_method :blank?, :empty?
94
+
95
+ # Return a new hash with all keys converted to symbols.
96
+ def symbolize_keys
97
+ inject({}) do |options, (key, value)|
98
+ options[key.to_sym] = value
99
+ options
100
+ end
101
+ end
102
+ end
103
+
104
+ class String #:nodoc:
105
+ def blank?
106
+ empty? || strip.empty?
107
+ end
108
+ end
109
+
110
+ class Numeric #:nodoc:
111
+ def blank?
112
+ false
113
+ end
114
+ end
115
+ end
116
+
117
+ # We also use String#first from 1.8.7 that doesn't exist in 1.8.6
118
+ unless String.instance_methods.include? "first"
119
+ class String
120
+ def first
121
+ self
122
+ end
123
+ end
124
+ end