cassandra-driver 1.0.0.beta.3-java → 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -14
  3. data/lib/cassandra.rb +164 -78
  4. data/lib/cassandra/address_resolution.rb +36 -0
  5. data/lib/cassandra/address_resolution/policies.rb +2 -0
  6. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
  7. data/lib/cassandra/address_resolution/policies/none.rb +35 -0
  8. data/lib/cassandra/auth.rb +1 -1
  9. data/lib/cassandra/auth/providers/password.rb +1 -1
  10. data/lib/cassandra/cluster.rb +18 -5
  11. data/lib/cassandra/cluster/client.rb +175 -101
  12. data/lib/cassandra/{client/connection_manager.rb → cluster/connection_pool.rb} +5 -5
  13. data/lib/cassandra/cluster/connector.rb +142 -56
  14. data/lib/cassandra/cluster/control_connection.rb +385 -134
  15. data/lib/cassandra/{client/null_logger.rb → cluster/failed_connection.rb} +12 -14
  16. data/lib/cassandra/cluster/options.rb +13 -2
  17. data/lib/cassandra/cluster/registry.rb +19 -9
  18. data/lib/cassandra/column.rb +5 -0
  19. data/lib/cassandra/compression.rb +1 -1
  20. data/lib/cassandra/compression/compressors/lz4.rb +1 -1
  21. data/lib/cassandra/compression/compressors/snappy.rb +1 -1
  22. data/lib/cassandra/driver.rb +29 -21
  23. data/lib/cassandra/errors.rb +325 -35
  24. data/lib/cassandra/execution/options.rb +13 -6
  25. data/lib/cassandra/execution/trace.rb +4 -4
  26. data/lib/cassandra/future.rb +7 -3
  27. data/lib/cassandra/keyspace.rb +5 -0
  28. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +13 -4
  29. data/lib/cassandra/load_balancing/policies/token_aware.rb +2 -4
  30. data/lib/cassandra/load_balancing/policies/white_list.rb +3 -6
  31. data/lib/cassandra/null_logger.rb +35 -0
  32. data/lib/cassandra/protocol.rb +0 -16
  33. data/lib/cassandra/protocol/cql_byte_buffer.rb +18 -18
  34. data/lib/cassandra/protocol/cql_protocol_handler.rb +78 -8
  35. data/lib/cassandra/protocol/frame_decoder.rb +2 -2
  36. data/lib/cassandra/protocol/frame_encoder.rb +1 -1
  37. data/lib/cassandra/protocol/requests/query_request.rb +1 -11
  38. data/lib/cassandra/protocol/response.rb +1 -1
  39. data/lib/cassandra/protocol/responses/detailed_error_response.rb +16 -1
  40. data/lib/cassandra/protocol/responses/error_response.rb +17 -0
  41. data/lib/cassandra/protocol/responses/event_response.rb +1 -1
  42. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +1 -1
  43. data/lib/cassandra/protocol/responses/result_response.rb +1 -1
  44. data/lib/cassandra/protocol/responses/rows_result_response.rb +1 -1
  45. data/lib/cassandra/protocol/type_converter.rb +4 -3
  46. data/lib/cassandra/reconnection.rb +1 -1
  47. data/lib/cassandra/result.rb +4 -6
  48. data/lib/cassandra/retry.rb +3 -5
  49. data/lib/cassandra/session.rb +14 -5
  50. data/lib/cassandra/statements/prepared.rb +5 -1
  51. data/lib/cassandra/table.rb +6 -1
  52. data/lib/cassandra/time_uuid.rb +21 -83
  53. data/lib/cassandra/util.rb +131 -1
  54. data/lib/cassandra/uuid.rb +6 -4
  55. data/lib/cassandra/uuid/generator.rb +207 -0
  56. data/lib/cassandra/version.rb +1 -1
  57. data/lib/cassandra_murmur3.jar +0 -0
  58. metadata +43 -49
  59. data/lib/cassandra/client.rb +0 -144
  60. data/lib/cassandra/client/batch.rb +0 -212
  61. data/lib/cassandra/client/client.rb +0 -591
  62. data/lib/cassandra/client/column_metadata.rb +0 -54
  63. data/lib/cassandra/client/connector.rb +0 -277
  64. data/lib/cassandra/client/execute_options_decoder.rb +0 -59
  65. data/lib/cassandra/client/peer_discovery.rb +0 -50
  66. data/lib/cassandra/client/prepared_statement.rb +0 -314
  67. data/lib/cassandra/client/query_result.rb +0 -230
  68. data/lib/cassandra/client/request_runner.rb +0 -71
  69. data/lib/cassandra/client/result_metadata.rb +0 -48
  70. data/lib/cassandra/client/void_result.rb +0 -78
@@ -17,14 +17,14 @@
17
17
  #++
18
18
 
19
19
  module Cassandra
20
- module Client
20
+ class Cluster
21
21
  # @private
22
- class ConnectionManager
22
+ class ConnectionPool
23
23
  include Enumerable
24
24
 
25
25
  def initialize
26
26
  @connections = []
27
- @lock = Mutex.new
27
+ @lock = ::Mutex.new
28
28
  end
29
29
 
30
30
  def add_connections(connections)
@@ -53,7 +53,7 @@ module Cassandra
53
53
  end
54
54
 
55
55
  def random_connection
56
- raise Errors::NotConnectedError unless connected?
56
+ raise Errors::IOError, 'Not connected' unless connected?
57
57
  @lock.synchronize do
58
58
  @connections.sample
59
59
  end
@@ -61,7 +61,7 @@ module Cassandra
61
61
 
62
62
  def each_connection(&callback)
63
63
  return self unless block_given?
