right_support 1.2.0 → 1.3.0

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 RightScale, Inc.
1
+ Copyright (c) 2009-2012 RightScale, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -72,7 +72,43 @@ rescue LoadError => e
72
72
  # Missing 'cassandra/0.8' indicates that the cassandra gem is not installed; we can ignore this
73
73
  end
74
74
 
75
+ # monkey patch thrift to work with jruby
76
+ if (RUBY_PLATFORM =~ /java/)
77
+ begin
78
+ require 'thrift'
79
+ module Thrift
80
+ class Socket
81
+ def open
82
+ begin
83
+ addrinfo = ::Socket::getaddrinfo(@host, @port).first
84
+ @handle = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
85
+ sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
86
+ begin
87
+ @handle.connect_nonblock(sockaddr)
88
+ rescue Errno::EINPROGRESS
89
+ resp = IO.select(nil, [ @handle ], nil, @timeout) # 3 lines removed here, 1 line added
90
+ begin
91
+ @handle.connect_nonblock(sockaddr)
92
+ rescue Errno::EISCONN
93
+ end
94
+ end
95
+ @handle
96
+ rescue StandardError => e
97
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ rescue LoadError => e
103
+ # Make sure we're dealing with a legitimate missing-file LoadError
104
+ raise e unless e.message =~ /^no such file to load/
105
+ end
106
+ end
107
+
108
+
75
109
  module RightSupport::DB
110
+ # Exception that indicates database configuration info is missing.
111
+ class MissingConfiguration < Exception; end
76
112
 
77
113
  # Base class for a column family in a keyspace
78
114
  # Used to access data persisted in Cassandra
@@ -116,14 +152,22 @@ module RightSupport::DB
116
152
 
117
153
  # Client connected to Cassandra server
118
154
  # Create connection if does not already exist
155
+ # Use BinaryProtocolAccelerated if it available
119
156
  #
120
157
  # === Return
121
158
  # (Cassandra):: Client connected to server
122
159
  def conn
123
160
  return @@conn if @@conn
124
161
 
162
+ # TODO remove hidden dependency on ENV['RACK_ENV'] (maybe require config= to accept a sub hash?)
125
163
  config = @@config[ENV["RACK_ENV"]]
126
- @@conn = Cassandra.new(keyspace, config["server"],{:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT})
164
+ raise MissingConfiguration, "CassandraModel config is missing a '#{ENV['RACK_ENV']}' section" unless config
165
+
166
+ thrift_client_options = {:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT}
167
+ thrift_client_options.merge!({:protocol => Thrift::BinaryProtocolAccelerated})\
168
+ if defined? Thrift::BinaryProtocolAccelerated
169
+
170
+ @@conn = Cassandra.new(keyspace, config["server"], thrift_client_options)
127
171
  @@conn.disable_node_auto_discovery!
128
172
  @@conn
129
173
  end
@@ -162,6 +206,8 @@ module RightSupport::DB
162
206
 
163
207
  # Get raw row(s) for specified primary key(s)
164
208
  # Unless :count is specified, a maximum of 100 columns are retrieved
209
+ # except in the case of an individual primary key request, in which
210
+ # case all columns are retrieved
165
211
  #
166
212
  # === Parameters
167
213
  # k(String|Array):: Individual primary key or list of keys on which to match
@@ -173,8 +219,25 @@ module RightSupport::DB
173
219
  def real_get(k, opt = {})
174
220
  if k.is_a?(Array)
175
221
  do_op(:multi_get, column_family, k, opt)
176
- else
222
+ elsif opt[:count]
177
223
  do_op(:get, column_family, k, opt)
