right_support 0.9.9 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -65,17 +65,20 @@ any exceptions. (NB: a nil return value counts as success!!) If you specify that
65
65
  certain class of exception is "fatal," then that exception will cause REST to re-
66
66
  raise immediately
67
67
 
68
- == HTTP REST Client
68
+ == HTTPClient
69
69
 
70
70
  We provide a very thin wrapper around the rest-client gem that enables simple but
71
71
  robust rest requests with a timeout, headers, etc.
72
72
 
73
- The RightSupport::Net::REST module is interface-compatible with the RestClient
73
+ The HTTPClient is interface-compatible with the RestClient
74
74
  module, but allows an optional timeout to be specified as an extra parameter.
75
-
75
+
76
+ # Create a wrapper's object
77
+ @client = RightSupport::Net::HTTPClient.new
78
+
76
79
  # Default timeout is 5 seconds
77
- RightSupport::Net::REST.get('http://localhost')
80
+ @client.get('http://localhost')
78
81
 
79
- # Make sure the REST request fails after 1 second so we can report an error
82
+ # Make sure the HTTPClient request fails after 1 second so we can report an error
80
83
  # and move on!
81
- RightSupport::Net::REST.get('http://localhost', {'X-Hello'=>'hi!'}, 1)
84
+ @client.get('http://localhost', {:headers => {'X-Hello'=>'hi!'}, :timeout => 1)}
@@ -32,7 +32,7 @@ module RightSupport::DB
32
32
  return @@conn if @@conn
33
33
 
34
34
  config = @@config[ENV["RACK_ENV"]]
35
- @@conn = Cassandra.new(keyspace, config["server"],{:timeout => RightSupport::CassandraModel::DEFAULT_TIMEOUT})
35
+ @@conn = Cassandra.new(keyspace, config["server"],{:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT})
36
36
  @@conn.disable_node_auto_discovery!
37
37
  @@conn
38
38
  end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Copyright (c) 2011 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
