cassandra-driver 0.1.0.alpha1 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.yardopts +4 -0
  2. data/README.md +117 -0
  3. data/lib/cassandra.rb +320 -0
  4. data/lib/cassandra/auth.rb +97 -0
  5. data/lib/cassandra/auth/providers.rb +16 -0
  6. data/lib/cassandra/auth/providers/password.rb +73 -0
  7. data/lib/cassandra/client.rb +144 -0
  8. data/lib/cassandra/client/batch.rb +212 -0
  9. data/lib/cassandra/client/client.rb +591 -0
  10. data/lib/cassandra/client/column_metadata.rb +54 -0
  11. data/lib/cassandra/client/connection_manager.rb +72 -0
  12. data/lib/cassandra/client/connector.rb +272 -0
  13. data/lib/cassandra/client/execute_options_decoder.rb +59 -0
  14. data/lib/cassandra/client/null_logger.rb +37 -0
  15. data/lib/cassandra/client/peer_discovery.rb +50 -0
  16. data/lib/cassandra/client/prepared_statement.rb +314 -0
  17. data/lib/cassandra/client/query_result.rb +230 -0
  18. data/lib/cassandra/client/request_runner.rb +71 -0
  19. data/lib/cassandra/client/result_metadata.rb +48 -0
  20. data/lib/cassandra/client/void_result.rb +78 -0
  21. data/lib/cassandra/cluster.rb +191 -0
  22. data/lib/cassandra/cluster/client.rb +767 -0
  23. data/lib/cassandra/cluster/connector.rb +231 -0
  24. data/lib/cassandra/cluster/control_connection.rb +420 -0
  25. data/lib/cassandra/cluster/options.rb +40 -0
  26. data/lib/cassandra/cluster/registry.rb +181 -0
  27. data/lib/cassandra/cluster/schema.rb +321 -0
  28. data/lib/cassandra/cluster/schema/type_parser.rb +138 -0
  29. data/lib/cassandra/column.rb +92 -0
  30. data/lib/cassandra/compression.rb +66 -0
  31. data/lib/cassandra/compression/compressors/lz4.rb +72 -0
  32. data/lib/cassandra/compression/compressors/snappy.rb +66 -0
  33. data/lib/cassandra/driver.rb +86 -0
  34. data/lib/cassandra/errors.rb +79 -0
  35. data/lib/cassandra/execution/info.rb +51 -0
  36. data/lib/cassandra/execution/options.rb +77 -0
  37. data/lib/cassandra/execution/trace.rb +152 -0
  38. data/lib/cassandra/future.rb +675 -0
  39. data/lib/cassandra/host.rb +75 -0
  40. data/lib/cassandra/keyspace.rb +120 -0
  41. data/lib/cassandra/listener.rb +87 -0
  42. data/lib/cassandra/load_balancing.rb +112 -0
  43. data/lib/cassandra/load_balancing/policies.rb +18 -0
  44. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +149 -0
  45. data/lib/cassandra/load_balancing/policies/round_robin.rb +95 -0
  46. data/lib/cassandra/load_balancing/policies/white_list.rb +90 -0
  47. data/lib/cassandra/protocol.rb +93 -0
  48. data/lib/cassandra/protocol/cql_byte_buffer.rb +307 -0
  49. data/lib/cassandra/protocol/cql_protocol_handler.rb +323 -0
  50. data/lib/cassandra/protocol/frame_decoder.rb +128 -0
  51. data/lib/cassandra/protocol/frame_encoder.rb +48 -0
  52. data/lib/cassandra/protocol/request.rb +38 -0
  53. data/lib/cassandra/protocol/requests/auth_response_request.rb +47 -0
  54. data/lib/cassandra/protocol/requests/batch_request.rb +76 -0
  55. data/lib/cassandra/protocol/requests/credentials_request.rb +47 -0
  56. data/lib/cassandra/protocol/requests/execute_request.rb +103 -0
  57. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  58. data/lib/cassandra/protocol/requests/prepare_request.rb +50 -0
  59. data/lib/cassandra/protocol/requests/query_request.rb +153 -0
  60. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  61. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  62. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  63. data/lib/cassandra/protocol/response.rb +38 -0
  64. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +41 -0
  65. data/lib/cassandra/protocol/responses/auth_success_response.rb +41 -0
  66. data/lib/cassandra/protocol/responses/authenticate_response.rb +41 -0
  67. data/lib/cassandra/protocol/responses/detailed_error_response.rb +60 -0
  68. data/lib/cassandra/protocol/responses/error_response.rb +50 -0
  69. data/lib/cassandra/protocol/responses/event_response.rb +39 -0
  70. data/lib/cassandra/protocol/responses/prepared_result_response.rb +64 -0
  71. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +43 -0
  72. data/lib/cassandra/protocol/responses/ready_response.rb +44 -0
  73. data/lib/cassandra/protocol/responses/result_response.rb +48 -0
  74. data/lib/cassandra/protocol/responses/rows_result_response.rb +139 -0
  75. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +60 -0
  76. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +57 -0
  77. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +42 -0
  78. data/lib/cassandra/protocol/responses/status_change_event_response.rb +44 -0
  79. data/lib/cassandra/protocol/responses/supported_response.rb +41 -0
  80. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +34 -0
  81. data/lib/cassandra/protocol/responses/void_result_response.rb +39 -0
  82. data/lib/cassandra/protocol/type_converter.rb +384 -0
  83. data/lib/cassandra/reconnection.rb +49 -0
  84. data/lib/cassandra/reconnection/policies.rb +20 -0
  85. data/lib/cassandra/reconnection/policies/constant.rb +48 -0
  86. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  87. data/lib/cassandra/result.rb +215 -0
  88. data/lib/cassandra/retry.rb +142 -0
  89. data/lib/cassandra/retry/policies.rb +21 -0
  90. data/lib/cassandra/retry/policies/default.rb +47 -0
  91. data/lib/cassandra/retry/policies/downgrading_consistency.rb +71 -0
  92. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  93. data/lib/cassandra/session.rb +195 -0
  94. data/lib/cassandra/statement.rb +22 -0
  95. data/lib/cassandra/statements.rb +23 -0
  96. data/lib/cassandra/statements/batch.rb +95 -0
  97. data/lib/cassandra/statements/bound.rb +46 -0
  98. data/lib/cassandra/statements/prepared.rb +59 -0
  99. data/lib/cassandra/statements/simple.rb +58 -0
  100. data/lib/cassandra/statements/void.rb +33 -0
  101. data/lib/cassandra/table.rb +254 -0
  102. data/lib/cassandra/time_uuid.rb +141 -0
  103. data/lib/cassandra/util.rb +169 -0
  104. data/lib/cassandra/uuid.rb +104 -0
  105. data/lib/cassandra/version.rb +17 -1
  106. metadata +134 -8