64
- raise Errors::NotConnectedError unless connected?
64
+ raise Errors::IOError, 'Not connected' unless connected?
65
65
  @lock.synchronize do
66
66
  @connections.each(&callback)
67
67
  end
@@ -22,13 +22,14 @@ module Cassandra
22
22
  class Connector
23
23
  include MonitorMixin
24
24
 
25
- def initialize(logger, io_reactor, cluster_registry, connection_options)
26
- @logger = logger
27
- @reactor = io_reactor
28
- @registry = cluster_registry
29
- @options = connection_options
30
- @connections = ::Hash.new
31
- @open_connections = ::Hash.new
25
+ def initialize(logger, io_reactor, cluster_registry, connection_options, execution_options)
26
+ @logger = logger
27
+ @reactor = io_reactor
28
+ @registry = cluster_registry
29
+ @connection_options = connection_options
30
+ @execution_options = execution_options
31
+ @connections = ::Hash.new
32
+ @open_connections = ::Hash.new
32
33
 
33
34
  mon_initialize
34
35
  end
@@ -43,25 +44,17 @@ module Cassandra
43
44
  end
44
45
  end
45
46
 
46
- @logger.info("Connecting ip=#{host.ip}")
47
-
48
47
  f = do_connect(host)
49
48
 
50
49
  f.on_failure do |error|
51
- @logger.warn("Connection failed ip=#{host.ip} error=\"#{error.class.name}: #{error.message}\"")
52
50
  connection_error(host, error)
53
51
  end
54
52
 
55
53
  f.on_value do |connection|
56
54
  connection.on_closed do |cause|
57
- message = "Disconnected ip=#{host.ip}"
58
- message << " error=#{cause.message}" if cause
59
-
60
- @logger.info(message)
61
55
  disconnected(host, cause)
62
56
  end
63
57
 
64
- @logger.info("Connected ip=#{host.ip}")
65
58
  connected(host)
66
59
  end
67
60
 
@@ -75,22 +68,15 @@ module Cassandra
75
68
  return Future.resolved
76
69
  end
77
70
 
78
- @logger.info("Refreshing host status ip=#{host.ip}")
71
+ @logger.debug("Checking if host #{host.ip} is up")
79
72
  f = do_connect(host)
80
73
 
81
74
  f.on_failure do |error|
82
- @logger.info("Refreshed host status ip=#{host.ip}")
83
- @logger.warn("Connection failed ip=#{host.ip} error=\"#{error.class.name}: #{error.message}\"")
84
75
  connection_error(host, error)
85
76
  end
86
77
 
87
78
  f.on_value do |connection|
88
- @logger.info("Refreshed host status ip=#{host.ip}")
89
79
  connection.on_closed do |cause|
90
- message = "Disconnected ip=#{host.ip}"
91
- message << " error=#{cause.message}" if cause
92
-
93
- @logger.info(message)
94
80
  disconnected(host, cause)
95
81
  end
96
82
 
@@ -99,7 +85,21 @@ module Cassandra
99
85
  @open_connections[host] << connection
100
86
  end
101
87
 
102
- @logger.info("Connected ip=#{host.ip}")
88
+ timer = @reactor.schedule_timer(UNCLAIMED_TIMEOUT)
89
+ timer.on_value do
90
+ close = false
91
+
92
+ synchronize do
93
+ open_connections = @open_connections[host]
94
+ if open_connections
95
+ close = !open_connections.delete(connection).nil?
96
+ @open_connections.delete(host) if open_connections.empty?
97
+ end
98
+ end
99
+
100
+ connection.close if close
101
+ end
102
+
103
103
  connected(host)
104
104
  end
105
105
 
@@ -112,52 +112,128 @@ module Cassandra
112
112
 
113
113
  private
114
114
 
115
- NO_CONNECTIONS = Ione::Future.resolved([])
115
+ NO_CONNECTIONS = Ione::Future.resolved([])
116
+ UNCLAIMED_TIMEOUT = 5 # close unclaimed connections in five seconds
116
117
 
117
118
  def do_connect(host)
118
- create_connector.connect(host.ip.to_s).fallback do |error|
119
- if error.is_a?(Errors::QueryError) && error.code == 0x0a
119
+ f = @reactor.connect(host.ip.to_s, @connection_options.port, {:timeout => @connection_options.connect_timeout, :ssl => @connection_options.ssl}) do |connection|
120
+ raise Errors::ClientError, 'Not connected, reactor stopped' unless connection
121
+ Protocol::CqlProtocolHandler.new(connection, @reactor, @connection_options.protocol_version, @connection_options.compressor, @connection_options.heartbeat_interval, @connection_options.idle_timeout)
122
+ end
123
+ f = f.flat_map do |connection|
124
+ request_options(connection).flat_map do |options|
125
+ compression = @connection_options.compression
126
+ supported_algorithms = options['COMPRESSION']
127
+
128
+ if compression && !supported_algorithms.include?(compression)
129
+ @logger.warn("Compression with #{compression.inspect} is not supported by host at #{host.ip}, supported algorithms are #{supported_algorithms.inspect}")
130
+ compression = nil
131
+ end
132
+
133
+ supported_cql_versions = options['CQL_VERSION']
134
+ cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ? supported_cql_versions.first : '3.1.0'
135
+
136
+ startup_connection(connection, cql_version, compression)
137
+ end
138
+ end
139
+ f.fallback do |error|
140
+ case error
141
+ when Errors::ProtocolError
120
142
  synchronize do