+ require 'set'
24
+
25
+ module RightSupport::Net::Balancing
26
+
27
+ class EndpointsStack
28
+
29
+ # Modified by Ryan Williamson on 9/28/2011 to ensure default values exist
30
+ # for @yellow_states and @reset_time
31
+ DEFAULT_YELLOW_STATES = 4
32
+ DEFAULT_RESET_TIME = 300
33
+
34
+ def initialize(endpoints, yellow_states=nil, reset_time=nil)
35
+ @endpoints = Hash.new
36
+ @yellow_states = yellow_states || DEFAULT_YELLOW_STATES
37
+ @reset_time = reset_time || DEFAULT_RESET_TIME
38
+ endpoints.each { |ep| @endpoints[ep] = {:n_level => 0,:timestamp => 0 }}
39
+ end
40
+
41
+ def sweep
42
+ @endpoints.each { |k,v| decrease_state(k,0,Time.now) if Float(Time.now - v[:timestamp]) > @reset_time }
43
+ end
44
+
45
+ def sweep_and_return_yellow_and_green
46
+ sweep
47
+ @endpoints.select { |k,v| v[:n_level] < @yellow_states }
48
+ end
49
+
50
+ def decrease_state(endpoint,t0,t1)
51
+ unless @endpoints[endpoint][:n_level] == 0
52
+ @endpoints[endpoint][:n_level] -= 1
53
+ @endpoints[endpoint][:timestamp] = t1
54
+ end
55
+ end
56
+
57
+ def increase_state(endpoint,t0,t1)
58
+ unless @endpoints[endpoint][:n_level] == @yellow_states
59
+ @endpoints[endpoint][:n_level] += 1
60
+ @endpoints[endpoint][:timestamp] = t1
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ # Implementation concepts: endpoints have three states, red, yellow and green. Yellow
67
+ # has several levels (@yellow_states) to determine the health of the endpoint. The
68
+ # balancer works by avoiding "red" endpoints and retrying them after awhile. Here is a
69
+ # brief description of the state transitions:
70
+ # * green: last request was successful.
71
+ # * on success: remain green
72
+ # * on failure: change state to yellow and set it's health to healthiest (1)
73
+ # * red: skip this server
74
+ # * after @reset_time passes change state to yellow and set it's health to
75
+ # sickest (@yellow_states)
76
+ # * yellow: last request was either successful or failed
77
+ # * on success: change state to green if it's health was healthiest (1), else
78
+ # retain yellow state and improve it's health
79
+ # * on failure: change state to red if it's health was sickest (@yellow_states), else
80
+ # retain yellow state and decrease it's health
81
+
82
+ class HealthCheck
83
+
84
+ def initialize(endpoints,options = {})
85
+ # Modified by Ryan Williamson on 9/27/2011
86
+ # Previously if you created an instance of HealthCheck without the required options
87
+ # they would get passed as nil and overwrite EndpointsStack's default options causing an ArgumentError
88
+ yellow_states = options[:yellow_states]
89
+ reset_time = options[:reset_time]
90
+ # End modification
91
+ @health_check = options.delete(:health_check)
92
+
93
+ @stack = EndpointsStack.new(endpoints,yellow_states,reset_time)
94
+ @counter = rand(0xffff)
95
+ end
96
+
97
+ def next
98
+ # Returns the array of hashes which consists of yellow and green endpoints with the
99
+ # following structure: [ [EP1, {:n_level => ..., :timestamp => ... }], [EP2, ... ] ]
100
+ endpoints = @stack.sweep_and_return_yellow_and_green
101
+ return nil if endpoints.empty?
102
+
103
+ # Selection of the next endpoint using RoundRobin
104
+ @counter += 1
105
+ i = @counter % endpoints.size
106
+
107
+ # Returns false or true, depending on whether EP is yellow or not
108
+ [ endpoints[i][0], endpoints[i][1][:n_level] != 0 ]
109
+ end
110
+
111
+ def good(endpoint, t0, t1)
112
+ @stack.decrease_state(endpoint,t0,t1)
113
+ end
114
+
115
+ def bad(endpoint, t0, t1)
116
+ @stack.increase_state(endpoint,t0,t1)
117
+ end
118
+
119
+ def health_check(endpoint)
120
+ @stack.increase_state(endpoint,t0,Time.now) unless @health_check.call(endpoint)
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,43 @@
1
+ #
2
+ # Copyright (c) 2011 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
+ module RightSupport::Net::Balancing
24
+ class RoundRobin
25
+ def initialize(endpoints,options ={})
26
+ @endpoints = endpoints
27
+ @counter = rand(0xffff)
28
+ end
29
+
30
+ def next
31
+ @counter += 1
32
+ [ @endpoints[@counter % @endpoints.size], false ]
33
+ end
34
+
35
+ def good(endpoint, t0, t1)
36
+ #no-op; round robin does not care about failures
37
+ end
38
+
39
+ def bad(endpoint, t0, t1)
40
+ #no-op; round robin does not care about failures
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # Copyright (c) 2011 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
+ # A namespace to hold load-balancing policies to be used with RequestBalancer
25
+ # and potentially other networking classes.
26
+ #
27
+ module RightSupport::Net::Balancing
28
+
29
+ end
30
+
31
+ Dir[File.expand_path('../balancing/*.rb', __FILE__)].each do |filename|
32
+ require filename
33
+ end
@@ -0,0 +1,98 @@
1
+ module RightSupport::Net
2
+ if_require_succeeds('right_http_connection') do
3
+ #nothing, nothing at all! just need to make sure
4
+ #that RightHttpConnection gets loaded before
5
+ #rest-client, so the Net::HTTP monkey patches
6
+ #take effect.
7
+ end
8
+
9
+ if_require_succeeds('restclient') do
10
+ HAS_REST_CLIENT = true
11
+ end
12
+
13
+ # Raised to indicate that no suitable provider of REST/HTTP services was found. Since RightSupport's
14
+ # REST support is merely a wrapper around other libraries, it cannot work in isolation. See the REST
15
+ # module for more information about supported providers.
16
+ class NoProvider < Exception; end
17
+
18
+ #
19
+ # A wrapper for the rest-client gem that provides timeouts and other useful features while preserving
20
+ # the simplicity and ease of use of RestClient's simple, static (class-level) interface.
21
+ #
22
+ # Even though this code relies on RestClient, the right_support gem does not depend on the rest-client
23
+ # gem because not all users of right_support will want to make use of this interface. If one of HTTPClient
24
+ # instance's method is called and RestClient is not available, an exception will be raised.
25
+ #
26
+ #
27
+ # HTTPClient supports a subset of the module methods provided by RestClient and is interface-compatible
28
+ # with those methods it replaces; the only difference is that the HTTPClient version of each method accepts an
29
+ # additional, optional parameter which is a request timeout in seconds. The RestClient gem does not allow
30
+ # timeouts without instantiating a "heavyweight" HTTPClient object.
31
+ #
32
+ # # create an instance ot HTTPClient
33
+ # @client = HTTPClient.new()
34
+ #
35
+ # # GET
36
+ # xml = @client.get 'http://example.com/resource'
37
+ # # and, with timeout of 5 seconds...
38
+ # jpg = @client.get 'http://example.com/resource', {:accept => 'image/jpg', :timeout => 5}
39
+ #
40
+ # # authentication and SSL
41
+ # @client.get 'https://user:password@example.com/private/resource'
42
+ #
43
+ # # POST or PUT with a hash sends parameters as a urlencoded form body
44
+ # @client.post 'http://example.com/resource', {:param1 => 'one'}
45
+ #
46
+ # # nest hash parameters, add a timeout of 10 seconds (and specify "no extra headers")
47
+ # @client.post 'http://example.com/resource', {:payload => {:nested => {:param1 => 'one'}}, :timeout => 10}
48
+ #
49
+ # # POST and PUT with raw payloads
50
+ # @client.post 'http://example.com/resource', {:payload => 'the post body', :headers => {:content_type => 'text/plain'}}
51
+ # @client.post 'http://example.com/resource.xml', {:payload => xml_doc}
52
+ # @client.put 'http://example.com/resource.pdf', {:payload => File.read('my.pdf'), :headers => {:content_type => 'application/pdf'}}
53
+ #
54
+ # # DELETE
55
+ # @client.delete 'http://example.com/resource'
56
+ #
57
+ # # retrieve the response http code and headers
58
+ # res = @client.get 'http://example.com/some.jpg'
59
+ # res.code # => 200
60
+ # res.headers[:content_type] # => 'image/jpg'
61
+ class HTTPClient
62
+
63
+ DEFAULT_TIMEOUT = 5
64
+ DEFAULT_OPEN_TIMEOUT = 2
65
+
66
+ def initialize(options = {})
67
+ [:get, :post, :put, :delete].each do |method|
68
+ define_instance_method(method) {|*args| query(method, *args)}
69
+ end
70
+ end
71
+
72
+ protected
73
+
74
+ # Helps to add default methods to class
75
+ def define_instance_method(method, &block)
76
+ (class << self; self; end).module_eval do
77
+ define_method(method, &block)
78
+ end
79
+ end
80
+
81
+ def query(type, url, options={}, &block)
82
+ options[:timeout] ||= DEFAULT_TIMEOUT
83
+ options[:open_timeout] ||= DEFAULT_OPEN_TIMEOUT
84
+ options[:headers] ||= {}
85
+ options.merge!(:method => type, :url => url)
86
+ request(options, &block)
87
+ end
88
+
89
+ # Wrapper around RestClient::Request.execute -- see class documentation for details.
90
+ def request(options, &block)
91
+ if HAS_REST_CLIENT
92
+ RestClient::Request.execute(options, &block)
93
+ else
94
+ raise NoProvider, "Cannot find a suitable HTTP client library"
95
+ end
96
+ end
97
+ end# HTTPClient
98
+ end
@@ -2,11 +2,15 @@ module RightSupport::Net
2
2
  # Raised to indicate the (uncommon) error condition where a RequestBalancer rotated
