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