right_support 2.8.3 → 2.8.6

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/Gemfile CHANGED
@@ -11,6 +11,7 @@ group :optional do
11
11
  gem "simple_uuid", "~> 0.2", :require => nil
12
12
  gem "uuid", "~> 2.3", :require => nil
13
13
  gem "yajl-ruby", "~> 1.1"
14
+ gem "iconv"
14
15
  end
15
16
 
16
17
  # Gems used during test and development of RightSupport.
data/Gemfile.lock CHANGED
@@ -34,6 +34,7 @@ GEM
34
34
  gherkin (2.12.0)
35
35
  multi_json (~> 1.3)
36
36
  git (1.2.5)
37
+ iconv (1.0.4)
37
38
  jeweler (1.8.4)
38
39
  bundler (~> 1.0)
39
40
  git (>= 1.2.5)
@@ -97,6 +98,7 @@ PLATFORMS
97
98
  DEPENDENCIES
98
99
  addressable (~> 2.2.7)
99
100
  flexmock (~> 1.0)
101
+ iconv
100
102
  jeweler (~> 1.8.3)
101
103
  net-ssh (~> 2.0)
102
104
  nokogiri (~> 1.5)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.8.3
1
+ 2.8.6
@@ -31,6 +31,14 @@ Feature: JSON serialization
31
31
  When I serialize a complex random data structure
32
32
  Then the serialized value should round-trip cleanly
33
33
 
34
+ Scenario Outline: Ruby string with non UTF-8 characters
35
+ When I serialize the Ruby value: <ruby>
36
+ Then the serialized value should be: <json>
37
+
38
+ Examples:
39
+ | ruby | json |
40
+ | "\xC0\xBCscript>\xC0\xBC/script>" | "script>/script>" |
41
+
34
42
  Scenario Outline: object-escaped Symbol
35
43
  When I serialize the Ruby value: <ruby>
36
44
  Then the serialized value should be: <json>
@@ -145,8 +145,10 @@ module RightSupport::Data
145
145
  # @param [Object] object any Ruby object
146
146
  # @return [Object] JSONish representation of input object
147
147
  def object_to_jsonish(object)
148
+ iconv = Iconv.new('UTF-8//IGNORE', 'UTF-8')
148
149
  case object
149
150
  when String
151
+ object = iconv.iconv(object)
150
152
  if (object =~ /^:/ ) || (object =~ TIME_PATTERN)
151
153
  # Strings that look like a Symbol or Time must be object-escaped.