3
3
  # through EVERY URL in a list without getting a non-nil, non-timeout response.
4
4
  class NoResult < Exception; end
5
-
5
+
6
6
  # Utility class that allows network requests to be randomly distributed across
7
7
  # a set of network endpoints. Generally used for REST requests by passing an
8
8
  # Array of HTTP service endpoint URLs.
9
9
  #
10
+ # Note that this class also serves as a namespace for endpoint selection policies,
11
+ # which are classes that actually choose the next endpoint based on some criterion
12
+ # (round-robin, health of endpoint, response time, etc).
13
+ #
10
14
  # The balancer does not actually perform requests by itself, which makes this
11
15
  # class usable for various network protocols, and potentially even for non-
12
16
  # networking purposes. The block does all the work; the balancer merely selects
@@ -18,6 +22,10 @@ module RightSupport::Net
18
22
  # MAY NOT BE SUFFICIENT for some uses of the request balancer! Please use the :fatal
19
23
  # option if you need different behavior.
20
24
  class RequestBalancer
25
+ DEFAULT_RETRY_PROC = lambda do |ep, n|
26
+ n < ep.size
27
+ end
28
+
21
29
  DEFAULT_FATAL_EXCEPTIONS = [ScriptError, ArgumentError, IndexError, LocalJumpError, NameError]
