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

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