121
- if @options.protocol_version > 1
122
- @logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@options.protocol_version, @options.protocol_version - 1, error.message])
123
- @options.protocol_version -= 1
143
+ if @connection_options.protocol_version > 1
144
+ @logger.info("Host #{host.ip} doesn't support protocol version #{@connection_options.protocol_version}, downgrading")
145
+ @connection_options.protocol_version -= 1
124
146
  do_connect(host)
125
147
  else
126
148
  Ione::Future.failed(error)
127
149
  end
128
150
  end
129
- else
151
+ when Error
130
152
  Ione::Future.failed(error)
153
+ else
154
+ e = Errors::IOError.new(error.message)
155
+ e.set_backtrace(error.backtrace)
156
+ Ione::Future.failed(e)
157
+ end
158
+ end
159
+ end
160
+
161
+ def startup_connection(connection, cql_version, compression)
162
+ connection.send_request(Protocol::StartupRequest.new(cql_version, compression), @execution_options.timeout).flat_map do |r|
163
+ case r
164
+ when Protocol::AuthenticateResponse
165
+ if @connection_options.protocol_version == 1
166
+ credentials = @connection_options.credentials
167
+ if credentials
168
+ send_credentials(connection, credentials)
169
+ else
170
+ Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
171
+ end
172
+ else
173
+ authenticator = @connection_options.create_authenticator(r.authentication_class)
174
+ if authenticator
175
+ challenge_response_cycle(connection, authenticator, authenticator.initial_response)
176
+ else
177
+ Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
178
+ end
179
+ end
180
+ when Protocol::ReadyResponse
181
+ ::Ione::Future.resolved(connection)
182
+ when Protocol::ErrorResponse
183
+ ::Ione::Future.failed(r.to_error(VOID_STATEMENT))
184
+ else
185
+ ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
131
186
  end
132
187
  end
133
188
  end
134
189
 
135
- def create_connector
136
- authentication_step = @options.protocol_version == 1 ? Cassandra::Client::CredentialsAuthenticationStep.new(@options.credentials) : Cassandra::Client::SaslAuthenticationStep.new(@options.auth_provider)
137
- protocol_handler_factory = lambda do |connection|
138
- raise "no connection given" unless connection
139
- Protocol::CqlProtocolHandler.new(connection, @reactor, @options.protocol_version, @options.compressor)
140
- end
141
-
142
- Cassandra::Client::Connector.new([
143
- Cassandra::Client::ConnectStep.new(
144
- @reactor,
145
- protocol_handler_factory,
146
- @options.port,
147
- {:timeout => @options.connect_timeout, :ssl => @options.ssl},
148
- @logger
149
- ),
150
- Cassandra::Client::CacheOptionsStep.new(@options.connect_timeout),
151
- Cassandra::Client::InitializeStep.new(@options.compressor, @logger),
152
- authentication_step,
153
- Cassandra::Client::CachePropertiesStep.new,
154
- ])
190
+ def request_options(connection)
191
+ connection.send_request(Protocol::OptionsRequest.new, @execution_options.timeout).map do |r|
192
+ case r
193
+ when Protocol::SupportedResponse
194
+ r.options
195
+ when Protocol::ErrorResponse
196
+ raise r.to_error(VOID_STATEMENT)
197
+ else
198
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
199
+ end
200
+ end
201
+ end
202
+
203
+ def send_credentials(connection, credentials)
204
+ connection.send_request(Protocol::CredentialsRequest.new(credentials), @execution_options.timeout).map do |r|
205
+ case r
206
+ when Protocol::ReadyResponse
207
+ connection
208
+ when Protocol::ErrorResponse
209
+ raise r.to_error(VOID_STATEMENT)
210
+ else
211
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
212
+ end
213
+ end
214
+ end
215
+
216
+ def challenge_response_cycle(connection, authenticator, token)
217
+ connection.send_request(Protocol::AuthResponseRequest.new(token), @execution_options.timeout).flat_map do |r|
218
+ case r
219
+ when Protocol::AuthChallengeResponse
220
+ token = authenticator.challenge_response(r.token)
221
+ challenge_response_cycle(pending_connection, authenticator, token)
222
+ when Protocol::AuthSuccessResponse
223
+ authenticator.authentication_successful(r.token) rescue nil
224
+ ::Ione::Future.resolved(connection)
225
+ when Protocol::ErrorResponse
226
+ ::Ione::Future.failed(r.to_error(VOID_STATEMENT))
227
+ else
228
+ ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
229
+ end
230
+ end
155
231
  end
156
232
 
157
233
  def create_additional_connections(host, count, established_connections, error = nil)
158
234
  futures = count.times.map do
159
235
  connect(host).recover do |e|
160
- Cassandra::Client::FailedConnection.new(e, host)
236
+ FailedConnection.new(e, host)
161
237
  end
162
238
  end
163
239
 
@@ -216,14 +292,19 @@ module Cassandra
216
292
  connections -= 1
217
293
 
218
294
  if connections == 0
219
- notify = !error.nil? && !error.is_a?(Cassandra::Error)
295
+ notify = !error.nil?
220
296
  @connections.delete(host)
221
297
  else
222
298
  @connections[host] = connections
223
299
  end
224
300
  end
225
301
 
226
- @registry.host_down(host.ip) if notify
302
+ @logger.debug("Host #{host.ip} closed connection (#{error.class.name}: #{error.message})") if error
303
+
304
+ if notify
305
+ @logger.warn("Host #{host.ip} closed all connections")
306
+ @registry.host_down(host.ip)
307
+ end
227
308
 
228
309
  self
229
310
  end
@@ -232,10 +313,15 @@ module Cassandra
232
313
  notify = false
233
314
 
234
315
  synchronize do
235
- notify = !error.is_a?(Cassandra::Error) && !@connections.has_key?(host)
316
+ notify = !error.nil? && !@connections.has_key?(host)
236
317
  end