22
30
 
23
31
  DEFAULT_FATAL_PROC = lambda do |e|
@@ -35,11 +43,28 @@ module RightSupport::Net
35
43
  end
36
44
  end
37
45
 
46
+ DEFAULT_HEALTH_CHECK_PROC = Proc.new do |endpoint|
47
+ true
48
+ end
49
+
38
50
  DEFAULT_OPTIONS = {
51
+ :policy => nil,
52
+ :retry => DEFAULT_RETRY_PROC,
39
53
  :fatal => DEFAULT_FATAL_PROC,
40
- :on_exception => nil
54
+ :on_exception => nil,
55
+ :health_check => DEFAULT_HEALTH_CHECK_PROC
41
56
  }
42
57
 
58
+ @@logger = nil
59
+
60
+ def self.logger
61
+ @@logger
62
+ end
63
+
64
+ def self.logger=(logger)
65
+ @@logger = logger
66
+ end
67
+
43
68
  def self.request(endpoints, options={}, &block)
44
69
  new(endpoints, options).request(&block)
45
70
  end
@@ -52,8 +77,10 @@ module RightSupport::Net
52
77
  # endpoints(Array):: a set of network endpoints (e.g. HTTP URLs) to be load-balanced
53
78
  #
54
79
  # === Options
55
- # fatal(Class):: a class, list of classes or decision Proc to determine whether an exception is fatal and should not be retried
56
- # on_exception(Proc|Lambda):: notification hook that accepts three arguments: whether the exception is fatal, the exception itself, and the endpoint for which the exception happened
80
+ # retry:: a Class, array of Class or decision Proc to determine whether to keep retrying; default is to try all endpoints
81
+ # fatal:: a Class, array of Class, or decision Proc to determine whether an exception is fatal and should not be retried
82
+ # on_exception(Proc):: notification hook that accepts three arguments: whether the exception is fatal, the exception itself, and the endpoint for which the exception happened
83
+ # health_check(Proc):: callback that allows balancer to check an endpoint health; should raise an exception if the endpoint is not healthy
57
84
  #
58
85
  def initialize(endpoints, options={})
59
86
  @options = DEFAULT_OPTIONS.merge(options)
@@ -62,7 +89,18 @@ module RightSupport::Net
62
89
  raise ArgumentError, "Must specify at least one endpoint"
63
90
  end
64
91
 
65
- unless test_callable_arity(options[:fatal], 1, true)
92
+ @options[:policy] ||= RightSupport::Net::Balancing::RoundRobin
93
+ @policy = @options[:policy]
94
+ @policy = @policy.new(endpoints,options) if @policy.is_a?(Class)
95
+ unless test_policy_duck_type(@policy)
96
+ raise ArgumentError, ":policy must be a class/object that responds to :next, :good and :bad"
97
+ end
98
+
99
+ unless test_callable_arity(options[:retry], 2)
100
+ raise ArgumentError, ":retry callback must accept two parameters"
101
+ end
102
+
103
+ unless test_callable_arity(options[:fatal], 1)
66
104
  raise ArgumentError, ":fatal callback must accept one parameter"
67
105
  end
68
106
 
@@ -70,6 +108,10 @@ module RightSupport::Net
70
108
  raise ArgumentError, ":on_exception callback must accept three parameters"
71
109
  end
72
110
 
111
+ unless test_callable_arity(options[:health_check], 1, false)
112
+ raise ArgumentError, ":health_check callback must accept one parameters"
113
+ end
114
+
73
115
  @endpoints = endpoints.shuffle