224
+ else
225
+ opt = opt.clone
226
+ opt[:start] ||= ""
227
+ opt[:count] = DEFAULT_COUNT
228
+ columns = Cassandra::OrderedHash.new
229
+ while true
230
+ chunk = do_op(:get, column_family, k, opt)
231
+ columns.merge!(chunk)
232
+ if chunk.size == opt[:count]
233
+ # Assume there are more chunks, use last key as start of next get
234
+ opt[:start] = chunk.keys.last
235
+ else
236
+ # This must be the last chunk
237
+ break
238
+ end
239
+ end
240
+ columns
178
241
  end
179
242
  end
180
243
 
@@ -312,12 +375,19 @@ module RightSupport::DB
312
375
  end
313
376
 
314
377
  # Reconnect to Cassandra server
378
+ # Use BinaryProtocolAccelerated if it available
315
379
  #
316
380
  # === Return
317
381
  # true:: Always return true
318
382
  def reconnect
319
383
  config = @@config[ENV["RACK_ENV"]]
320
- @@conn = Cassandra.new(keyspace, config["server"], {:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT})
384
+ raise MissingConfiguration, "CassandraModel config is missing a '#{ENV['RACK_ENV']}' section" unless config
385
+
386
+ thrift_client_options = {:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT}
387
+ thrift_client_options.merge!({:protocol => Thrift::BinaryProtocolAccelerated})\
388
+ if defined? Thrift::BinaryProtocolAccelerated
389
+
390
+ @@conn = Cassandra.new(keyspace, config["server"], thrift_client_options)
321
391
  @@conn.disable_node_auto_discovery!
322
392
  true
323
393
  end
@@ -329,7 +399,8 @@ module RightSupport::DB
329
399
  def ring
330
400
  conn.ring
331
401
  end
332
- end
402
+
403
+ end # self
333
404
 
334
405
  attr_accessor :key, :attributes
335
406
 
@@ -30,7 +30,7 @@ module RightSupport::Net::Balancing
30
30
  class EndpointsStack
31
31
  DEFAULT_YELLOW_STATES = 4
32
32
  DEFAULT_RESET_TIME = 300
33
-
33
+
34
34
  def initialize(endpoints, yellow_states=nil, reset_time=nil, on_health_change=nil)
35
35
  @endpoints = Hash.new
36
36
  @yellow_states = yellow_states || DEFAULT_YELLOW_STATES
@@ -39,11 +39,11 @@ module RightSupport::Net::Balancing
39
39
  @min_n_level = 0
40
40
  endpoints.each { |ep| @endpoints[ep] = {:n_level => @min_n_level, :timestamp => 0} }
41
41
  end
42
-
42
+
43
43
  def sweep
44
44
  @endpoints.each { |k,v| decrease_state(k, 0, Time.now) if Float(Time.now - v[:timestamp]) > @reset_time }
45
45
  end
46
-
46
+
47
47
  def sweep_and_return_yellow_and_green
48
48
  sweep
49
49
  @endpoints.select { |k,v| v[:n_level] < @yellow_states }
@@ -82,14 +82,15 @@ module RightSupport::Net::Balancing
82
82
  @endpoints.each { |k, v| stats[k] = state_color(v[:n_level]) }
83
83
  stats
84
84
  end
85
-
85
+
86
+ def update!(endpoints)
87
+ @endpoints.each { |k,v| endpoints.include?(k) ? endpoints.delete(k) : @endpoints.delete(k) }
88
+ endpoints.each { |ep| @endpoints[ep] = {:n_level => @min_n_level, :timestamp => 0} }
89
+ end
90
+
86
91
  end
87
-
88
- # Implementation concepts: endpoints have three states, red, yellow and green. Yellow
89
92
  # has several levels (@yellow_states) to determine the health of the endpoint. The
90
93
  # balancer works by avoiding "red" endpoints and retrying them after awhile. Here is a
91
- # brief description of the state transitions:
92
- # * green: last request was successful.
93
94
  # * on success: remain green
94
95
  # * on failure: change state to yellow and set it's health to healthiest (1)
95
96
  # * red: skip this server
@@ -109,16 +110,20 @@ module RightSupport::Net::Balancing
109
110
  # transitions from red to yellow, and so on.