152
154
  {@marker => String.name,
@@ -44,6 +44,40 @@ if require_succeeds?('cassandra/0.8')
44
44
  end
45
45
  end
46
46
 
47
+ # Monkey patch get_range_single so that it can return a array of key slices
48
+ # rather than converting it to a Hash
49
+ def get_range_single(column_family, options = {})
50
+ return_empty_rows = options.delete(:return_empty_rows) || false
51
+ slices_please = options.delete(:slices_not_hash) || false
52
+
53
+ column_family, _, _, options =
54
+ extract_and_validate_params(column_family, "", [options],
55
+ READ_DEFAULTS.merge(:start_key => '',
56
+ :finish_key => '',
57
+ :key_count => 100,
58
+ :columns => nil,
59
+ :reversed => false
60
+ )
61
+ )
62
+
63
+ results = _get_range( column_family,
64
+ options[:start_key].to_s,
65
+ options[:finish_key].to_s,
66
+ options[:key_count],
67
+ options[:columns],
68
+ options[:start].to_s,
69
+ options[:finish].to_s,
70
+ options[:count],
71
+ options[:consistency],
72
+ options[:reversed] )
73
+
74
+ unless slices_please
75
+ multi_key_slices_to_hash(column_family, results, return_empty_rows)
76
+ else
77
+ results
78
+ end
79
+ end
80
+
47
81
  # Monkey patch get_indexed_slices so that it returns OrderedHash, otherwise cannot determine
48
82
  # next start key when getting in chunks
49
83
  def get_indexed_slices(column_family, index_clause, *columns_and_options)
@@ -124,6 +158,16 @@ module RightSupport::DB
124
158
 
125
159
  METHODS_TO_LOG = [:multi_get, :get, :get_indexed_slices, :get_columns, :insert, :remove, 'multi_get', 'get', 'get_indexed_slices', 'get_columns', 'insert', 'remove']
126
160
 
161
+ #This is required to be overwritten in order to set the read CL
162
+ def default_read_consistency
163
+ nil
164
+ end
165
+
166
+ #This is required to be overwritten in order to set the write CL
167
+ def default_write_consistency
168
+ nil
169
+ end
170
+
127
171
  # Deprecate usage of CassandraModel under Ruby < 1.9
128
172
  def inherited(base)
129
173
  raise UnsupportedRubyVersion, "Support only Ruby >= 1.9" unless RUBY_VERSION >= "1.9"
@@ -229,6 +273,23 @@ module RightSupport::DB
229
273
  @@current_keyspace = nil
230
274
  end
231
275
 
276
+ def get_connection(current=nil)
277
+ config = env_config
278
+ thrift_client_options = {:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT}
279
+ thrift_client_options = {
280
+ :timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT,
281
+ :server_retry_period => nil,
282
+ }
283
+
284
+ thrift_client_options.merge!({:protocol => Thrift::BinaryProtocolAccelerated}) if defined? Thrift::BinaryProtocolAccelerated
285
+
286
+ current ||= Cassandra.new(keyspace, config["server"], thrift_client_options)
287
+ current.disable_node_auto_discovery!
288
+ current.default_write_consistency = self.default_write_consistency if self.default_write_consistency
289
+ current.default_read_consistency = self.default_read_consistency if self.default_read_consistency
290
+ current
291
+ end
292
+
232
293
  # Client connected to Cassandra server
233
294
  # Create connection if does not already exist
234
295
  # Use BinaryProtocolAccelerated if it available
@@ -237,20 +298,7 @@ module RightSupport::DB
237
298
  # (Cassandra):: Client connected to server
238
299
  def conn()
239
300
  @@connections ||= {}
240
-
241
- config = env_config
242
-
243
- thrift_client_options = {
244
- :timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT,
245
- :server_retry_period => nil,
246
- }
247
-
248
- if defined? Thrift::BinaryProtocolAccelerated
249
- thrift_client_options.merge!({:protocol => Thrift::BinaryProtocolAccelerated})
250
- end
251
-
252
- @@connections[self.keyspace] ||= Cassandra.new(self.keyspace, config["server"], thrift_client_options)
253
- @@connections[self.keyspace].disable_node_auto_discovery!
301
+ @@connections[self.keyspace] = get_connection(@@connections[self.keyspace])
254
302
  @@connections[self.keyspace]
255
303
  end
256
304
 
@@ -371,48 +419,79 @@ module RightSupport::DB
371
419
  def stream_all_indexed_slices(index, key)
372
420
  expr = do_op(:create_idx_expr, index, key, "EQ")
373
421
 
374
- start_row = ''
375
- row_count = 10
376
- has_more_rows = true
422
+ start_row = ''
423
+ max_row_count = 100
424
+ max_initial_column_count = 1000 # number of columns to retrieve in the initial 2ndary index search
425
+ max_additional_column_count = 10000 # Number of columns to retrieve in a batch once we're targetting a single (long) row
377
426
 
427
+ # Loop over all CF rows, with batches of X
378
428
  while (start_row != nil)
379
- clause = do_op(:create_idx_clause, [expr], start_row, row_count)
380
-
381
- rows = self.conn.get_indexed_slices(column_family, clause, index,
382
- :key_count => row_count,
383
- :key_start => start_row)
384
-
385
- rows = rows.keys
386
- rows.shift unless start_row == ''
387
- start_row = rows.last
388
-
389
- rows.each do |row|
390
- start_column = ''
391
- column_count = 1_000
392
- has_more_columns = true
393
-
394
- while has_more_columns
395
- clause = do_op(:create_idx_clause, [expr], row, 1)
396
- chunk = self.conn.get_indexed_slices(column_family, clause, nil,
397
- :start => start_column,
398
- :count => column_count)
399
-
400
- # Get first row's columns, because where are getting only one row [see clause, for more details]
401
- key = chunk.keys.first
402
- columns = chunk[key]
403
-
404
- columns.shift unless start_column == ''
405
- yield(key, columns) unless chunk.empty?
406
-
407
- if columns.size >= column_count - 1
408
- #Assume there are more columns, use last column as start of next slice
409
- start_column = columns.last.column.name
410
- column_count = 1_001
411
- else
412
- has_more_columns = false
429
+ clause = do_op(:create_idx_clause, [expr], start_row, max_row_count)
430
+
431
+ # Now, for each batch of rows, make sure don't ask for "ALL" columns of each row, to avoid hitting rows with a huge amount of columns,
432
+ # which would cause large memory pressure here in the client, but more specially might cause long wait times and possible timeouts.
433
+ begin
434
+ rows = self.conn.get_indexed_slices(column_family, clause, :count => max_initial_column_count)
435
+ rescue Exception => e
436
+ wrapped_timeout = e.is_a?(CassandraThrift::TimedOutException)
437
+ unwrapped_timeout = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::TIMED_OUT)
438
+ unwrapped_disconnect = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::NOT_OPEN)
439
+
440
+ if (wrapped_timeout || unwrapped_timeout || unwrapped_disconnect) && (timeout_retries < retry_timeout)
441
+ timeout_retries += 1
442
+ retry
443
+ else
444
+ timeout_retries = 0
445
+ raise e
446
+ end
447
+ end
448
+
449
+ rows.each_pair do |row_key, columns|
450
+ # We already processed this row the previous iteration
451
+ next if row_key == start_row
452
+
453
+ yield(row_key, columns)
454
+
455
+ if columns.size >= max_initial_column_count
456
+ # Loop over all columns of the row (1000 at a time) starting at the last column name
457
+ last_column_name = columns.last.column.name
458
+ while( last_column_name != nil )
459
+ begin
460
+ # Retrieve a slice of this row excluding the first column
461
+ # as it's already been processed.
462
+ more_cols = self.conn.get_range(
463
+ column_family,
464
+ :start_key => row_key,
465
+ :finish_key => row_key,
466
+ :count => max_additional_column_count,
467
+ :start => last_column_name,
468
+ :slices_not_hash => true ).first.columns[1..-1]
469
+ rescue Exception => e
470
+ wrapped_timeout = e.is_a?(CassandraThrift::TimedOutException)
471
+ unwrapped_timeout = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::TIMED_OUT)
472
+ unwrapped_disconnect = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::NOT_OPEN)
473
+
474
+ if (wrapped_timeout || unwrapped_timeout || unwrapped_disconnect) && (timeout_retries < retry_timeout)
475
+ timeout_retries += 1
476
+ retry
477
+ else
478
+ timeout_retries = 0
479
+ raise e
480
+ end
481
+ end
482
+
483
+ yield(row_key, more_cols)
484
+ if more_cols.size < max_additional_column_count
485
+ last_column_name = nil
486
+ else
487
+ last_column_name = more_cols.last.column.name
488
+ end
413
489
  end
