right_support 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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