110
111
 
111
112
  class HealthCheck
112
-
113
- def initialize(endpoints, options = {})
114
- yellow_states = options[:yellow_states]
115
- reset_time = options[:reset_time]
116
113
 
117
- @health_check = options.delete(:health_check)
114
+ def initialize(options = {})
115
+ @options = options
116
+ end
118
117
 
119
- @stack = EndpointsStack.new(endpoints, yellow_states, reset_time, options[:on_health_change])
120
- @counter = rand(0xffff) % endpoints.size
121
- @last_size = endpoints.size
118
+ def set_endpoints(endpoints)
119
+ if @stack
120
+ @stack.update!(endpoints)
121
+ else
122
+ @health_check = @options.delete(:health_check)
123
+ @counter = rand(0xffff) % endpoints.size
124
+ @last_size = endpoints.size
125
+ @stack = EndpointsStack.new(endpoints, @options[:yellow_states], @options[:reset_time], @options[:on_health_change])
126
+ end
122
127
  end
123
128
 
124
129
  def next
@@ -126,7 +131,7 @@ module RightSupport::Net::Balancing
126
131
  # following structure: [ [EP1, {:n_level => ..., :timestamp => ... }], [EP2, ... ] ]
127
132
  endpoints = @stack.sweep_and_return_yellow_and_green
128
133
  return nil if endpoints.empty?
129
-
134
+
130
135
  # Selection of the next endpoint using RoundRobin
131
136
  @counter += 1 unless endpoints.size < @last_size
132
137
  @counter = 0 if @counter == endpoints.size
@@ -151,7 +156,7 @@ module RightSupport::Net::Balancing
151
156
  def bad(endpoint, t0, t1)
152
157
  @stack.increase_state(endpoint, t0, t1)
153
158
  end
154
-
159
+
155
160
  def health_check(endpoint)
156
161
  t0 = Time.now
157
162
  result = @health_check.call(endpoint)
@@ -173,6 +178,6 @@ module RightSupport::Net::Balancing
173
178
  def get_stats
174
179
  @stack.get_stats
175
180
  end
176
-
181
+
177
182
  end
178
183
  end
@@ -22,14 +22,19 @@
22
22
 
23
23
  module RightSupport::Net::Balancing
24
24
  class RoundRobin
25
- def initialize(endpoints,options ={})
26
- @endpoints = endpoints
25
+
26
+ def initialize(options ={})
27
+ @endpoints = []
27
28
  @counter = rand(0xffff)
28
29
  end
29
30
 
31
+ def set_endpoints(endpoints)
32
+ @endpoints = endpoints
33
+ end
34
+
30
35
  def next
31
36
  @counter += 1
32
- [ @endpoints[@counter % @endpoints.size], false ]
37
+ [ @endpoints[@counter % @endpoints.size], false ] unless @endpoints.empty?
33
38
  end
34
39
 
35
40
  def good(endpoint, t0, t1)