237
318
 
238
- @registry.host_down(host.ip) if notify
319
+ @logger.debug("Host #{host.ip} refused connection (#{error.class.name}: #{error.message})")
320
+
321
+ if notify
322
+ @logger.warn("Host #{host.ip} refused all connections")
323
+ @registry.host_down(host.ip)
324
+ end
239
325
 
240
326
  self
241
327
  end
@@ -22,7 +22,7 @@ module Cassandra
22
22
  class ControlConnection
23
23
  include MonitorMixin
24
24
 
25
- def initialize(logger, io_reactor, cluster_registry, cluster_schema, cluster_metadata, load_balancing_policy, reconnection_policy, connector)
25
+ def initialize(logger, io_reactor, cluster_registry, cluster_schema, cluster_metadata, load_balancing_policy, reconnection_policy, address_resolution_policy, connector)
26
26
  @logger = logger
27
27
  @io_reactor = io_reactor
28
28
  @registry = cluster_registry
@@ -30,9 +30,15 @@ module Cassandra
30
30
  @metadata = cluster_metadata
31
31
  @load_balancing_policy = load_balancing_policy
32
32
  @reconnection_policy = reconnection_policy
33
+ @address_resolver = address_resolution_policy
33
34
  @connector = connector
34
35
  @refreshing_statuses = Hash.new(false)
35
36
  @status = :closed
37
+ @refreshing_schema = false
38
+ @refreshing_keyspaces = Hash.new(false)
39
+ @refreshing_tables = Hash.new
40
+ @refreshing_hosts = false
41
+ @refreshing_host = Hash.new(false)
36
42
 
37
43
  mon_initialize
38
44
  end
@@ -43,12 +49,14 @@ module Cassandra
43
49
  @status = :connecting
44
50
  end
45
51
 
46
- @logger.debug('Establishing control connection')
47
-
48
- @io_reactor.start.flat_map do
52
+ f = @io_reactor.start.flat_map do
49
53
  plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
50
54
  connect_to_first_available(plan)
51
55
  end
56
+ f.on_failure do |e|
57
+ close_async
58
+ end
59
+ f
52
60
  end
53
61
 
54
62
  def host_found(host)
@@ -56,31 +64,48 @@ module Cassandra
56
64
 
57
65
  def host_lost(host)
58
66
  synchronize do
59
- @refreshing_statuses.delete(host)
67
+ timer = @refreshing_statuses.delete(host)
68
+ @io_reactor.cancel_timer(timer) if timer
60
69
  end
70
+
71
+ nil
61
72
  end
62
73
 
63
74
  def host_up(host)
64
75
  synchronize do
65
- @refreshing_statuses.delete(host)
76
+ timer = @refreshing_statuses.delete(host)
77
+ @io_reactor.cancel_timer(timer) if timer
66
78
 
67
- return connect_async if !@connection && !(@status == :closed || @status == :closed)
79
+ unless @connection || (@status == :closing || @status == :closed) || @load_balancing_policy.distance(host) == :ignore
80
+ return connect_to_first_available(@load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS))
81
+ end
68
82
  end
69
83
 
70
84
  Ione::Future.resolved
71
85
  end
72
86
 
73
87
  def host_down(host)
88
+ schedule = nil
89
+ timer = nil
90
+
74
91
  synchronize do
75
- return Ione::Future.resolved if @refreshing_statuses[host]
92
+ return Ione::Future.resolved if @refreshing_statuses[host] || @load_balancing_policy.distance(host) == :ignore
93
+
94
+ schedule = @reconnection_policy.schedule
95
+ timeout = schedule.next
76
96
 
77
- @logger.debug("Starting to continuously refresh status for ip=#{host.ip}")
78
- @refreshing_statuses[host] = true
97
+ @logger.debug("Starting to continuously refresh status of #{host.ip} in #{timeout} seconds")
98
+
99
+ @refreshing_statuses[host] = timer = @io_reactor.schedule_timer(timeout)
79
100
  end
80
101
 
81
- refresh_host_status(host).fallback do |e|
82
- refresh_host_status_with_retry(host, @reconnection_policy.schedule)
102
+ timer.on_value do
103
+ refresh_host_status(host).fallback do |e|
104
+ refresh_host_status_with_retry(timer, host, schedule)
105
+ end
83
106
  end
107
+
108
+ nil
84
109
  end
85
110
 
86
111
  def close_async
@@ -116,19 +141,18 @@ module Cassandra
116
141
  f = @io_reactor.schedule_timer(timeout)
117
142
  f = f.flat_map do
118
143
  if synchronize { @status == :reconnecting }
119
- @logger.debug('Reestablishing control connection')
120
144
  plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
121
145
  connect_to_first_available(plan)
122
146
  else
123
- @logger.debug('Stopping reconnection')
124
147
  Ione::Future.resolved
125
148
  end
126
149
  end
127
- f.fallback do
150
+ f.fallback do |e|
151
+ @logger.error("Control connection failed (#{e.class.name}: #{e.message})")
152
+
128
153
  if synchronize { @status == :reconnecting }
129
154
  reconnect_async(schedule)
130
155
  else
131
- @logger.debug('Stopping reconnection')
132
156
  return Ione::Future.resolved
133
157
  end
134
158
  end
@@ -137,13 +161,20 @@ module Cassandra
137
161
  def register_async
138
162
  connection = @connection
139
163
 
140
- return Ione::Future.failed("not connected") if connection.nil?
141
-
142
- @logger.debug('Registering for events')
143
-
144
- connection.send_request(REGISTER).map do
145
- @logger.debug('Registered for events')
164
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
146
165
 