414
490
  end
415
491
  end
492
+
493
+ break if rows.size < max_row_count
494
+ start_row = rows.keys.last
416
495
  end
417
496
  end
418
497
 
@@ -573,17 +652,8 @@ module RightSupport::DB
573
652
  # === Return
574
653
  # true:: Always return true
575
654
  def reconnect
576
- config = env_config
577
-
578
655
  return false if keyspace.nil?
579
-
580
- thrift_client_options = {:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT}
581
- thrift_client_options.merge!({:protocol => Thrift::BinaryProtocolAccelerated})\
582
- if defined? Thrift::BinaryProtocolAccelerated
583
-
584
- connection = Cassandra.new(keyspace, config["server"], thrift_client_options)
585
- connection.disable_node_auto_discovery!
586
- @@connections[keyspace] = connection
656
+ @@connections[keyspace] = get_connection
587
657
  true
588
658
  end
589
659
 
@@ -20,17 +20,99 @@
20
20
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
+ require 'ipaddr'
23
24
  require 'socket'
24
25
  require 'uri'
25
26
 
26
27
  module RightSupport::Net
28
+
29
+ # IPAddr does not support returning a block of addresses in CIDR notation.
30
+ class IPNet < ::IPAddr
31
+ def cidr_block
32
+ # Ruby Math:: does not have a log2() function :(
33
+ cidr_block = 32 - (Math.log(self.to_range.count) / Math.log(2)).to_i
34
+ end
35
+
36
+ # Don't want to override to_s here since we also
37
+ # use this during URI transformation and need a way
38
+ # to get the raw dotted-quad address
39
+ #
40
+ def to_cidr
41
+ "#{self.to_s}/#{self.cidr_block}"
42
+ end
43
+ end
44
+
45
+ # A ResolvedEndpoint represents the resolution of a host which can contain
46
+ # multiple IP addresses or entire IP address ranges of varying size.
47
+ class ResolvedEndpoint
48
+ attr_accessor :uri
49
+
50
+ def initialize(ips, opts={})
51
+ @ip_addresses = []
52
+ @uri = opts[:uri]
53
+
54
+ ips.to_a.each do |address|
55
+ @ip_addresses << IPNet.new(address)
56
+ end
57
+
58
+ unless self.all_hosts? || @uri == nil
59
+ raise URI::InvalidURIError, "Cannot resolve URI with CIDR block bigger than a single host" unless self.all_hosts?
60
+ end
61
+ end
62
+
63
+ def addresses()
64
+ @ip_addresses.map do |addr|
65
+ if addr.to_range.count == 1 && @uri != nil
66
+ transformed_uri = uri.dup
67
+ transformed_uri.host = addr.to_s
68
+ transformed_uri.to_s
69
+ else
70
+ addr
71
+ end
72
+ end
73
+ end
74
+ alias addrs :addresses
75
+
76
+ def blocks()
77
+ @ip_addresses.map {|addr| addr.to_cidr}
78
+ end
79
+
80
+ def all_hosts?()
81
+ @ip_addresses.all? {|addr| addr.to_range.count == 1}
82
+ end
83
+
84
+ def ==(another_endpoint)
85
+ another_endpoint.addresses.all? {|addr| addresses.member? addr } && another_endpoint.uri == @uri
86
+ end
87
+ end
88
+
27
89
  module DNS
28
90
  DEFAULT_RESOLVE_OPTIONS = {
29
91
  :address_family => Socket::AF_INET,
30
92
  :socket_type => Socket::SOCK_STREAM,
31
93
  :protocol => Socket::IPPROTO_TCP,
32
- :retry => 3
33
- }
94
+ :retry => 3,
95
+ :uri => nil,
96
+ }.freeze
97
+
98
+ STATIC_HOSTNAME_TRANSLATIONS = {
99
+ # This list of CIDR blocks comes directly from Amazon and
100
+ # can be found here: https://forums.aws.amazon.com/ann.jspa?annID=2051
101
+ 'cf-mirror.rightscale.com' => [ '54.192.0.0/16',
102
+ '54.230.0.0/16',
103
+ '54.239.128.0/18',
104
+ '54.240.128.0/18',
105
+ '204.246.164.0/22',
106
+ '204.246.168.0/22',
107
+ '204.246.174.0/23',
108
+ '204.246.176.0/20',
109
+ '205.251.192.0/19',
110
+ '205.251.249.0/24',
111
+ '205.251.250.0/23',
112
+ '205.251.252.0/23',
113
+ '205.251.254.0/24',
114
+ '216.137.32.0/19' ],
115
+ }.freeze
34
116
 