@@ -0,0 +1,82 @@
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
+
25
+ # Implementation concepts: Create a policy that selects an endpoint and sticks with it.
26
+ #
27
+ # The policy should:
28
+ # - iterate through each endpoint until a valid endpoint is found;
29
+ # - continue returning the same endpoint until it is no longer valid;
30
+ # - re-iterate through each endpoint when it's endpoint loses validity;
31
+ # - return an Exception if it performs a complete iteration though each endpoint and finds none valid;
32
+
33
+ class StickyPolicy
34
+
35
+ def initialize(options = {})
36
+ @health_check = options.delete(:health_check)
37
+ @endpoints = []
38
+ @counter = rand(0xffff)
39
+ end
40
+
41
+ def set_endpoints(endpoints)
42
+ unless @endpoints.empty?
43
+ last_chosen = self.next.first
44
+ @endpoints = []
45
+ if endpoints.include?(last_chosen)
46
+ @endpoints << last_chosen
47
+ @counter = 0
48
+ end
49
+ end
50
+ @endpoints |= endpoints
51
+ end
52
+
53
+ def next
54
+ [ @endpoints[@counter % @endpoints.size], true ] unless @endpoints.empty?
55
+ end
56
+
57
+ def good(endpoint, t0, t1)
58
+ #no-op; round robin does not care about failures
59
+ end
60
+
61
+ def bad(endpoint, t0, t1)
62
+ @counter += 1
63
+ end
64
+
65
+ def health_check(endpoint)
66
+ t0 = Time.now
67
+ result = @health_check.call(endpoint)
68
+ t1 = Time.now
69
+ if result
70
+ return true
71
+ else
72
+ @counter += 1
73
+ return false
74
+ end
75
+ rescue Exception => e
76
+ t1 = Time.now
77
+ @counter += 1
78
+ raise e
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and the licensee.
10
+
11
+ require 'socket'
12
+
13
+ module RightSupport::Net
14
+
15
+ module DNS
16
+
17
+ def self.resolve_all_ip_addresses(hostnames)
18
+ ips = []
19
+ hostnames = [hostnames] unless hostnames.respond_to?(:each)
20
+ hostnames.each do |hostname|
21
+ infos = nil
22
+ begin
23
+ infos = Socket.getaddrinfo(hostname, 443, Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
24
+ rescue Exception => e
25
+ # NOTE: Need to figure out, which logger can we use here?
26
+ # Log.error "Rescued #{e.class.name} resolving Repose hostnames: #{e.message}; retrying"
27
+ retry
28
+ end
29
+
30
+ #Randomly permute the addrinfos of each hostname to help spread load.
31
+ infos.shuffle.each do |info|
32
+ ips << info[3]
33
+ end
34
+ end
35
+ ips
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+
@@ -2,7 +2,7 @@ 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.
@@ -74,6 +74,16 @@ module RightSupport::Net
74
74
  new(endpoints, options).request(&block)
75
75
  end
76
76
 
77
+ def resolve(endpoints)
78
+ endpoints = RightSupport::Net::DNS.resolve_all_ip_addresses(endpoints)
79
+ @resolved_at = Time.now.to_i
80
+ endpoints
81
+ end
82
+
83
+ def expired?
84
+ @options[:resolve] && Time.now.to_i - @resolved_at > @options[:resolve]
85
+ end
86
+
77
87
  # Constructor. Accepts a sequence of request endpoints which it shuffles randomly at
78
88
  # creation time; however, the ordering of the endpoints does not change thereafter
79
89
  # and the sequence is tried from the beginning for every request.
@@ -100,7 +110,8 @@ module RightSupport::Net
100
110
 
101
111
  @options[:policy] ||= RightSupport::Net::Balancing::RoundRobin
102
112
  @policy = @options[:policy]
103
- @policy = @policy.new(endpoints, options) if @policy.is_a?(Class)
113
+ @policy = @policy.new(options) if @policy.is_a?(Class)
114
+
104
115
  unless test_policy_duck_type(@policy)
105
116
  raise ArgumentError, ":policy must be a class/object that responds to :next, :good and :bad"
106
117
  end
@@ -126,6 +137,12 @@ module RightSupport::Net
126
137
  end
127
138
 
128
139
  @endpoints = endpoints
140
+
141
+ if @options[:resolve]
142
+ @resolved_at = 0
143
+ else
144
+ @policy.set_endpoints(@endpoints)
145
+ end
129
146
  end
130
147
 
131
148
  # Perform a request.
@@ -144,6 +161,8 @@ module RightSupport::Net
144
161
  def request
145
162
  raise ArgumentError, "Must call this method with a block" unless block_given?
146
163
 
164
+ @policy.set_endpoints(self.resolve(@endpoints)) if self.expired?
165
+
147
166
  exceptions = []
148
167
  result = nil
149
168
  complete = false
@@ -159,7 +178,6 @@ module RightSupport::Net
159
178
  end
160
179
 
161
180
  endpoint, need_health_check = @policy.next
162
-
163
181
  raise NoResult, "No endpoints are available" unless endpoint
164
182
  n += 1
165
183
  t0 = Time.now
@@ -0,0 +1,67 @@
1
+ module RightSupport::Net
2
+ module SSL
3
+ # A helper module that provides monkey patch for OpenSSL::SSL.verify_certificate_identity method.
4
+ #
5
+ # To start use it, all you need is to enable it and then register DNS name of the host, which
6
+ # you will trust for sure. This module automatically adds OpenSSL library and reopens
7
+ # verify_certificate_identity method, to change the SSL algorythm of hostname verification.
8
+ module OpenSSLPatch
9
+ class << self
10
+ @@status = false
11
+
12
+ def enable!
13
+ return if @@status
14
+ @@status = true
15
+
16
+ require 'openssl'
17
+ OpenSSL::SSL.module_exec do
18
+ def verify_certificate_identity(cert, hostname)
19
+ if RightSupport::Net::SSL::OpenSSLPatch.enabled?
20
+ actual_hostname = RightSupport::Net::SSL.expected_hostname
21
+ end
22
+ actual_hostname ||= hostname
23
+ verify_certificate_identity_without_hack(cert, actual_hostname)
24
+ end
25
+ module_function :verify_certificate_identity
26
+
27
+ # The original module function of OpenSSL::SSL
28
+ def verify_certificate_identity_without_hack(cert, hostname)
29
+ should_verify_common_name = true
30
+ cert.extensions.each{|ext|
31
+ next if ext.oid != "subjectAltName"
32
+ ext.value.split(/,\s+/).each{|general_name|
33
+ if /\ADNS:(.*)/ =~ general_name
34
+ should_verify_common_name = false
35
+ reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
36
+ return true if /\A#{reg}\z/i =~ hostname
37
+ elsif /\AIP Address:(.*)/ =~ general_name
38
+ should_verify_common_name = false
39
+ return true if $1 == hostname
40
+ end
41
+ }
42
+ }
43
+ if should_verify_common_name
44
+ cert.subject.to_a.each{|oid, value|
45
+ if oid == "CN"
46
+ reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
47
+ return true if /\A#{reg}\z/i =~ hostname
48
+ end
49
+ }
50
+ end
51
+ return false
52
+ end
53
+ module_function :verify_certificate_identity_without_hack
54
+ end
55
+ end
56
+
57
+ def disable!
58
+ @@status = false
59
+ end
60
+
61
+ def enabled?
62
+ @@status
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and the licensee.
10
+
11
+ require 'right_support/net/ssl/open_ssl_patch'
12
+
13
+ module RightSupport::Net
14
+ module SSL
15
+ module_function
16
+
17
+ def expected_hostname
18
+ @expected_hostname
19
+ end
20
+
21
+ def with_expected_hostname(hostname, &block)
22
+ @expected_hostname = hostname
23
+ block.call
24
+ rescue Exception => e
25
+ @expected_hostname = nil
26
+ raise e
27
+ ensure
28
+ @expected_hostname = nil
29
+ end
30
+ end
31
+ end
32
+
33
+ RightSupport::Net::SSL::OpenSSLPatch.enable!
@@ -34,5 +34,7 @@ require 'right_support/net/http_client'
34
34
  require 'right_support/net/string_encoder'
35
35
  require 'right_support/net/balancing'
36
36
  require 'right_support/net/request_balancer'
37
+ require 'right_support/net/ssl'
38
+ require 'right_support/net/dns'
37
39
 
38
- RightSupport::Net.extend(RightSupport::Net::AddressHelper)
40
+ RightSupport::Net.extend(RightSupport::Net::AddressHelper)
@@ -193,25 +193,43 @@ module RightSupport
193
193
  #
194
194
  # === Parameters
195
195
  # stats(Hash):: Statistics with generic keys "name", "identity", "hostname", "service uptime",
196
- # "machine uptime", "stat time", "last reset time", "version", and "broker" with the
197
- # latter two and "machine uptime" being optional; any other keys ending with "stats"
198
- # have an associated hash value that is displayed in sorted key order
196
+ # "machine uptime", "memory KB", "stat time", "last reset time", "version", and "broker" with
197
+ # the latter two and "machine uptime", "memory KB", "version", and "broker" being optional;
198
+ # any other keys ending with "stats" have an associated hash value that is displayed in sorted
199
+ # key order, unless "stats" is preceded by a non-blank, in which case that character is prepended
200
+ # to the key to drive the sort order
201
+ # options(Hash):: Formatting options
202
+ # :name_width(Integer):: Maximum characters in displayed stat name
203
+ # :sub_name_width(Integer):: Maximum characters in displayed sub-stat name
204
+ # :sub_stat_value_width(Integer):: Maximum characters in displayed sub-stat value line
205
+ # :exception_message_width(Integer):: Maximum characters displayed for exception message
199
206
  #
200
207
  # === Return
201
208
  # (String):: Display string
202
- def self.stats_str(stats)
203
- name_width = MAX_STAT_NAME_WIDTH
209
+ def self.stats_str(stats, options = {})
210
+ name_width = options[:name_width] || MAX_STAT_NAME_WIDTH
211
+
204
212
  str = stats["name"] ? sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "name", stats["name"]) : ""