@@ -0,0 +1,231 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ class Cluster
21
+ # @private
22
+ class Connector
23
+ include MonitorMixin
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
32
+
33
+ mon_initialize
34
+ end
35
+
36
+ def connect(host)
37
+ synchronize do
38
+ open_connections = @open_connections[host]
39
+ if open_connections
40
+ connection = open_connections.shift
41
+ @open_connections.delete(host) if open_connections.empty?
42
+ return Ione::Future.resolved(connection)
43
+ end
44
+ end
45
+
46
+ @logger.info("Connecting ip=#{host.ip}")
47
+
48
+ f = do_connect(host)
49
+
50
+ f.on_failure do |error|
51
+ @logger.warn("Connection failed ip=#{host.ip} error=\"#{error.class.name}: #{error.message}\"")
52
+ connection_error(host, error)
53
+ end
54
+
55
+ f.on_value do |connection|
56
+ connection.on_closed do |cause|
57
+ message = "Disconnected ip=#{host.ip}"
58
+ message << " error=#{cause.message}" if cause
59
+
60
+ @logger.info(message)
61
+ disconnected(host, cause)
62
+ end
63
+
64
+ @logger.info("Connected ip=#{host.ip}")
65
+ connected(host)
66
+ end
67
+
68
+ f
69
+ end
70
+
71
+ def refresh_status(host)
72
+ return Future.resolved if synchronize { @connections[host] }
73
+
74
+ @logger.info("Refreshing host status ip=#{host.ip}")
75
+ f = do_connect(host)
76
+
77
+ f.on_failure do |error|
78
+ @logger.info("Refreshed host status ip=#{host.ip}")
79
+ @logger.warn("Connection failed ip=#{host.ip} error=\"#{error.class.name}: #{error.message}\"")
80
+ connection_error(host, error)
81
+ end
82
+
83
+ f.on_value do |connection|
84
+ @logger.info("Refreshed host status ip=#{host.ip}")
85
+ connection.on_closed do |cause|
86
+ message = "Disconnected ip=#{host.ip}"
87
+ message << " error=#{cause.message}" if cause
88
+
89
+ @logger.info(message)
90
+ disconnected(host, cause)
91
+ end
92
+
93
+ synchronize do
94
+ @open_connections[host] ||= []
95
+ @open_connections[host] << connection
96
+ end
97
+
98
+ @logger.info("Connected ip=#{host.ip}")
99
+ connected(host)
100
+ end
101
+
102
+ f
103
+ end
104
+
105
+ def connect_many(host, count)
106
+ create_additional_connections(host, count, [])
107
+ end
108
+
109
+ private
110
+
111
+ NO_CONNECTIONS = Ione::Future.resolved([])
112
+
113
+ def do_connect(host)
114
+ create_connector.connect(host.ip.to_s).fallback do |error|
115
+ if error.is_a?(Errors::QueryError) && error.code == 0x0a
116
+ synchronize do
117
+ if @options.protocol_version > 1
118
+ @logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@options.protocol_version, @options.protocol_version - 1, error.message])
119
+ @options.protocol_version -= 1
120
+ do_connect(host)
121
+ else
122
+ Ione::Future.failed(error)
123
+ end
124
+ end
125
+ else
126
+ Ione::Future.failed(error)
127
+ end
128
+ end
129
+ end
130
+
131
+ def create_connector
132
+ authentication_step = @options.protocol_version == 1 ? Cassandra::Client::CredentialsAuthenticationStep.new(@options.credentials) : Cassandra::Client::SaslAuthenticationStep.new(@options.auth_provider)
133
+ protocol_handler_factory = lambda { |connection| Protocol::CqlProtocolHandler.new(connection, @reactor, @options.protocol_version, @options.compressor) }
134
+
135
+ Cassandra::Client::Connector.new([
136
+ Cassandra::Client::ConnectStep.new(@reactor, protocol_handler_factory, @options.port, @options.connection_timeout, @logger),
137
+ Cassandra::Client::CacheOptionsStep.new,
138
+ Cassandra::Client::InitializeStep.new(@options.compressor, @logger),
139
+ authentication_step,
140
+ Cassandra::Client::CachePropertiesStep.new,
141
+ ])
142
+ end
143
+
144
+ def create_additional_connections(host, count, established_connections, error = nil)
145
+ futures = count.times.map do
146
+ connect(host).recover do |e|
147
+ Cassandra::Client::FailedConnection.new(e, host)
148
+ end
149
+ end
150
+
151
+ Ione::Future.all(*futures).flat_map do |connections|
152
+ established_connections.select!(&:connected?)
153
+
154
+ connections.each do |connection|
155
+ if connection.connected?
156
+ established_connections << connection
157
+ else
158
+ error = connection.error
159
+ end
160
+ end
161
+
162
+ if !established_connections.empty?
163
+ connections_left = count - established_connections.size
164
+ if connections_left == 0
165
+ Ione::Future.resolved(established_connections)
166
+ else
167
+ create_additional_connections(host, connections_left, established_connections, error)
168
+ end
169
+ else
170
+ Ione::Future.failed(error)
171
+ end
172
+ end
173
+ end
174
+
175
+ def connected(host)
176
+ notify = false
177
+
178
+ synchronize do
179
+ connections = @connections[host]
180
+
181
+ if connections
182
+ @connections[host] = connections + 1
183
+ else
184
+ notify = true
185
+
186
+ @connections[host] = 1
187
+ end
188
+ end
189
+
190
+ @registry.host_up(host.ip) if notify
191
+
192
+ self
193
+ end
194
+
195
+ def disconnected(host, error)
196
+ notify = false
197
+
198
+ synchronize do
199
+ connections = @connections[host]
200
+
201
+ return self unless connections
202
+
203
+ connections -= 1
204
+
205
+ if connections == 0
206
+ notify = !error.nil? && !error.is_a?(Cassandra::Error)
207
+ @connections.delete(host)
208
+ else
209
+ @connections[host] = connections
210
+ end
211
+ end
212
+
213
+ @registry.host_down(host.ip) if notify
214
+
215
+ self
216
+ end
217
+
218
+ def connection_error(host, error)
219
+ notify = false
220
+
221
+ synchronize do
222
+ notify = !error.is_a?(Cassandra::Error) && !@connections.has_key?(host)
223
+ end
224
+
225
+ @registry.host_down(host.ip) if notify
226
+
227
+ self
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,420 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ class Cluster
21
+ # @private
22
+ class ControlConnection
23
+ include MonitorMixin
24
+
25
+ def initialize(logger, io_reactor, cluster_registry, cluster_schema, load_balancing_policy, reconnection_policy, connector)
26
+ @logger = logger
27
+ @io_reactor = io_reactor
28
+ @registry = cluster_registry
29
+ @schema = cluster_schema
30
+ @load_balancing_policy = load_balancing_policy
31
+ @reconnection_policy = reconnection_policy
32
+ @connector = connector
33
+ @refreshing_statuses = Hash.new(false)
34
+ @status = :closed
35
+
36
+ mon_initialize
37
+ end
38
+
39
+ def connect_async
40
+ synchronize do
41
+ return Ione::Future.resolved if @status == :connecting || @status == :connected
42
+ @status = :connecting
43
+ end
44
+
45
+ @logger.debug('Establishing control connection')
46
+
47
+ @io_reactor.start.flat_map do
48
+ plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
49
+ connect_to_first_available(plan)
50
+ end
51
+ end
52
+
53
+ def host_found(host)
54
+ end
55
+
56
+ def host_lost(host)
57
+ synchronize do
58
+ @refreshing_statuses.delete(host)
59
+ end
60
+ end
61
+
62
+ def host_up(host)
63
+ synchronize do
64
+ @refreshing_statuses.delete(host)
65
+
66
+ return connect_async if !@connection && !(@status == :closed || @status == :closed)
67
+ end
68
+
69
+ Ione::Future.resolved
70
+ end
71
+
72
+ def host_down(host)
73
+ synchronize do
74
+ return Ione::Future.resolved if @refreshing_statuses[host]
75
+
76
+ @logger.debug("Starting to continuously refresh status for ip=#{host.ip}")
77
+ @refreshing_statuses[host] = true
78
+ end
79
+
80
+ refresh_host_status(host).fallback do |e|
81
+ refresh_host_status_with_retry(host, @reconnection_policy.schedule)
82
+ end
83
+ end
84
+
85
+ def close_async
86
+ synchronize do
87
+ return Ione::Future.resolved if @status == :closing || @status == :closed
88
+ @status = :closing
89
+ end
90
+ @io_reactor.stop
91
+ end
92
+
93
+ def inspect
94
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)}>"
95
+ end
96
+
97
+ private
98
+
99
+ SELECT_LOCAL = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, release_version FROM system.local', nil, nil, :one)
100
+ SELECT_PEERS = Protocol::QueryRequest.new('SELECT peer, rack, data_center, host_id, rpc_address, release_version FROM system.peers', nil, nil, :one)
101
+ SELECT_KEYSPACES = Protocol::QueryRequest.new('SELECT * FROM system.schema_keyspaces', nil, nil, :one)
102
+ SELECT_TABLES = Protocol::QueryRequest.new('SELECT * FROM system.schema_columnfamilies', nil, nil, :one)
103
+ SELECT_COLUMNS = Protocol::QueryRequest.new('SELECT * FROM system.schema_columns', nil, nil, :one)
104
+ REGISTER = Protocol::RegisterRequest.new(
105
+ Protocol::TopologyChangeEventResponse::TYPE,
106
+ Protocol::StatusChangeEventResponse::TYPE,
107
+ Protocol::SchemaChangeEventResponse::TYPE
108
+ )
109
+
110
+ def reconnect_async(schedule)
111
+ timeout = schedule.next
112
+
113
+ @logger.debug("Reestablishing control connection in #{timeout} seconds")
114
+
115
+ f = @io_reactor.schedule_timer(timeout)
116
+ f = f.flat_map do
117
+ if synchronize { @status == :reconnecting }
118
+ @logger.debug('Reestablishing control connection')
119
+ plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
120
+ connect_to_first_available(plan)
121
+ else
122
+ @logger.debug('Stopping reconnection')
123
+ Ione::Future.resolved
124
+ end
125
+ end
126
+ f.fallback do
127
+ if synchronize { @status == :reconnecting }
128
+ reconnect_async(schedule)
129
+ else
130
+ @logger.debug('Stopping reconnection')
131
+ return Ione::Future.resolved
132
+ end
133
+ end
134
+ end
135
+
136
+ def register_async
137
+ connection = @connection
138
+
139
+ return Ione::Future.failed("not connected") if connection.nil?
140
+
141
+ @logger.debug('Registering for events')
142
+
143
+ connection.send_request(REGISTER).map do
144
+ @logger.debug('Registered for events')
145
+
146
+ connection.on_event do |event|
147
+ @logger.debug("Event received #{event}")
148
+
149
+ if event.type == 'SCHEMA_CHANGE'
150
+ case event.change
151
+ when 'CREATED'
152
+ if event.table.empty?
153
+ refresh_schema_async
154
+ else
155
+ refresh_keyspace_async(event.keyspace)
156
+ end
157
+ when 'DROPPED'
158
+ if event.table.empty?
159
+ refresh_schema_async
160
+ else
161
+ refresh_keyspace_async(event.keyspace)
162
+ end
163
+ when 'UPDATED'
164
+ if event.table.empty?
165
+ refresh_keyspace_async(event.keyspace)
166
+ else
167
+ refresh_table_async(event.keyspace, event.table)
168
+ end
169
+ end
170
+ else
171
+ case event.change
172
+ when 'UP'
173
+ address = event.address
174
+
175
+ refresh_host_async(address) if @registry.has_host?(address)
176
+ when 'DOWN'
177
+ @registry.host_down(event.address)
178
+ when 'NEW_NODE'
179
+ address = event.address
180
+
181
+ refresh_host_async(address) unless @registry.has_host?(address)
182
+ when 'REMOVED_NODE'
183
+ @registry.host_lost(event.address)
184
+ end
185
+ end
186
+ end
187
+
188
+ self
189
+ end
190
+ end
191
+
192
+ def refresh_schema_async
193
+ connection = @connection
194
+
195
+ return Ione::Future.failed("not connected") if connection.nil?
196
+
197
+ @logger.debug('Fetching schema metadata')
198
+
199
+ keyspaces = connection.send_request(SELECT_KEYSPACES)
200
+ tables = connection.send_request(SELECT_TABLES)
201
+ columns = connection.send_request(SELECT_COLUMNS)
202
+
203
+ Ione::Future.all(keyspaces, tables, columns).map do |(keyspaces, tables, columns)|
204
+ @logger.debug('Fetched schema metadata')
205
+
206
+ host = @registry.host(connection.host)
207
+
208
+ @schema.update_keyspaces(host, keyspaces.rows, tables.rows, columns.rows)
209
+ end
210
+ end
211
+
212
+ def refresh_keyspace_async(keyspace)
213
+ connection = @connection
214
+
215
+ return Ione::Future.failed("not connected") if connection.nil?
216
+
217
+ @logger.debug("Fetching keyspace #{keyspace.inspect} metadata")
218
+
219
+ params = [keyspace]
220
+ keyspaces = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?", params, nil, :one))
221
+ tables = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?", params, nil, :one))
222
+ columns = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = ?", params, nil, :one))
223
+
224
+ Ione::Future.all(keyspaces, tables, columns).map do |(keyspaces, tables, columns)|
225
+ @logger.debug("Fetched keyspace #{keyspace.inspect} metadata")
226
+
227
+ host = @registry.host(connection.host)
228
+
229
+ @schema.update_keyspace(host, keyspaces.rows.first, tables.rows, columns.rows)
230
+ end
231
+ end
232
+
233
+ def refresh_table_async(keyspace, table)
234
+ connection = @connection
235
+
236
+ return Ione::Future.failed("not connected") if connection.nil?
237
+
238
+ @logger.debug("Fetching table \"#{keyspace}.#{table}\" metadata")
239
+
240
+ params = [keyspace, table]
241
+ table = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ? AND columnfamily_name = ?", params, nil, :one))
242
+ columns = connection.send_request(Protocol::QueryRequest.new("SELECT * FROM system.schema_columns WHERE keyspace_name = ? AND columnfamily_name = ?", params, nil, :one))
243
+
244
+ Ione::Future.all(table, columns).map do |(table, columns)|
245
+ @logger.debug("Fetched table \"#{keyspace}.#{table}\" metadata")
246
+
247
+ host = @registry.host(connection.host)
248
+
249
+ @schema.udpate_table(host, keyspace, table.rows, columns.rows)
250
+ end
251
+ end
252
+
253
+ def refresh_hosts_async
254
+ connection = @connection
255
+
256
+ return Ione::Future.failed("not connected") if connection.nil?
257
+
258
+ @logger.debug('Fetching cluster metadata and peers')
259
+
260
+ local = connection.send_request(SELECT_LOCAL)
261
+ peers = connection.send_request(SELECT_PEERS)
262
+
263
+ Ione::Future.all(local, peers).flat_map do |(local, peers)|
264
+ local = local.rows
265
+ peers = peers.rows
266
+
267
+ @logger.debug('%d peers found' % peers.size)
268
+
269
+ raise NO_HOSTS if local.empty? && peers.empty?
270
+
271
+ local_ip = connection.host
272
+ ips = ::Set.new
273
+
274
+ unless local.empty?
275
+ ips << local_ip
276
+ @registry.host_found(IPAddr.new(local_ip), local.first)
277
+ end
278
+
279
+ peers.each do |data|
280
+ ip = peer_ip(data)
281
+ ips << ip.to_s
282
+ @registry.host_found(ip, data)
283
+ end
284
+
285
+ futures = []
286
+
287
+ @registry.each_host do |host|
288
+ if ips.include?(host.ip.to_s)
289
+ futures << refresh_host_status(host) if host.down?
290
+ else
291
+ @registry.host_lost(host.ip)
292
+ end
293
+ end
294
+
295
+ if futures.empty?
296
+ Ione::Future.resolved(self)
297
+ else
298
+ Ione::Future.all(*futures)
299
+ end
300
+ end
301
+ end
302
+
303
+ def refresh_host_status(host)
304
+ @connector.refresh_status(host)
305
+ end
306
+
307
+ def refresh_host_status_with_retry(host, schedule)
308
+ timeout = schedule.next
309
+
310
+ @logger.info("Refreshing host status refresh ip=#{host.ip} in #{timeout}")
311
+
312
+ f = @io_reactor.schedule_timer(timeout)
313
+ f.flat_map do
314
+ if synchronize { @refreshing_statuses[host] }
315
+ refresh_host_status(host).fallback do |e|
316
+ refresh_host_status_with_retry(host, schedule)
317
+ end
318
+ else
319
+ Ione::Future.resolved
320
+ end
321
+ end
322
+ end
323
+
324
+ def refresh_host_async(address)
325
+ connection = @connection
326
+ return Ione::Future.failed("not connected") if connection.nil?
327
+
328
+ ip = address.to_s
329
+
330
+ @logger.debug('Fetching node information for %s' % ip)
331
+
332
+ if ip == connection.host
333
+ request = SELECT_LOCAL
334
+ else
335
+ request = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, rpc_address, release_version FROM system.peers WHERE peer = ?', [address], nil, :one)
336
+ end
337
+
338
+ connection.send_request(request).map do |response|
339
+ @logger.debug('Fetched node information for %s' % ip)
340
+
341
+ rows = response.rows
342
+
343
+ unless rows.empty?
344
+ @registry.host_found(address, rows.first)
345
+ host = @registry.host(address)
346
+ refresh_host_status(host) if host.down?
347
+ end
348
+
349
+ self
350
+ end
351
+ end
352
+
353
+ def connect_to_first_available(plan, errors = nil)
354
+ unless plan.has_next?
355
+ @logger.warn("Control connection failed")
356
+ return Ione::Future.failed(Errors::NoHostsAvailable.new(errors || {}))
357
+ end
358
+
359
+ host = plan.next
360
+ @logger.debug("Attempting connection to ip=#{host.ip}")
361
+ f = connect_to_host(host)
362
+ f = f.flat_map do |connection|
363
+ synchronize do
364
+ @status = :connected
365
+
366
+ @logger.debug("Control connection established ip=#{connection.host}")
367
+ @connection = connection
368
+
369
+ connection.on_closed do
370
+ reconnect = false
371
+
372
+ synchronize do
373
+ if @status == :closing
374
+ @status = :closed
375
+ else
376
+ @status = :reconnecting
377
+ reconnect = true
378
+ end
379
+
380
+ @logger.debug("Control connection closed ip=#{connection.host}")
381
+ @connection = nil
382
+ end
383
+
384
+ reconnect_async(@reconnection_policy.schedule) if reconnect
385
+ end
386
+ end
387
+
388
+ register_async
389
+ end
390
+ f = f.flat_map { refresh_hosts_async }
391
+ f = f.flat_map { refresh_schema_async }
392
+ f.fallback do |error|
393
+ if error.is_a?(Errors::AuthenticationError)
394
+ Ione::Future.failed(error)
395
+ elsif error.is_a?(Errors::QueryError)
396
+ if error.code == 0x100
397
+ Ione::Future.failed(Errors::AuthenticationError.new(error.message))
398
+ else
399
+ Ione::Future.failed(error)
400
+ end
401
+ else
402
+ errors ||= {}
403
+ errors[host] = error
404
+ connect_to_first_available(plan, errors)
405
+ end
406
+ end
407
+ end
408
+
409
+ def connect_to_host(host)
410
+ @connector.connect(host)
411
+ end
412
+
413
+ def peer_ip(data)
414
+ ip = data['rpc_address']
415
+ ip = data['peer'] if ip == '0.0.0.0'
416
+ ip
417
+ end
418
+ end
419
+ end
420
+ end