35
117
  # Resolve a set of DNS hostnames to the individual IP addresses to which they map. Only handles
36
118
  # IPv4 addresses.
@@ -81,10 +163,37 @@ module RightSupport::Net
81
163
  # @raise URI::InvalidURIError if endpoints contains an invalid or URI
82
164
  # @raise SocketError if endpoints contains an invalid or unresolvable hostname
83
165
  def self.resolve(endpoints, opts={})
84
- resolved_hostnames = resolve_with_hostnames(endpoints, opts)
166
+ opts = DEFAULT_RESOLVE_OPTIONS.merge(opts)
167
+ endpoints = Array(endpoints)
168
+
169
+ retries = 0
85
170
  resolved_endpoints = []
86
- resolved_hostnames.each_value{ |v| resolved_endpoints.concat(v) }
87
- return resolved_endpoints
171
+
172
+ endpoints.each do |endpoint|
173
+ begin
174
+ resolved_endpoint = nil
175
+ if endpoint.include?(':')
176
+ # It contains a colon, therefore it must be a URI -- we don't support IPv6
177
+ uri = URI.parse(endpoint)
178
+ hostname = uri.host
179
+ raise URI::InvalidURIError, "Could not parse host component of URI" unless hostname
180
+
181
+ resolved_endpoint = resolve_endpoint(hostname, opts.merge(:uri=>uri))
182
+ else
183
+ resolved_endpoint = resolve_endpoint(endpoint, opts)
184
+ end
185
+ resolved_endpoints << resolved_endpoint
186
+ rescue SocketError => e
187
+ retries += 1
188
+ if retries < opts[:retry]
189
+ retry
190
+ else
191
+ raise
192
+ end
193
+ end
194
+ end
195
+
196
+ resolved_endpoints
88
197
  end
89
198
 
90
199
  # Similar to resolve, but return a hash of { hostnames => [endpoints] }
@@ -113,52 +222,44 @@ module RightSupport::Net
113
222
  # @raise URI::InvalidURIError if endpoints contains an invalid or URI
114
223
  # @raise SocketError if endpoints contains an invalid or unresolvable hostname
115
224
  def self.resolve_with_hostnames(endpoints, opts={})
116
- opts = DEFAULT_RESOLVE_OPTIONS.merge(opts)
117
- endpoints = [endpoints] unless endpoints.respond_to?(:each)
118
-
119
225
  hostname_hash = {}
120
- retries = 0
121
-
122
- endpoints.each do |endpoint|
123
- begin
124
- resolved_endpoints = []
125
- if endpoint.include?(':')
126
- # It contains a colon, therefore it must be a URI -- we don't support IPv6
127
- uri = URI.parse(endpoint)
128
- hostname = uri.host
129
- raise URI::InvalidURIError, "Could not parse host component of URI" unless hostname
226
+ endpoints.each {|endpoint| hostname_hash[endpoint] = resolve(endpoint, opts).first}
227
+ hostname_hash
228
+ end
130
229
 
131
- infos = Socket.getaddrinfo(hostname, nil,
132
- opts[:address_family], opts[:socket_type], opts[:protocol])
230
+ private
133
231
 
134
- infos.each do |info|
135
- transformed_uri = uri.dup
136
- transformed_uri.host = info[3]
137
- resolved_endpoints << transformed_uri.to_s
138
- end
139
- else
140
- # No colon; it's a hostname or IP address
141
- infos = Socket.getaddrinfo(endpoint, nil,
142
- opts[:address_family], opts[:socket_type], opts[:protocol])
143
-
144
- infos.each do |info|
145
- resolved_endpoints << info[3]
146
- end
147
- end
148
- hostname_hash[endpoint.to_s] = resolved_endpoints
149
- rescue SocketError => e
150
- retries += 1
151
- if retries < opts[:retry]
152
- retry
153
- else
154
- raise
155
- end
156
- end
232
+ # Lookup the address(es) associated with the given endpoints.
233
+ #
234
+ # Perform an address lookup in this order:
235
+ # 1. Hardcoded translation table.
236
+ # 2. System address lookup (e.g. DNS, hosts files...etc)
237
+ #
238
+ # Although this method does accept IPv4 dotted-quad addresses as input, it does not accept
239
+ # IPv6 addresses. However, given hostnames as input, one _can_ resolve the hostnames
240
+ # to IPv6 addresses by specifying the appropriate address_family in the options.
241
+ #
242
+ # It should never be necessary to specify a different :socket_type or :protocol, but these
243
+ # options are exposed just in case.
244
+ #
245
+ # @param [String] endpoint as a hostname or IPv4 address
246
+ # @option opts [Integer] :retry number of times to retry SocketError; default is 3
247
+ # @option opts [Integer] :address_family what kind of IP addresses to resolve; default is Socket::AF_INET (IPv4)
248
+ # @option opts [Integer] :socket_type socket-type context to pass to getaddrinfo, default is Socket::SOCK_STREAM
249
+ # @option opts [Integer] :protocol protocol context to pass to getaddrinfo, default is Socket::IPPROTO_TCP
250
+ #
251
+ # @return [Array<String>] List of resolved IPv4/IPv6 addresses.
252
+ #
253
+ # @raise SocketError if endpoints contains an invalid or unresolvable hostname
254
+ def self.resolve_endpoint(endpoint, opts=DEFAULT_RESOLVE_OPTIONS)
255
+ if STATIC_HOSTNAME_TRANSLATIONS.has_key?(endpoint)
256
+ ResolvedEndpoint.new(STATIC_HOSTNAME_TRANSLATIONS[endpoint], opts)
257
+ else
258
+ infos = Socket.getaddrinfo(endpoint, nil,
259
+ opts[:address_family], opts[:socket_type], opts[:protocol])
260
+ ResolvedEndpoint.new(infos.map { |info| info[3] }, opts)
157
261
  end