205
213
  str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "identity", stats["identity"]) +
206
214
  sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "hostname", stats["hostname"]) +
207
215
  sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "stat time", time_at(stats["stat time"])) +
208
216
  sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "last reset", time_at(stats["last reset time"])) +
209
217
  sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "service up", elapsed(stats["service uptime"]))
210
- str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "machine up", elapsed(stats["machine uptime"])) if stats.has_key?("machine uptime")
211
- str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "memory KB", stats["memory"]) if stats.has_key?("memory")
212
- str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "version", stats["version"].to_i) if stats.has_key?("version")
213
- str += brokers_str(stats["brokers"], name_width) if stats.has_key?("brokers")
214
- stats.to_a.sort.each { |k, v| str += sub_stats_str(k[0..-7], v, name_width) if k.to_s =~ /stats$/ }
218
+ if stats.has_key?("machine uptime")
219
+ str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "machine up", elapsed(stats["machine uptime"]))
220
+ end
221
+ if stats.has_key?("memory")
222
+ str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "memory KB", stats["memory"])
223
+ end
224
+ if stats.has_key?("version")
225
+ str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "version", stats["version"].to_i)
226
+ end
227
+ if stats.has_key?("brokers")
228
+ str += brokers_str(stats["brokers"], options)
229
+ end
230
+ stats.to_a.sort_by { |(k, v)| k.to_s =~ /(.)stats$/ ? ($1 == ' ' ? '~' : $1) + k : k }.each do |k, v|
231
+ str += sub_stats_str(k[0..-7], v, options) if k.to_s =~ /stats$/
232
+ end
215
233
  str