166
+ f = connection.send_request(REGISTER)
167
+ f = f.map do |r|
168
+ case r
169
+ when Protocol::ReadyResponse
170
+ nil
171
+ when Protocol::ErrorResponse
172
+ raise r.to_error(VOID_STATEMENT)
173
+ else
174
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
175
+ end
176
+ end
177
+ f = f.map do
147
178
  connection.on_event do |event|
148
179
  @logger.debug("Event received #{event}")
149
180
 
@@ -151,21 +182,21 @@ module Cassandra
151
182
  case event.change
152
183
  when 'CREATED'
153
184
  if event.table.empty?
154
- refresh_schema_async
185
+ refresh_schema_async_maybe_retry
155
186
  else
156
- refresh_keyspace_async(event.keyspace)
187
+ refresh_keyspace_async_maybe_retry(event.keyspace)
157
188
  end
158
189
  when 'DROPPED'
159
190
  if event.table.empty?
160
- refresh_schema_async
191
+ refresh_schema_async_maybe_retry
161
192
  else
162
- refresh_keyspace_async(event.keyspace)
193
+ refresh_keyspace_async_maybe_retry(event.keyspace)
163
194
  end
164
195
  when 'UPDATED'
165
196
  if event.table.empty?
166
- refresh_keyspace_async(event.keyspace)
197
+ refresh_keyspace_async_maybe_retry(event.keyspace)
167
198
  else
168
- refresh_table_async(event.keyspace, event.table)
199
+ refresh_table_async_maybe_retry(event.keyspace, event.table)
169
200
  end
170
201
  end
171
202
  else
@@ -173,19 +204,19 @@ module Cassandra
173
204
  when 'UP'
174
205
  address = event.address
175
206
 
176
- refresh_host_async(address) if @registry.has_host?(address)
207
+ refresh_host_async_maybe_retry(address) if @registry.has_host?(address)
177
208
  when 'DOWN'
178
209
  @registry.host_down(event.address)
179
210
  when 'NEW_NODE'
180
211
  address = event.address
181
212
 
182
213
  unless @registry.has_host?(address)
183
- refresh_host_async(address)
184
- refresh_schema_async
214
+ refresh_host_async_maybe_retry(address)
215
+ refresh_schema_async_maybe_retry
185
216
  end
186
217
  when 'REMOVED_NODE'
187
218
  @registry.host_lost(event.address)
188
- refresh_schema_async
219
+ refresh_schema_async_maybe_retry
189
220
  end
190
221
  end
191
222
  end
@@ -197,85 +228,246 @@ module Cassandra
197
228
  def refresh_schema_async
198
229
  connection = @connection
199
230
 
200
- return Ione::Future.failed("not connected") if connection.nil?
201
-
202
- @logger.debug('Fetching schema metadata')
231
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
203
232
 
204
- keyspaces = connection.send_request(SELECT_KEYSPACES)
205
- tables = connection.send_request(SELECT_TABLES)
206
- columns = connection.send_request(SELECT_COLUMNS)
233
+ keyspaces = send_select_request(connection, SELECT_KEYSPACES)
234
+ tables = send_select_request(connection, SELECT_TABLES)
235
+ columns = send_select_request(connection, SELECT_COLUMNS)
207
236
 
208
237
  Ione::Future.all(keyspaces, tables, columns).map do |(keyspaces, tables, columns)|
209
- @logger.debug('Fetched schema metadata')
210
-
211
238
  host = @registry.host(connection.host)
212
239
 
213
- @schema.update_keyspaces(host, keyspaces.rows, tables.rows, columns.rows)
240
+ @schema.update_keyspaces(host, keyspaces, tables, columns)
214
241
  @metadata.rebuild_token_map
215
242
  end
216
243
  end
217
244
 