158
-
159
- hostname_hash
160
262
  end
161
-
162
263
  end
163
264
  end
164
265
 
@@ -151,6 +151,10 @@ module RightSupport::Net
151
151
  request(:delete, *args)
152
152
  end
153
153
 
154
+ def patch(*args)
155
+ request(:patch, *args)
156
+ end
157
+
154
158
  # A very thin wrapper around RestClient::Request.execute.
155
159
  #
156
160
  # === Parameters
@@ -204,7 +204,7 @@ module RightSupport::Net
204
204
  # === Return
205
205
  # Return the first hostname that resolved to the IP (there should only ever be one)
206
206
  def lookup_hostname(endpoint)
207
- @resolved_hostnames.select{ |k,v| v.include?(endpoint) }.shift[0]
207
+ @resolved_hostnames.select{ |k,v| v.addresses.include?(endpoint) }.shift[0]
208
208
  end
209
209
 
210
210
  # Perform a request.
@@ -369,7 +369,7 @@ module RightSupport::Net
369
369
  def resolve
370
370
  @resolved_hostnames = RightSupport::Net::DNS.resolve_with_hostnames(@endpoints)
371
371
  resolved_endpoints = []
372
- @resolved_hostnames.each_value{ |v| resolved_endpoints.concat(v) }
372
+ @resolved_hostnames.each_value{ |v| resolved_endpoints.concat(v.addrs.map {|addr| addr.to_s}) }
373
373
  logger.info("RequestBalancer: resolved #{@endpoints.inspect} to #{resolved_endpoints.inspect}") if resolved_endpoints != @ips
374
374
  @ips = resolved_endpoints
375
375
  @policy.set_endpoints(@ips)
@@ -4,14 +4,14 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{right_support}
8
- s.version = "2.8.3"
7
+ s.name = "right_support"
8
+ s.version = "2.8.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tony Spataro", "Sergey Sergyenko", "Ryan Williamson", "Lee Kirchhoff", "Alexey Karpik", "Scott Messier"]
12
- s.date = %q{2013-10-03}
13
- s.description = %q{A toolkit of useful, reusable foundation code created by RightScale.}
14
- s.email = %q{support@rightscale.com}
12
+ s.date = "2013-12-18"
13
+ s.description = "A toolkit of useful, reusable foundation code created by RightScale."
14
+ s.email = "support@rightscale.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
17
17
  "README.rdoc"
@@ -136,14 +136,13 @@ Gem::Specification.new do |s|
136
136
  "spec/validation/openssl_spec.rb",
137
137
  "spec/validation/ssh_spec.rb"
138
138
  ]
139
- s.homepage = %q{https://github.com/rightscale/right_support}
139
+ s.homepage = "https://github.com/rightscale/right_support"
140
140
  s.licenses = ["MIT"]
141
141
  s.require_paths = ["lib"]
142
- s.rubygems_version = %q{1.3.7}
143
- s.summary = %q{Reusable foundation code.}
142
+ s.rubygems_version = "1.8.15"
143
+ s.summary = "Reusable foundation code."
144
144
 
145
145
  if s.respond_to? :specification_version then
146
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
147
146
  s.specification_version = 3
148
147
 
149
148
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -58,14 +58,14 @@ describe RightSupport::DB::CassandraModel do
58
58
  ,'ring1, ring2, ring3' => ['ring1', 'ring2', 'ring3'] \
59
59
  ,['ring1', 'ring2', 'ring3'] => ['ring1', 'ring2', 'ring3']
60
60
  }.each do |config_string, congig_test|
61
- it "shoudl successfully intialize from #{config_string.inspect}" do
61
+ it "should successfully intialize from #{config_string.inspect}" do
62
62
 
63
63
 
64
64
  old_rack_env = ENV['RACK_ENV']
65
65
  begin
66
66
  ENV['RACK_ENV'] = env
67
67
 