216
234
  end
217
235
 
@@ -234,13 +252,20 @@ module RightSupport
234
252
  # "heartbeat"(Integer|nil):: Number of seconds between AMQP heartbeats, or nil if heartbeat disabled
235
253
  # "returns"(Hash|nil):: Message return activity stats with keys "total", "percent", "last", and "rate"
236
254
  # with percentage breakdown per request type, or nil if none
237
- # name_width(Integer):: Fixed width for left-justified name display
255
+ # options(Hash):: Formatting options
256
+ # :name_width(Integer):: Fixed width for left-justified name display
257
+ # :sub_name_width(Integer):: Maximum characters in displayed sub-stat name
258
+ # :sub_stat_value_width(Integer):: Maximum characters in displayed sub-stat value line
259
+ # :exception_message_width(Integer):: Maximum characters displayed for exception message
238
260
  #
239
261
  # === Return
240
262
  # str(String):: Broker display with one line per broker plus exceptions
241
- def self.brokers_str(brokers, name_width)
263
+ def self.brokers_str(brokers, options = {})
264
+ name_width = options[:name_width] || MAX_STAT_NAME_WIDTH
265
+ sub_name_width = options[:sub_name_width] || MAX_SUB_STAT_NAME_WIDTH
266
+ sub_stat_value_width = options[:sub_stat_value_width] || MAX_SUB_STAT_VALUE_WIDTH
267
+
242
268
  value_indent = " " * (name_width + SEPARATOR.size)