74
116
  end
75
117
 
@@ -94,27 +136,59 @@ module RightSupport::Net
94
136
  complete = false
95
137
  n = 0
96
138
 
97
- while !complete && n < @endpoints.size
98
- endpoint = next_endpoint
139
+ retry_opt = @options[:retry] || DEFAULT_RETRY_PROC
140
+ health_check = @options[:health_check]
141
+
142
+ loop do
143
+ if complete
144
+ break
145
+ else
146
+ max_n = retry_opt
147
+ max_n = max_n.call(@endpoints, n) if max_n.respond_to?(:call)
148
+ break if (max_n.is_a?(Integer) && n >= max_n) || !(max_n)
149
+ end
150
+
151
+ endpoint, need_health_check = @policy.next
152
+
153
+ raise NoResult, "No endpoints are available" unless endpoint
99
154
  n += 1
155
+ t0 = Time.now
156
+
157
+ # HealthCheck goes here
158
+ if need_health_check
159
+ begin
160
+ @policy.health_check(endpoint)
161
+ rescue Exception => e
162
+ @policy.bad(endpoint, t0, Time.now)
163
+ log_error("RequestBalancer: health check failed to #{endpoint} because of #{e.class.name}: #{e.message}")
164
+ next
165
+ end
166
+
167
+ log_info("RequestBalancer: health check succeeded to #{endpoint}")
168
+ end
100
169
 
101
170
  begin
102
171
  result = yield(endpoint)
172
+ @policy.good(endpoint, t0, Time.now)
103
173
  complete = true
104
174
  break
105
175
  rescue Exception => e
176
+ @policy.bad(endpoint, t0, Time.now)
106
177
  if to_raise = handle_exception(endpoint, e)
107
178
  raise(to_raise)
108
179
  else
109
180
  exceptions << e
110
181
  end
111
182
  end
183
+
112
184
  end
113
185
 
114
186
  return result if complete
115
187
 
116
188
  exceptions = exceptions.map { |e| e.class.name }.uniq.join(', ')
117
- raise NoResult, "All URLs in the rotation failed! Exceptions: #{exceptions}"
189
+ msg = "No available endpoints from #{@endpoints.inspect}! Exceptions: #{exceptions}"
190
+ log_error("RequestBalancer: #{msg}")
191
+ raise NoResult, msg
118
192
  end
119
193
 
120
194
  protected
@@ -135,7 +209,8 @@ module RightSupport::Net
135
209
  #whether the exception we're handling is an instance of any mentioned exception
136
210
  #class
137
211
  fatal = fatal.any?{ |c| e.is_a?(c) } if fatal.respond_to?(:any?)
138
-
212
+ msg = "RequestBalancer: rescued #{fatal ? 'fatal' : 'retryable'} #{e.class.name} during request to #{endpoint}: #{e.message}"
213
+ log_error msg
139
214
  @options[:on_exception].call(fatal, e, endpoint) if @options[:on_exception]
140
215
 
141
216
  if fatal
@@ -146,20 +221,28 @@ module RightSupport::Net
146
221
  end
147
222
  end
148
223
 
149
- def next_endpoint
150
- @round_robin ||= 0
151
- result = @endpoints[ @round_robin % @endpoints.size ]
152
- @round_robin += 1
153
- return result
224
+ def test_policy_duck_type(object)
225
+ [:next, :good, :bad].all? { |m| object.respond_to?(m) }
154
226
  end
155
227
 
156
228
  # Test that something is a callable (Proc, Lambda or similar) with the expected arity.
157
229
  # Used mainly by the initializer to test for correct options.
158
- def test_callable_arity(callable, arity, optional)
230
+ def test_callable_arity(callable, arity, optional=true)
159
231
  return true if callable.nil?
160
232
  return true if optional && !callable.respond_to?(:call)
161
233
  return callable.respond_to?(:arity) && (callable.arity == arity)
162
234
  end
235
+
236
+ # Log an info message with the class logger, if provided
237
+ def log_info(*args)
238
+ self.class.logger.__send__(:info, *args) if self.class.logger.respond_to?(:info)
239
+ end
240
+
241
+ # Log an error message with the class logger, if provided
242
+ def log_error(*args)
243
+ self.class.logger.__send__(:error, *args) if self.class.logger.respond_to?(:error)
244
+ end
245
+
163
246
  end # RequestBalancer