68
- flexmock(Cassandra).should_receive(:new).with(/#{default_keyspace}_#{env}/, congig_test , {:timeout=>10}).and_return(default_keyspace_connection)
68
+ flexmock(Cassandra).should_receive(:new).with(/#{default_keyspace}_#{env}/, congig_test , {:timeout=>10, :server_retry_period=>nil}).and_return(default_keyspace_connection)
69
69
  default_keyspace_connection.should_receive(:disable_node_auto_discovery!).and_return(true)
70
70
 
71
71
 
@@ -81,4 +81,4 @@ describe RightSupport::DB::CassandraModel do
81
81
  end
82
82
  end
83
83
  end
84
- end
84
+ end
data/spec/net/dns_spec.rb CHANGED
@@ -22,7 +22,7 @@ describe RightSupport::Net::DNS do
22
22
  context :resolve do
23
23
  context 'given default :retry => 3' do
24
24
  let(:endpoint) { 'www.example.com' }
25
- let(:output) { ['1.1.1.1', '2.2.2.2'] }
25
+ let(:output) { [RightSupport::Net::ResolvedEndpoint.new(['1.1.1.1', '2.2.2.2'])] }
26
26
 
27
27
  it 'retries SocketError' do
28
28
  mock_getaddrinfo('www.example.com', SocketError)
@@ -55,7 +55,7 @@ describe RightSupport::Net::DNS do
55
55
  context 'given various endpoint formats' do
56
56
  context 'e.g. a DNS hostname' do
57
57
  let(:endpoint) { 'www.example.com' }
58
- let(:output) { ['1.1.1.1', '2.2.2.2'] }
58
+ let(:output) { [RightSupport::Net::ResolvedEndpoint.new(['1.1.1.1', '2.2.2.2'])] }
59
59
 
60
60
  it 'resolves to IP addresses' do
61
61
  mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
@@ -65,7 +65,7 @@ describe RightSupport::Net::DNS do
65
65
 
66
66
  context 'e.g. an IPv4 address' do
67
67
  let(:endpoint) { '127.0.0.1' }
68
- let(:output) { ['127.0.0.1'] }
68
+ let(:output) { [RightSupport::Net::ResolvedEndpoint.new(['127.0.0.1'])] }
69
69
 
70
70
  it 'resolves to the same address' do
71
71
  mock_getaddrinfo('127.0.0.1', ['127.0.0.1'])
@@ -79,7 +79,7 @@ describe RightSupport::Net::DNS do
79
79
 
80
80
  it 'resolves to URLs with addresses substituted' do
81
81
  mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
82
- subject.resolve(endpoint).should == output
82
+ subject.resolve(endpoint).first.addrs.should == output
83
83
  end
84
84
 
85
85
  context 'with a path component' do
@@ -88,7 +88,7 @@ describe RightSupport::Net::DNS do
88
88
 
89
89
  it 'resolves to URLs with path component preserved' do
90
90
  mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
91
- subject.resolve(endpoint).should == output
91
+ subject.resolve(endpoint).first.addrs.should == output
92
92
  end
93
93
  end
94
94
  end
@@ -111,13 +111,13 @@ describe RightSupport::Net::DNS do
111
111
 
112
112
  context 'e.g. several hostnames' do
113
113
  let(:endpoints) { ['www.example.com', 'www.example.net'] }
114
- let(:output) { ['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4'] }
114
+ let(:output) { [RightSupport::Net::ResolvedEndpoint.new(['1.1.1.1', '2.2.2.2']), RightSupport::Net::ResolvedEndpoint.new(['3.3.3.3', '4.4.4.4'])] }
115
115
 
116
116
  it 'resolves to IP addresses' do
117
117
  mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
118
118
  mock_getaddrinfo('www.example.net', ['3.3.3.3', '4.4.4.4'])
119
119
 
120
- subject.resolve(endpoints).sort.should == output
120
+ subject.resolve(endpoints).should == output
121
121
  end
122
122
  end
123
123
 
@@ -129,7 +129,42 @@ describe RightSupport::Net::DNS do
129
129
  mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
130
130
  mock_getaddrinfo('www.example.net', ['3.3.3.3', '4.4.4.4'])
131
131
 
132
- subject.resolve(endpoints).sort.should == output.sort
132
+ subject.resolve(endpoints).map {|ep| ep.addrs}.flatten == output
133
+ end
134
+ end
135
+ end
136
+
137
+ context 'requesting CIDR blocks' do
138
+ context 'DNS resolvable addresses' do
139
+ let(:endpoint) { 'www.example.com' }
140
+ let(:output) { ['1.1.1.1/32', '2.2.2.2/32'] }
141
+
142
+ it 'resolves hostname to CIDR /32 blocks' do
143
+ mock_getaddrinfo('www.example.com', ['1.1.1.1', '2.2.2.2'])
144
+ subject.resolve(endpoint).first.blocks.should == output
145
+ end
146
+
147
+ it 'resolves IP addresses to CIDR /32 blocks' do
148
+ mock_getaddrinfo('1.1.1.1', ['1.1.1.1'])
149
+ mock_getaddrinfo('2.2.2.2', ['2.2.2.2'])
150
+ subject.resolve(['1.1.1.1', '2.2.2.2']).map {|endpoint| endpoint.blocks}.flatten.should =~ output
151
+ end
152
+
153
+ it 'refuses to resolve a URI having a CIDR block < /32' do
154
+ lambda do
155
+ subject.resolve("http://cf-mirror.rightscale.com")
156
+ end.should raise_error(URI::InvalidURIError)
157
+ end
158
+ end
159
+
160
+ context 'Static CIDR blocks' do
161
+ let(:endpoint) { 'cf-mirror.rightscale.com' }
162
+
163
+ it 'resolves hostname to static CIDR blocks' do
164
+ subject.resolve(endpoint).first.blocks.each do |addr|
165
+ # Addresses should all be in CIDR form and not single /32 addresses
166
+ addr.should =~ /^\d+(?:\.\d+){3}\/(?:#{1.upto(31).to_a.join('|')})$/
167
+ end
133
168
  end
134
169
  end
135
170
  end
@@ -138,8 +173,8 @@ describe RightSupport::Net::DNS do
138
173
  context :resolve_with_hostnames do
139
174
  context 'given common inputs' do
140
175
  let(:endpoints) { ['www.example1.com','www.example2.com'] }
141
- let(:output) { {'www.example1.com'=>['1.1.1.1', '2.2.2.2'],
142
- 'www.example2.com'=>['3.3.3.3', '4.4.4.4']} }
176
+ let(:output) { {'www.example1.com'=>RightSupport::Net::ResolvedEndpoint.new(['1.1.1.1', '2.2.2.2']),
177
+ 'www.example2.com'=>RightSupport::Net::ResolvedEndpoint.new(['3.3.3.3', '4.4.4.4'])} }
143
178
 
144
179
  it 'resolves to IP addresses' do
145
180
  mock_getaddrinfo('www.example1.com', ['1.1.1.1', '2.2.2.2'])
@@ -91,6 +91,10 @@ describe RightSupport::Net::RequestBalancer do
91
91
  @health_checks.should == expect * (yellow_states - 1)
92
92
  end
93
93
 
94
+ def make_endpoint(addresses)
95
+ RightSupport::Net::ResolvedEndpoint.new(addresses)
96
+ end
97
+
94
98
  context :initialize do
95
99
  it 'requires a list of endpoint URLs' do
96
100
  lambda do
@@ -267,7 +271,7 @@ describe RightSupport::Net::RequestBalancer do
267
271
  context 'with :resolve option' do
268
272
  before(:each) do
269
273
  flexmock(RightSupport::Net::DNS).should_receive(:resolve_with_hostnames).
270
- with(['host1', 'host2']).and_return({'host1' => ['1.1.1.1', '2.2.2.2'], 'host2' => ['3.3.3.3']})
274
+ with(['host1', 'host2']).and_return({'host1' => make_endpoint(['1.1.1.1', '2.2.2.2']), 'host2' => make_endpoint(['3.3.3.3'])})
271
275
  @balancer = RightSupport::Net::RequestBalancer.new(['host1', 'host2'], :resolve => 15)
272
276
  end
273
277
 
@@ -482,10 +486,10 @@ describe RightSupport::Net::RequestBalancer do
482
486
  context 'with :resolve option' do
483
487
  before(:each) do
484
488
  @endpoints = ['host1', 'host2', 'host3', 'host4']
485
- @resolved_set_1 = {'host1' => ['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4']}
486
- @resolved_set_2 = {'host1'=>['5.5.5.5'],'host2'=>['6.6.6.6'],'host3'=>['7.7.7.7'],'host4'=>['8.8.8.8']}
489
+ @resolved_set_1 = {'host1' => make_endpoint(['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4'])}
490
+ @resolved_set_2 = {'host1'=> make_endpoint(['5.5.5.5']),'host2'=>make_endpoint(['6.6.6.6']),'host3'=>make_endpoint(['7.7.7.7']),'host4'=>make_endpoint(['8.8.8.8'])}
487
491
  @resolved_set_2_array = []
488
- @resolved_set_2.each_value{ |v| @resolved_set_2_array.concat(v) }
492
+ @resolved_set_2.each_value{ |v| @resolved_set_2_array.concat(v.addrs) }
489
493
  @dns = flexmock(RightSupport::Net::DNS)
490
494
  end
491
495
 
@@ -495,7 +499,7 @@ describe RightSupport::Net::RequestBalancer do
495
499
 
496
500
  @rb.request { true }
497
501
  @policy = @rb.instance_variable_get("@policy")
498
- @resolved_set_1['host1'].include?(@policy.next.first).should be_true
502
+ @resolved_set_1['host1'].addrs.include?(@policy.next.first).should be_true
499
503
  end
500
504
 
501
505
  it 're-resolves list of ip addresses if TTL is expired' do
@@ -504,7 +508,7 @@ describe RightSupport::Net::RequestBalancer do
504
508
 
505
509
  @rb.request { true }
506
510
  @policy = @rb.instance_variable_get("@policy")
507
- @resolved_set_1['host1'].include?(@policy.next.first).should be_true
511
+ @resolved_set_1['host1'].addrs.include?(@policy.next.first).should be_true
508
512
 
509
513
  @rb.instance_variable_set("@resolved_at", Time.now.to_i - 16)
510
514
  @rb.request { true }
@@ -431,7 +431,7 @@ describe RightSupport::Stats do
431
431
  "/opt/rightscale/right_link/common/lib/common/serializer.rb:133:in `cascade_serializers'"}]}}