245
+ def refresh_schema_async_maybe_retry
246
+ synchronize do
247
+ return Ione::Future.resolved if @refreshing_schema
248
+ @refreshing_schema = true
249
+ end
250
+
251
+ refresh_schema_async.fallback do |e|
252
+ case e
253
+ when Errors::HostError
254
+ refresh_schema_async_retry(e, @reconnection_policy.schedule)
255
+ else
256
+ connection = @connection
257
+ connection && connection.close(e)
258
+
259
+ Ione::Future.resolved
260
+ end
261
+ end.map do
262
+ synchronize do
263
+ @refreshing_schema = false
264
+ end
265
+ end
266
+ end
267
+
268
+ def refresh_schema_async_retry(error, schedule)
269
+ timeout = schedule.next
270
+ @logger.info("Failed to refresh schema (#{error.class.name}: #{error.message}), retrying in #{timeout}")
271
+
272
+ timer = @io_reactor.schedule_timer(timeout)
273
+ timer.flat_map do
274
+ refresh_schema_async.fallback do |e|
275
+ case e
276
+ when Errors::HostError
277
+ refresh_schema_async_retry(e, schedule)
278
+ else
279
+ connection = @connection
280
+ connection && connection.close(e)
281
+
282
+ Ione::Future.resolved
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def refresh_keyspace_async_maybe_retry(keyspace)
289
+ synchronize do
290
+ return Ione::Future.resolved if @refreshing_schema || @refreshing_keyspaces[keyspace]
291
+ @refreshing_keyspaces[keyspace] = true
292
+ end
293
+
294
+ refresh_keyspace_async(keyspace).fallback do |e|
295
+ case e
296
+ when Errors::HostError
297
+ refresh_keyspace_async_retry(keyspace, e, @reconnection_policy.schedule)
298
+ else
299
+ connection = @connection
300
+ connection && connection.close(e)
301
+
302
+ Ione::Future.resolved
303
+ end
304
+ end.map do
305
+ synchronize do
306
+ @refreshing_keyspaces.delete(host)
307
+ end
308
+ end
309
+ end
310
+
311
+ def refresh_keyspace_async_retry(keyspace, error, schedule)
312
+ timeout = schedule.next
313
+ @logger.info("Failed to refresh keyspace #{keyspace} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
314
+
315
+ timer = @io_reactor.schedule_timer(timeout)
316
+ timer.flat_map do
317
+ refresh_keyspace_async(keyspace).fallback do |e|
318
+ case e
319
+ when Errors::HostError
320
+ refresh_keyspace_async_retry(keyspace, e, schedule)
321
+ else
322
+ connection = @connection
323
+ connection && connection.close(e)
324
+
325
+ Ione::Future.resolved
326
+ end
327
+ end
328
+ end
329
+ end
330
+
218
331
  def refresh_keyspace_async(keyspace)
219
332
  connection = @connection
220
333
 
221
- return Ione::Future.failed("not connected") if connection.nil?
334
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
222
335
 
223
- @logger.debug("Fetching keyspace #{keyspace.inspect} metadata")
224
-
225
- params = [keyspace]
226
- keyspaces = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?", params, nil, :one))
227
- tables = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?", params, nil, :one))
228
- columns = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = ?", params, nil, :one))
336
+ keyspaces = send_select_request(connection, Protocol::QueryRequest.new("SELECT * FROM system.schema_keyspaces WHERE keyspace_name = '%s'" % keyspace, nil, nil, :one))
337
+ tables = send_select_request(connection, Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = '%s'" % keyspace, nil, nil, :one))
338
+ columns = send_select_request(connection, Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = '%s'" % keyspace, nil, nil, :one))
229
339
 
230
340
  Ione::Future.all(keyspaces, tables, columns).map do |(keyspaces, tables, columns)|
231
- @logger.debug("Fetched keyspace #{keyspace.inspect} metadata")
232
-
233
341
  host = @registry.host(connection.host)
234
342
 
235
- @schema.update_keyspace(host, keyspaces.rows.first, tables.rows, columns.rows)
343
+ @schema.update_keyspace(host, keyspaces.first, tables, columns) unless keyspaces.empty?
344
+ end
345
+ end
346
+
347
+ def refresh_table_async_maybe_retry(keyspace, table)
348
+ synchronize do
349
+ return Ione::Future.resolved if @refreshing_schema || @refreshing_keyspaces[keyspace] || @refreshing_tables[keyspace][table]
350
+ @refreshing_tables[keyspace] ||= ::Hash.new(false)
351
+ @refreshing_tables[keyspace][table] = true
352
+ end
353
+
354
+ refresh_table_async(keyspace, table).fallback do |e|
355
+ case e
356
+ when Errors::HostError
357
+ refresh_keyspace_async_retry(keyspace, e, @reconnection_policy.schedule)
358
+ else
359
+ connection = @connection
360
+ connection && connection.close(e)
361
+
362
+ Ione::Future.resolved
363
+ end
364
+ end.map do
365
+ synchronize do
366
+ @refreshing_tables[keyspace].delete(table)
367
+ @refreshing_tables.delete(keyspace) if @refreshing_tables[keyspace].empty?
368
+ end
369
+ end
370
+ end
371
+
372
+ def refresh_table_async_retry(keyspace, table, error, schedule)
373
+ timeout = schedule.next
374
+ @logger.info("Failed to refresh keyspace #{keyspace} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
375
+
376
+ timer = @io_reactor.schedule_timer(timeout)
377
+ timer.flat_map do
378
+ refresh_keyspace_async(keyspace).fallback do |e|
379
+ case e
380
+ when Errors::HostError
381
+ refresh_keyspace_async_retry(keyspace, e, schedule)
382
+ else
383
+ connection = @connection
384
+ connection && connection.close(e)
385
+
386
+ Ione::Future.resolved
387
+ end
388
+ end
236
389
  end
237
390
  end
238
391
 
239
392
  def refresh_table_async(keyspace, table)
240
393
  connection = @connection
241
394
 
242
- return Ione::Future.failed("not connected") if connection.nil?
243
-
244
- @logger.debug("Fetching table \"#{keyspace}.#{table}\" metadata")
395
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
245
396
 
246
397
  params = [keyspace, table]
247
- table = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ? AND columnfamily_name = ?", params, nil, :one))
248
- columns = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = ? AND columnfamily_name = ?", params, nil, :one))
398
+ table = send_select_request(connection, Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = '%s' AND columnfamily_name = '%s'" % params, nil, nil, :one))
399
+ columns = send_select_request(connection, Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = '%s' AND columnfamily_name = '%s'" % params, nil, nil, :one))
249
400
 
250
401
  Ione::Future.all(table, columns).map do |(table, columns)|
251
- @logger.debug("Fetched table \"#{keyspace}.#{table}\" metadata")
252
-
253
402
  host = @registry.host(connection.host)
254
403
 
255
- @schema.udpate_table(host, keyspace, table.rows, columns.rows)
404
+ @schema.udpate_table(host, keyspace, table, columns)
256
405
  end
257
406
  end
258
407
 
259
- def refresh_hosts_async
260
- connection = @connection
408
+ def refresh_hosts_async_maybe_retry
409
+ synchronize do
410
+ return Ione::Future.resolved if @refreshing_hosts
411
+ @refreshing_hosts = true
412
+ end
261
413
 
262
- return Ione::Future.failed("not connected") if connection.nil?
414
+ refresh_hosts_async.fallback do |e|
415
+ case e
416
+ when Errors::HostError
417
+ refresh_hosts_async_retry(e, @reconnection_policy.schedule)
418
+ else
419
+ connection = @connection
420
+ connection && connection.close(e)
263
421
 
264
- @logger.debug('Fetching cluster metadata and peers')
422
+ Ione::Future.resolved
423
+ end
424
+ end.map do
425
+ synchronize do
426
+ @refreshing_hosts = false
427
+ end
428
+ end
429
+ end
265
430
 
266
- local = connection.send_request(SELECT_LOCAL)
267
- peers = connection.send_request(SELECT_PEERS)
431
+ def refresh_hosts_async_retry(error, schedule)
432
+ timeout = schedule.next
433
+ @logger.info("Failed to refresh hosts (#{error.class.name}: #{error.message}), retrying in #{timeout}")
434
+
435
+ timer = @io_reactor.schedule_timer(timeout)
436
+ timer.flat_map do
437
+ refresh_hosts_async.fallback do |e|
438
+ case e
439
+ when Errors::HostError
440
+ refresh_hosts_async_retry(e, schedule)
441
+ else
442
+ connection = @connection
443
+ connection && connection.close(e)
444
+
445
+ Ione::Future.resolved
446
+ end
447
+ end
448
+ end
449
+ end
268
450
 
269
- Ione::Future.all(local, peers).flat_map do |(local, peers)|
270
- local = local.rows
271
- peers = peers.rows
451
+ def refresh_hosts_async
452
+ connection = @connection
272
453
 
273
- @logger.debug('%d peers found' % peers.size)
454
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
274
455
 
275
- raise NO_HOSTS if local.empty? && peers.empty?
456
+ local = send_select_request(connection, SELECT_LOCAL)
457
+ peers = send_select_request(connection, SELECT_PEERS)
458
+
459
+ Ione::Future.all(local, peers).map do |(local, peers)|
460
+ @logger.debug("#{peers.size} peer(s) found")
276
461
 
277
462
  ips = ::Set.new
278
463
 
464
+ unless local.empty?
465
+ ips << ip = IPAddr.new(connection.host)
466
+ data = local.first
467
+ @registry.host_found(ip, data)
468
+ @metadata.update(data)
469
+ end
470
+
279
471
  peers.each do |data|
280
472
  ip = peer_ip(data)
281
473
  next unless ip
@@ -283,27 +475,11 @@ module Cassandra
283
475
  @registry.host_found(ip, data)
284
476
  end
285
477
 
286
- ips << ip = IPAddr.new(connection.host)
287
- data = local.first
288
- @registry.host_found(ip, data)
289
-
290
- futures = []
291
-
292
478
  @registry.each_host do |host|
293
- if ips.include?(host.ip)
294
- futures << refresh_host_status(host) if host.down?
295
- else
296
- @registry.host_lost(host.ip)
297
- end
479
+ @registry.host_lost(host.ip) unless ips.include?(host.ip)
298
480
  end
299
481
 
300
- @metadata.update(data)
301
-
302
- if futures.empty?
303
- Ione::Future.resolved
304
- else
305
- Ione::Future.all(*futures)
306
- end
482
+ nil
307
483
  end
308
484
  end
309
485
 
@@ -311,46 +487,87 @@ module Cassandra
311
487
  @connector.refresh_status(host)
312
488
  end
313
489
 
314
- def refresh_host_status_with_retry(host, schedule)
315
- timeout = schedule.next
490
+ def refresh_host_status_with_retry(original_timer, host, schedule)
491
+ timer = nil
316
492
 
317
- @logger.info("Refreshing host status refresh ip=#{host.ip} in #{timeout}")
493
+ synchronize do
494
+ timer = @refreshing_statuses[host]
318
495
 
319
- f = @io_reactor.schedule_timer(timeout)
320
- f.flat_map do
321
- if synchronize { @refreshing_statuses[host] }
322
- refresh_host_status(host).fallback do |e|
323
- refresh_host_status_with_retry(host, schedule)
324
- end
496
+ # host must have been lost/up or timer was rescheduled
497
+ return Ione::Future.resolved if timer.nil? || timer != original_timer
498
+
499
+ timeout = schedule.next
500
+
501
+ @logger.debug("Checking host #{host.ip} in #{timeout} seconds")
502
+
503
+ @refreshing_statuses[host] = timer = @io_reactor.schedule_timer(timeout)
504
+ end
505
+
506
+ timer.on_value do
507
+ refresh_host_status(host).fallback do |e|
508
+ refresh_host_status_with_retry(timer, host, schedule)
509
+ end
510
+ end
511
+ end
512
+
513
+ def refresh_host_async_maybe_retry(host)
514
+ synchronize do
515
+ return Ione::Future.resolved if @refreshing_hosts || @refreshing_host[host]
516
+ @refreshing_host[host] = true
517
+ end
518
+
519
+ refresh_host_async(host).fallback do |e|
520
+ case e
521
+ when Errors::HostError
522
+ refresh_host_async_retry(host, e, @reconnection_policy.schedule)
325
523
  else
524
+ connection = @connection
525
+ connection && connection.close(e)
526
+
326
527
  Ione::Future.resolved
327
528
  end
529
+ end.map do
530
+ synchronize do
531
+ @refreshing_host.delete(host)
532
+ end
533
+ end
534
+ end
535
+
536
+ def refresh_host_async_retry(host, error, schedule)
537
+ timeout = schedule.next
538
+ @logger.info("Failed to refresh host #{host.ip.to_s} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
539
+
540
+ timer = @io_reactor.schedule_timer(timeout)
541
+ timer.flat_map do
542
+ refresh_host_async(host).fallback do |e|
543
+ case e
544
+ when Errors::HostError
545
+ refresh_host_async_retry(host, e, schedule)
546
+ else
547
+ connection = @connection
548
+ connection && connection.close(e)
549
+
550
+ Ione::Future.resolved
551
+ end
552
+ end
328
553
  end
329
554
  end
330
555
 
331
556
  def refresh_host_async(address)
332
557
  connection = @connection
333
- return Ione::Future.failed("not connected") if connection.nil?
558
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
334
559
 
335
560
  ip = address.to_s
336
561
 
337
- @logger.debug('Fetching node information for %s' % ip)
338
-
339
562
  if ip == connection.host
340
563
  request = SELECT_LOCAL
341
564
  else
342
- request = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers WHERE peer = ?', [address], nil, :one)
565
+ request = Protocol::QueryRequest.new("SELECT rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers WHERE peer = '%s'" % address, nil, nil, :one)
343
566
  end
344
567
 
345
- connection.send_request(request).map do |response|
346
- @logger.debug('Fetched node information for %s' % ip)
347
-
348
- rows = response.rows
349
-
568
+ send_select_request(connection, request).map do |rows|
350
569
  unless rows.empty?
351
570
  @registry.host_found(address, rows.first)
352
- host = @registry.host(address)
353
- refresh_host_status(host) if host.down?
354
571
  end
355
572
 
356
573
  self
@@ -359,33 +576,50 @@ module Cassandra
359
576
 
360
577
  def connect_to_first_available(plan, errors = nil)
361
578
  unless plan.has_next?
362
- @logger.warn("Control connection failed")
363
- return Ione::Future.failed(Errors::NoHostsAvailable.new(errors || {}))
579
+ if errors.nil? && synchronize { @refreshing_statuses.empty? }
580
+ @logger.fatal(<<-MSG)
581
+ Control connection failed and is unlikely to recover.
582
+
583
+ This usually means that all hosts are ignored by current load
584
+ balancing policy, most likely because they changed datacenters.
585
+ Reconnections attempts will continue getting scheduled to
586
+ repeat this message in the logs.
587
+ MSG
588
+ end
589
+
590
+ return Ione::Future.failed(Errors::NoHostsAvailable.new(errors))
364
591
  end
365
592
 
366
593
  host = plan.next
367
- @logger.debug("Attempting connection to ip=#{host.ip}")
594
+ @logger.debug("Connecting to #{host.ip}")
595
+
368
596
  f = connect_to_host(host)
369
597
  f = f.flat_map do |connection|
370
598
  synchronize do
371
599
  @status = :connected
372
600
 
373
- @logger.debug("Control connection established ip=#{connection.host}")
374
601
  @connection = connection
375
602
 
376
- connection.on_closed do
603
+ connection.on_closed do |cause|
377
604
  reconnect = false
378
605
 
379
606
  synchronize do
380
- if @status == :closing
381
- @status = :closed
382
- else
383
- @status = :reconnecting
384
- reconnect = true
607
+ if connection == @connection
608
+ if @status == :closing
609
+ @status = :closed
610
+ else
611
+ @status = :reconnecting
612
+ reconnect = true
613
+ end
614
+
615
+ if cause
616
+ @logger.info("Control connection closed (#{cause.class.name}: #{cause.message})")
617
+ else
618
+ @logger.info("Control connection closed")
619
+ end
620
+
621
+ @connection = nil
385
622
  end
386
-
387
- @logger.debug("Control connection closed ip=#{connection.host}")
388
- @connection = nil
389
623
  end
390
624
 
391
625
  reconnect_async(@reconnection_policy.schedule) if reconnect
@@ -394,23 +628,26 @@ module Cassandra
394
628
 
395
629
  register_async
396
630
  end
397
- f = f.flat_map { refresh_hosts_async }
398
- f = f.flat_map { refresh_schema_async }
399
- f.fallback do |error|
400
- if error.is_a?(Errors::AuthenticationError)
401
- Ione::Future.failed(error)
402
- elsif error.is_a?(Errors::QueryError)
403
- if error.code == 0x100
404
- Ione::Future.failed(Errors::AuthenticationError.new(error.message))
405
- else
406
- Ione::Future.failed(error)
407
- end
408
- else
631
+ f = f.flat_map { refresh_hosts_async_maybe_retry }
632
+ f = f.flat_map { refresh_schema_async_maybe_retry }
633
+ f = f.fallback do |error|
634
+ @logger.debug("Connection to #{host.ip} failed (#{error.class.name}: #{error.message})")
635
+
636
+ case error
637
+ when Errors::HostError
409
638
  errors ||= {}
410
639
  errors[host] = error
411
640
  connect_to_first_available(plan, errors)
641
+ else
642
+ Ione::Future.failed(error)
412
643
  end
413
644
  end
645
+
646
+ f.on_complete do |f|
647
+ @logger.info('Control connection established') if f.resolved?
648
+ end
649
+
650
+ f
414
651
  end
415
652
 
416
653
  def connect_to_host(host)
@@ -420,7 +657,21 @@ module Cassandra
420
657
  def peer_ip(data)
421
658
  ip = data['rpc_address']
422
659
  ip = data['peer'] if ip == '0.0.0.0'
423
- ip
660
+
661
+ @address_resolver.resolve(ip)
662
+ end
663
+
664
+ def send_select_request(connection, request)
665
+ connection.send_request(request).map do |r|
666
+ case r
667
+ when Protocol::RowsResultResponse
668
+ r.rows
669
+ when Protocol::ErrorResponse
670
+ raise r.to_error(VOID_STATEMENT)
671
+ else
672
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
673
+ end
674
+ end
424
675
  end
425
676
  end
426
677
  end