243
- sub_name_width = MAX_SUB_STAT_NAME_WIDTH
244
269
  sub_value_indent = " " * (name_width + sub_name_width + (SEPARATOR.size * 2))
245
270
  str = sprintf("%-#{name_width}s#{SEPARATOR}", "brokers")
246
271
  brokers["brokers"].each do |b|
@@ -263,7 +288,7 @@ module RightSupport
263
288
  str += if brokers["exceptions"].nil? || brokers["exceptions"].empty?
264
289
  "none\n"
265
290
  else
266
- exceptions_str(brokers["exceptions"], sub_value_indent) + "\n"
291
+ exceptions_str(brokers["exceptions"], sub_value_indent, options) + "\n"
267
292
  end
268
293
  str += value_indent
269
294
  str += sprintf("%-#{sub_name_width}s#{SEPARATOR}", "heartbeat")
@@ -277,7 +302,7 @@ module RightSupport
277
302
  str += if brokers["returns"].nil? || brokers["returns"].empty?
278
303
  "none\n"
279
304
  else
280
- wrap(activity_str(brokers["returns"]), MAX_SUB_STAT_VALUE_WIDTH, sub_value_indent, ", ") + "\n"
305
+ wrap(activity_str(brokers["returns"]), sub_stat_value_width, sub_value_indent, ", ") + "\n"
281
306
  end
282
307
  end
283
308
 
@@ -296,13 +321,20 @@ module RightSupport
296
321
  # === Parameters
297
322
  # name(String):: Display name for the stat
298
323
  # value(Object):: Value of this stat
299
- # name_width(Integer):: Fixed width for left-justified name display
324
+ # options(Hash):: Formatting options
325
+ # :name_width(Integer):: Fixed width for left-justified name display
326
+ # :sub_name_width(Integer):: Maximum characters in displayed sub-stat name
327
+ # :sub_stat_value_width(Integer):: Maximum characters in displayed sub-stat value line
328
+ # :exception_message_width(Integer):: Maximum characters displayed for exception message
300
329
  #
301
330
  # === Return
302
331
  # (String):: Single line display of stat
303
- def self.sub_stats_str(name, value, name_width)
332
+ def self.sub_stats_str(name, value, options = {})
333
+ name_width = options[:name_width] || MAX_STAT_NAME_WIDTH
334
+ sub_name_width = options[:sub_name_width] || MAX_SUB_STAT_NAME_WIDTH
335
+ sub_stat_value_width = options[:sub_stat_value_width] || MAX_SUB_STAT_VALUE_WIDTH
336
+
304
337
  value_indent = " " * (name_width + SEPARATOR.size)
305
- sub_name_width = MAX_SUB_STAT_NAME_WIDTH
306
338
  sub_value_indent = " " * (name_width + sub_name_width + (SEPARATOR.size * 2))
307
339
  sprintf("%-#{name_width}s#{SEPARATOR}", name) + value.to_a.sort.map do |attr|
308
340
  k, v = attr
@@ -317,13 +349,13 @@ module RightSupport
317
349
  if v.empty? || v["total"] == 0
318
350
  "none"
319
351
  elsif v["total"]