432
432
  RightSupport::Stats.exceptions_str(exceptions, "").should ==
433
433
  "receive total: 2, most recent:\n" +
434
- "(2) Sun Aug 11 15:26:19 RightScale::Serializer::SerializationError: Could not \n" +
434
+ "(2) #{Time.at(exceptions['receive']['recent'].first['when']).strftime("%a %b %d %H:%M:%S")} RightScale::Serializer::SerializationError: Could not \n" +
435
435
  " load packet using [RightScale::SecureSerializer] (Failed to load with \n" +
436
436
  " RightScale::SecureSerializer (TypeError: can't convert nil into String in /opt/\n" +
437
437
  " rightscale/right_link/common/lib/common/security/signature.rb:56:in \n" +
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
5
- prerelease: false
4
+ hash: 35
5
+ prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 8
9
- - 3
10
- version: 2.8.3
9
+ - 6
10
+ version: 2.8.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -20,8 +20,7 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2013-10-21 00:00:00 -07:00
24
- default_executable:
23
+ date: 2013-12-19 00:00:00 Z
25
24
  dependencies:
26
25
  - !ruby/object:Gem::Dependency
27
26
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -34,10 +33,10 @@ dependencies:
34
33
  - 0
35
34
  - 9
36
35
  version: "0.9"
