right_support 0.9.9 → 1.0.4

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