164
247
 
165
248
  end # RightScale
data/lib/right_support.rb CHANGED
@@ -5,9 +5,3 @@ require 'right_support/log'
5
5
  require 'right_support/net'
6
6
  require 'right_support/rack'
7
7
  require 'right_support/validation'
8
-
9
- # Deprecated stubs
10
- require 'right_support/cassandra_model'
11
- require 'right_support/filter_logger'
12
- require 'right_support/system_logger'
13
- require 'right_support/tag_logger'
@@ -7,8 +7,8 @@ spec = Gem::Specification.new do |s|
7
7
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
8
 
9
9
  s.name = 'right_support'
10
- s.version = '0.9.9'
11
- s.date = '2011-09-12'
10
+ s.version = '1.0.4'
11
+ s.date = '2011-10-06'
12
12
 
13
13
  s.authors = ['Tony Spataro']
14
14
  s.email = 'tony@rightscale.com'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_support
3
3
  version: !ruby/object:Gem::Version
4
- hash: 41
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
+ - 1
7
8
  - 0
8
- - 9
9
- - 9
10
- version: 0.9.9
9
+ - 4
10
+ version: 1.0.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-12 00:00:00 -07:00
18
+ date: 2011-10-06 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -136,26 +136,23 @@ files:
136
136
  - LICENSE
137
137
  - README.rdoc
138
138
  - lib/right_support.rb
139
- - lib/right_support/cassandra_model.rb
140
139
  - lib/right_support/db.rb
141
140
  - lib/right_support/db/cassandra_model.rb
142
- - lib/right_support/filter_logger.rb
143
141
  - lib/right_support/log.rb
144
142
  - lib/right_support/log/filter_logger.rb
145
143
  - lib/right_support/log/system_logger.rb
146
144
  - lib/right_support/log/tag_logger.rb
147
145
  - lib/right_support/net.rb
148
146
  - lib/right_support/net/address_helper.rb
147
+ - lib/right_support/net/balancing.rb
148
+ - lib/right_support/net/balancing/health_check.rb
149
+ - lib/right_support/net/balancing/round_robin.rb
150
+ - lib/right_support/net/http_client.rb
149
151
  - lib/right_support/net/request_balancer.rb
150
- - lib/right_support/net/request_balancer/policy.rb
151
- - lib/right_support/net/request_balancer/round_robin.rb
152
- - lib/right_support/net/rest.rb
153
152
  - lib/right_support/rack.rb
154
153
  - lib/right_support/rack/custom_logger.rb
155
154
  - lib/right_support/ruby.rb
156
155
  - lib/right_support/ruby/object_extensions.rb
157
- - lib/right_support/system_logger.rb
158
- - lib/right_support/tag_logger.rb
159
156
  - lib/right_support/validation.rb
160
157
  - lib/right_support/validation/openssl.rb
161
158
  - lib/right_support/validation/ssh.rb