320
- wrap(activity_str(v), MAX_SUB_STAT_VALUE_WIDTH, sub_value_indent, ", ")
352
+ wrap(activity_str(v), sub_stat_value_width, sub_value_indent, ", ")
321
353
  elsif k =~ /last$/
322
354
  last_activity_str(v)
323
355
  elsif k == "exceptions"
324
- exceptions_str(v, sub_value_indent)
356
+ exceptions_str(v, sub_value_indent, options)
325
357
  else
326
- wrap(hash_str(v), MAX_SUB_STAT_VALUE_WIDTH, sub_value_indent, ", ")
358
+ wrap(hash_str(v), sub_stat_value_width, sub_value_indent, ", ")
327
359
  end
328
360
  else
329
361
  "#{v || "none"}"
@@ -394,16 +426,19 @@ module RightSupport
394
426
  # "total"(Integer):: Total exceptions for this category
395
427
  # "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
396
428
  # indent(String):: Indentation for each line
429
+ # options(Hash):: Formatting options
430
+ # :exception_message_width(Integer):: Maximum characters displayed for exception message
397
431
  #
398
432
  # === Return
399
433
  # (String):: Exceptions in displayable format with line separators
400
- def self.exceptions_str(exceptions, indent)
434
+ def self.exceptions_str(exceptions, indent, options = {})
435
+ exception_message_width = options[:exception_message_width] || MAX_EXCEPTION_MESSAGE_WIDTH
401
436
  indent2 = indent + (" " * 4)
402
437
  exceptions.to_a.sort.map do |k, v|
403
438
  sprintf("%s total: %d, most recent:\n", k, v["total"]) + v["recent"].reverse.map do |e|
404
439
  message = e["message"]
405
- if message && message.size > (MAX_EXCEPTION_MESSAGE_WIDTH - 3)
406
- message = e["message"][0, MAX_EXCEPTION_MESSAGE_WIDTH - 3] + "..."
440
+ if message && message.size > (exception_message_width - 3)
441
+ message = e["message"][0, exception_message_width - 3] + "..."
407
442
  end
408
443
  indent + "(#{e["count"]}) #{time_at(e["when"])} #{e["type"]}: #{message}\n" + indent2 + "#{e["where"]}"
409
444
  end.join("\n")
data/lib/right_support.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # Workaround for badly-coded gems such as active_support which fail to require this for themselves
2
+ require 'thread'
3
+
1
4
  require 'right_support/ruby'
2
5
  require 'right_support/crypto'
3
6
  require 'right_support/db'
@@ -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 = '1.2.0'
11
- s.date = '2012-01-23'
10
+ s.version = '1.3.0'
11
+ s.date = '2012-03-06'
12
12
 
13
13
  s.authors = ['Tony Spataro', 'Sergey Sergyenko', 'Ryan Williamson', 'Lee Kirchhoff']
14
14
  s.email = 'support@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: 31
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 1.2.0
10
+ version: 1.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -18,7 +18,7 @@ autorequire:
18
18
  bindir: bin
19
19
  cert_chain: []
20
20
 
21
- date: 2012-01-23 00:00:00 -08:00
21
+ date: 2012-03-06 00:00:00 -08:00
22
22
  default_executable:
23
23
  dependencies: []
24
24
 
@@ -51,8 +51,12 @@ files:
51
51
  - lib/right_support/net/balancing.rb
52
52
  - lib/right_support/net/balancing/health_check.rb
53
53
  - lib/right_support/net/balancing/round_robin.rb
54
+ - lib/right_support/net/balancing/sticky_policy.rb
55
+ - lib/right_support/net/dns.rb
54
56
  - lib/right_support/net/http_client.rb
55
57
  - lib/right_support/net/request_balancer.rb
58
+ - lib/right_support/net/ssl.rb
59
+ - lib/right_support/net/ssl/open_ssl_patch.rb
56
60
  - lib/right_support/net/string_encoder.rb
57
61
  - lib/right_support/rack.rb
58
62
  - lib/right_support/rack/custom_logger.rb