37
- requirement: *id001
38
36
  type: :development
39
- name: rake
37
+ requirement: *id001
40
38
  prerelease: false
39
+ name: rake
41
40
  - !ruby/object:Gem::Dependency
42
41
  version_requirements: &id002 !ruby/object:Gem::Requirement
43
42
  none: false
@@ -50,10 +49,10 @@ dependencies:
50
49
  - 8
51
50
  - 3
52
51
  version: 1.8.3
53
- requirement: *id002
54
52
  type: :development
55
- name: jeweler
53
+ requirement: *id002
56
54
  prerelease: false
55
+ name: jeweler
57
56
  - !ruby/object:Gem::Dependency
58
57
  version_requirements: &id003 !ruby/object:Gem::Requirement
59
58
  none: false
@@ -65,10 +64,10 @@ dependencies:
65
64
  - 1
66
65
  - 0
67
66
  version: "1.0"
68
- requirement: *id003
69
67
  type: :development
70
- name: right_develop
68
+ requirement: *id003
71
69
  prerelease: false
70
+ name: right_develop
72
71
  - !ruby/object:Gem::Dependency
73
72
  version_requirements: &id004 !ruby/object:Gem::Requirement
74
73
  none: false
@@ -80,10 +79,10 @@ dependencies:
80
79
  - 0
81
80
  - 10
82
81
  version: "0.10"
83
- requirement: *id004
84
82
  type: :development
85
- name: ruby-debug
83
+ requirement: *id004
86
84
  prerelease: false
85
+ name: ruby-debug
87
86
  - !ruby/object:Gem::Dependency
88
87
  version_requirements: &id005 !ruby/object:Gem::Requirement
89
88
  none: false
@@ -96,10 +95,10 @@ dependencies:
96
95
  - 11
97
96
  - 6
98
97
  version: 0.11.6
99
- requirement: *id005
100
98
  type: :development
101
- name: ruby-debug19
99
+ requirement: *id005
102
100
  prerelease: false
101
+ name: ruby-debug19
103
102
  - !ruby/object:Gem::Dependency
104
103
  version_requirements: &id006 !ruby/object:Gem::Requirement
105
104
  none: false
@@ -112,10 +111,10 @@ dependencies:
112
111
  - 4
113
112
  - 2
114
113
  version: 2.4.2
115
- requirement: *id006
116
114
  type: :development
117
- name: rdoc
115
+ requirement: *id006
118
116
  prerelease: false
117
+ name: rdoc
119
118
  - !ruby/object:Gem::Dependency
120
119
  version_requirements: &id007 !ruby/object:Gem::Requirement
121
120
  none: false
@@ -127,10 +126,10 @@ dependencies:
127
126
  - 1
128
127
  - 0
129
128
  version: "1.0"
130
- requirement: *id007
131
129
  type: :development
132
- name: flexmock
130
+ requirement: *id007
133
131
  prerelease: false
132
+ name: flexmock
134
133
  - !ruby/object:Gem::Dependency
135
134
  version_requirements: &id008 !ruby/object:Gem::Requirement
136
135
  none: false
@@ -143,10 +142,10 @@ dependencies:
143
142
  - 0
144
143
  - 0
145
144
  version: 1.0.0
146
- requirement: *id008
147
145
  type: :development
148
- name: syntax
146
+ requirement: *id008
149
147
  prerelease: false
148
+ name: syntax
150
149
  - !ruby/object:Gem::Dependency
151
150
  version_requirements: &id009 !ruby/object:Gem::Requirement
152
151
  none: false
@@ -158,10 +157,10 @@ dependencies:
158
157
  - 1
159
158
  - 5
160
159
  version: "1.5"
161
- requirement: *id009
162
160
  type: :development
163
- name: nokogiri
161
+ requirement: *id009
164
162
  prerelease: false
163
+ name: nokogiri
165
164
  description: A toolkit of useful, reusable foundation code created by RightScale.
166
165
  email: support@rightscale.com
167
166
  executables: []
@@ -290,7 +289,6 @@ files:
290
289
  - spec/stats/helpers_spec.rb
291
290
  - spec/validation/openssl_spec.rb
292
291
  - spec/validation/ssh_spec.rb
293
- has_rdoc: true
294
292
  homepage: https://github.com/rightscale/right_support
295
293
  licenses:
296
294
  - MIT
@@ -320,7 +318,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
320
318
  requirements: []
321
319
 
322
320
  rubyforge_project:
323
- rubygems_version: 1.3.7
321
+ rubygems_version: 1.8.15
324
322
  signing_key:
325
323
  specification_version: 3
326
324
  summary: Reusable foundation code.