@@ -1,3 +0,0 @@
1
- module RightSupport
2
- CassandraModel = RightSupport::DB::CassandraModel
3
- end
@@ -1,3 +0,0 @@
1
- module RightSupport
2
- FilterLogger = RightSupport::Log::FilterLogger
3
- end
@@ -1,14 +0,0 @@
1
- class RightSupport::Net::RequestBalancer::Policy
2
- def next_endpoint
3
- raise NotImplementedError, "Subclass responsibility"
4
- end
5
-
6
- def report_success(endpoint)
7
- #no-op
8
- end
9
-
10
- def report_failure(endpoint)
11
- #no-op
12
- end
13
- end
14
-
@@ -1,7 +0,0 @@
1
- class RightSupport::Net::RequestBalancer::RoundRobin < Policy
2
- def next_endpoint(endpoints)
3
- result = @endpoints[ @counter % @endpoints.size ]
4
- @counter += 1
5
- return result
6
- end
7
- end
@@ -1,90 +0,0 @@
1
- module RightSupport::Net
2
- begin
3
- require 'right_http_connection'
4
- rescue LoadError
5
- end
6
- if_require_succeeds('restclient') do
7
- HAS_REST_CLIENT = true
8
- end
9
-
10
- # Raised to indicate that no suitable provider of REST/HTTP services was found. Since RightSupport's
11
- # REST support is merely a wrapper around other libraries, it cannot work in isolation. See the REST
12
- # module for more information about supported providers.
13
- class NoProvider < Exception; end
14
-
15
- #
16
- # A wrapper for the rest-client gem that provides timeouts and other useful features while preserving
17
- # the simplicity and ease of use of RestClient's simple, static (module-level) interface.
18
- #
19
- # Even though this code relies on RestClient, the right_support gem does not depend on the rest-client
20
- # gem because not all users of right_support will want to make use of this interface. If one of REST's
21
- # method is called and RestClient is not available, an exception will be raised.
22
- #
23
- #
24
- # This module supports a subset of the module methods provided by RestClient and is interface-compatible
25
- # with those methods it replaces; the only difference is that the REST version of each method accepts an
26
- # additional, optional parameter which is a request timeout in seconds. The RestClient gem does not allow
27
- # timeouts without instantiating a "heavyweight" REST client object.
28
- #
29
- # # GET
30
- # xml = REST.get 'http://example.com/resource'
31
- # # and, with timeout of 5 seconds...
32
- # jpg = REST.get 'http://example.com/resource', :accept => 'image/jpg', 5
33
- #
34
- # # authentication and SSL
35
- # REST.get 'https://user:password@example.com/private/resource'
36
- #
37
- # # POST or PUT with a hash sends parameters as a urlencoded form body
38
- # REST.post 'http://example.com/resource', :param1 => 'one'
39
- #
40
- # # nest hash parameters, add a timeout of 10 seconds (and specify "no extra headers")
41
- # REST.post 'http://example.com/resource', :nested => { :param1 => 'one' }, {}, 10
42
- #
43
- # # POST and PUT with raw payloads
44
- # REST.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
45
- # REST.post 'http://example.com/resource.xml', xml_doc
46
- # REST.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
47
- #
48
- # # DELETE
49
- # REST.delete 'http://example.com/resource'
50
- #
51
- # # retrieve the response http code and headers
52
- # res = REST.get 'http://example.com/some.jpg'
53
- # res.code # => 200
54
- # res.headers[:content_type] # => 'image/jpg'
55
- module REST
56
- DEFAULT_TIMEOUT = 5
57
-
58
- # Wrapper around RestClient.get -- see class documentation for details.
59
- def self.get(url, headers={}, timeout=DEFAULT_TIMEOUT, &block)
60
- request(:method=>:get, :url=>url, :timeout=>timeout, :headers=>headers, &block)
61
- end
62
-
63
- # Wrapper around RestClient.get -- see class documentation for details.
64
- def self.post(url, payload, headers={}, timeout=DEFAULT_TIMEOUT, &block)
65
- request(:method=>:post, :url=>url, :payload=>payload,
66
- :timeout=>timeout, :headers=>headers, &block)
67
- end
68
-
69
- # Wrapper around RestClient.get -- see class documentation for details.
70
- def self.put(url, payload, headers={}, timeout=DEFAULT_TIMEOUT, &block)
71
- request(:method=>:put, :url=>url, :payload=>payload,
72
- :timeout=>timeout, :headers=>headers, &block)
73
- end
74
-
75
- # Wrapper around RestClient.get -- see class documentation for details.
76
- def self.delete(url, headers={}, timeout=DEFAULT_TIMEOUT, &block)
77
- request(:method=>:delete, :url=>url, :timeout=>timeout, :headers=>headers, &block)
78
- end
79
-
80
- # Wrapper around RestClient::Request.execute -- see class documentation for details.
81
- def self.request(options, &block)
82
- if HAS_REST_CLIENT
83
- RestClient::Request.execute(options, &block)
84
- else
85
- raise NoProvider, "Cannot find a suitable HTTP client library"
86
- end
87
- end
88
-
89
- end
90
- end
@@ -1,3 +0,0 @@
1
- module RightSupport
2
- SystemLogger = RightSupport::Log::SystemLogger if defined?(RightSupport::Log::SystemLogger)
3
- end
@@ -1,3 +0,0 @@
1
- module RightSupport
2
- TagLogger = RightSupport::Log::TagLogger
3
- end