yugabyte-ycql-driver 3.2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/README.md +242 -0
  4. data/ext/cassandra_murmur3/cassandra_murmur3.c +178 -0
  5. data/ext/cassandra_murmur3/extconf.rb +2 -0
  6. data/lib/cassandra/address_resolution.rb +36 -0
  7. data/lib/cassandra/address_resolution/policies.rb +2 -0
  8. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
  9. data/lib/cassandra/address_resolution/policies/none.rb +35 -0
  10. data/lib/cassandra/aggregate.rb +123 -0
  11. data/lib/cassandra/argument.rb +51 -0
  12. data/lib/cassandra/attr_boolean.rb +33 -0
  13. data/lib/cassandra/auth.rb +100 -0
  14. data/lib/cassandra/auth/providers.rb +17 -0
  15. data/lib/cassandra/auth/providers/password.rb +65 -0
  16. data/lib/cassandra/cassandra_logger.rb +80 -0
  17. data/lib/cassandra/cluster.rb +331 -0
  18. data/lib/cassandra/cluster/client.rb +1612 -0
  19. data/lib/cassandra/cluster/connection_pool.rb +78 -0
  20. data/lib/cassandra/cluster/connector.rb +372 -0
  21. data/lib/cassandra/cluster/control_connection.rb +962 -0
  22. data/lib/cassandra/cluster/failed_connection.rb +35 -0
  23. data/lib/cassandra/cluster/metadata.rb +142 -0
  24. data/lib/cassandra/cluster/options.rb +145 -0
  25. data/lib/cassandra/cluster/registry.rb +284 -0
  26. data/lib/cassandra/cluster/schema.rb +405 -0
  27. data/lib/cassandra/cluster/schema/cql_type_parser.rb +112 -0
  28. data/lib/cassandra/cluster/schema/fetchers.rb +1627 -0
  29. data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +175 -0
  30. data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
  31. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +45 -0
  32. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
  33. data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
  34. data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
  35. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +102 -0
  36. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
  37. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
  38. data/lib/cassandra/column.rb +66 -0
  39. data/lib/cassandra/column_container.rb +326 -0
  40. data/lib/cassandra/compression.rb +69 -0
  41. data/lib/cassandra/compression/compressors/lz4.rb +73 -0
  42. data/lib/cassandra/compression/compressors/snappy.rb +69 -0
  43. data/lib/cassandra/custom_data.rb +53 -0
  44. data/lib/cassandra/driver.rb +260 -0
  45. data/lib/cassandra/errors.rb +784 -0
  46. data/lib/cassandra/execution/info.rb +69 -0
  47. data/lib/cassandra/execution/options.rb +267 -0
  48. data/lib/cassandra/execution/profile.rb +153 -0
  49. data/lib/cassandra/execution/profile_manager.rb +71 -0
  50. data/lib/cassandra/execution/trace.rb +192 -0
  51. data/lib/cassandra/executors.rb +113 -0
  52. data/lib/cassandra/function.rb +156 -0
  53. data/lib/cassandra/function_collection.rb +85 -0
  54. data/lib/cassandra/future.rb +794 -0
  55. data/lib/cassandra/host.rb +102 -0
  56. data/lib/cassandra/index.rb +118 -0
  57. data/lib/cassandra/keyspace.rb +473 -0
  58. data/lib/cassandra/listener.rb +87 -0
  59. data/lib/cassandra/load_balancing.rb +121 -0
  60. data/lib/cassandra/load_balancing/policies.rb +20 -0
  61. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +172 -0
  62. data/lib/cassandra/load_balancing/policies/round_robin.rb +141 -0
  63. data/lib/cassandra/load_balancing/policies/token_aware.rb +149 -0
  64. data/lib/cassandra/load_balancing/policies/white_list.rb +100 -0
  65. data/lib/cassandra/materialized_view.rb +92 -0
  66. data/lib/cassandra/null_logger.rb +56 -0
  67. data/lib/cassandra/protocol.rb +102 -0
  68. data/lib/cassandra/protocol/coder.rb +1085 -0
  69. data/lib/cassandra/protocol/cql_byte_buffer.rb +418 -0
  70. data/lib/cassandra/protocol/cql_protocol_handler.rb +448 -0
  71. data/lib/cassandra/protocol/request.rb +41 -0
  72. data/lib/cassandra/protocol/requests/auth_response_request.rb +51 -0
  73. data/lib/cassandra/protocol/requests/batch_request.rb +117 -0
  74. data/lib/cassandra/protocol/requests/credentials_request.rb +51 -0
  75. data/lib/cassandra/protocol/requests/execute_request.rb +122 -0
  76. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  77. data/lib/cassandra/protocol/requests/prepare_request.rb +59 -0
  78. data/lib/cassandra/protocol/requests/query_request.rb +112 -0
  79. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  80. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  81. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  82. data/lib/cassandra/protocol/response.rb +28 -0
  83. data/lib/cassandra/protocol/responses/already_exists_error_response.rb +50 -0
  84. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +36 -0
  85. data/lib/cassandra/protocol/responses/auth_success_response.rb +36 -0
  86. data/lib/cassandra/protocol/responses/authenticate_response.rb +36 -0
  87. data/lib/cassandra/protocol/responses/error_response.rb +142 -0
  88. data/lib/cassandra/protocol/responses/event_response.rb +30 -0
  89. data/lib/cassandra/protocol/responses/function_failure_error_response.rb +52 -0
  90. data/lib/cassandra/protocol/responses/prepared_result_response.rb +62 -0
  91. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +59 -0
  92. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +71 -0
  93. data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +61 -0
  94. data/lib/cassandra/protocol/responses/ready_response.rb +43 -0
  95. data/lib/cassandra/protocol/responses/result_response.rb +42 -0
  96. data/lib/cassandra/protocol/responses/rows_result_response.rb +39 -0
  97. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +73 -0
  98. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +70 -0
  99. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +37 -0
  100. data/lib/cassandra/protocol/responses/status_change_event_response.rb +39 -0
  101. data/lib/cassandra/protocol/responses/supported_response.rb +36 -0
  102. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +33 -0
  103. data/lib/cassandra/protocol/responses/unavailable_error_response.rb +58 -0
  104. data/lib/cassandra/protocol/responses/unprepared_error_response.rb +48 -0
  105. data/lib/cassandra/protocol/responses/void_result_response.rb +34 -0
  106. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +73 -0
  107. data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +63 -0
  108. data/lib/cassandra/protocol/v1.rb +326 -0
  109. data/lib/cassandra/protocol/v3.rb +358 -0
  110. data/lib/cassandra/protocol/v4.rb +478 -0
  111. data/lib/cassandra/reconnection.rb +49 -0
  112. data/lib/cassandra/reconnection/policies.rb +20 -0
  113. data/lib/cassandra/reconnection/policies/constant.rb +46 -0
  114. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  115. data/lib/cassandra/result.rb +276 -0
  116. data/lib/cassandra/retry.rb +154 -0
  117. data/lib/cassandra/retry/policies.rb +21 -0
  118. data/lib/cassandra/retry/policies/default.rb +53 -0
  119. data/lib/cassandra/retry/policies/downgrading_consistency.rb +73 -0
  120. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  121. data/lib/cassandra/session.rb +270 -0
  122. data/lib/cassandra/statement.rb +32 -0
  123. data/lib/cassandra/statements.rb +23 -0
  124. data/lib/cassandra/statements/batch.rb +146 -0
  125. data/lib/cassandra/statements/bound.rb +65 -0
  126. data/lib/cassandra/statements/prepared.rb +235 -0
  127. data/lib/cassandra/statements/simple.rb +118 -0
  128. data/lib/cassandra/statements/void.rb +38 -0
  129. data/lib/cassandra/table.rb +240 -0
  130. data/lib/cassandra/time.rb +103 -0
  131. data/lib/cassandra/time_uuid.rb +78 -0
  132. data/lib/cassandra/timestamp_generator.rb +37 -0
  133. data/lib/cassandra/timestamp_generator/simple.rb +38 -0
  134. data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
  135. data/lib/cassandra/trigger.rb +67 -0
  136. data/lib/cassandra/tuple.rb +131 -0
  137. data/lib/cassandra/types.rb +1704 -0
  138. data/lib/cassandra/udt.rb +443 -0
  139. data/lib/cassandra/util.rb +464 -0
  140. data/lib/cassandra/uuid.rb +110 -0
  141. data/lib/cassandra/uuid/generator.rb +212 -0
  142. data/lib/cassandra/version.rb +21 -0
  143. data/lib/datastax/cassandra.rb +47 -0
  144. data/lib/ycql.rb +842 -0
  145. metadata +243 -0
@@ -0,0 +1,962 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 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,
26
+ cluster_metadata, load_balancing_policy,
27
+ reconnection_policy, address_resolution_policy, connector,
28
+ connection_options, schema_fetcher)
29
+ @logger = logger
30
+ @io_reactor = io_reactor
31
+ @registry = cluster_registry
32
+ @schema = cluster_schema
33
+ @metadata = cluster_metadata
34
+ @load_balancing_policy = load_balancing_policy
35
+ @reconnection_policy = reconnection_policy
36
+ @address_resolver = address_resolution_policy
37
+ @connector = connector
38
+ @connection_options = connection_options
39
+ @connection = nil
40
+ @schema_fetcher = schema_fetcher
41
+ @refreshing_statuses = ::Hash.new(false)
42
+ @refresh_schema_future = nil
43
+ @status = :closed
44
+ @refreshing_hosts = false
45
+ @refreshing_host = ::Hash.new(false)
46
+ @closed_promise = Ione::Promise.new
47
+ @schema_changes = ::Array.new
48
+ @schema_refresh_timer = nil
49
+ @schema_refresh_window = nil
50
+
51
+ mon_initialize
52
+ end
53
+
54
+ def on_close(&block)
55
+ @closed_promise.future.on_value(&block)
56
+ @closed_promise.future.on_failure(&block)
57
+ end
58
+
59
+ def connect_async
60
+ synchronize do
61
+ return Ione::Future.resolved if @status == :connecting || @status == :connected
62
+ @status = :connecting
63
+ end
64
+
65
+ f = @io_reactor.start.flat_map do
66
+ plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
67
+ connect_to_first_available(plan)
68
+ end
69
+ f
70
+ end
71
+
72
+ def host_found(host)
73
+ end
74
+
75
+ def host_lost(host)
76
+ synchronize do
77
+ timer = @refreshing_statuses.delete(host)
78
+ @io_reactor.cancel_timer(timer) if timer
79
+ end
80
+
81
+ nil
82
+ end
83
+
84
+ def host_up(host)
85
+ synchronize do
86
+ timer = @refreshing_statuses.delete(host)
87
+ @io_reactor.cancel_timer(timer) if timer
88
+
89
+ unless @connection ||
90
+ (@status == :closing || @status == :closed) ||
91
+ @load_balancing_policy.distance(host) == :ignore
92
+ return connect_to_first_available(
93
+ @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
94
+ )
95
+ end
96
+ end
97
+
98
+ Ione::Future.resolved
99
+ end
100
+
101
+ def host_down(host)
102
+ schedule = nil
103
+ timer = nil
104
+
105
+ synchronize do
106
+ if @refreshing_statuses[host] ||
107
+ @load_balancing_policy.distance(host) == :ignore
108
+ return Ione::Future.resolved
109
+ end
110
+
111
+ schedule = @reconnection_policy.schedule
112
+ timeout = schedule.next
113
+
114
+ @logger.debug("Starting to continuously refresh status of #{host.ip} in " \
115
+ "#{timeout} seconds")
116
+
117
+ @refreshing_statuses[host] = timer = @io_reactor.schedule_timer(timeout)
118
+ end
119
+
120
+ timer.on_value do
121
+ refresh_host_status(host).fallback do |_e|
122
+ refresh_host_status_with_retry(timer, host, schedule)
123
+ end
124
+ end
125
+
126
+ nil
127
+ end
128
+
129
+ def close_async
130
+ synchronize do
131
+ return @closed_promise.future if @status == :closing || @status == :closed
132
+ @status = :closing
133
+ end
134
+ f = @io_reactor.stop
135
+
136
+ f.on_value(&method(:connection_closed))
137
+ f.on_failure(&method(:connection_closed))
138
+
139
+ @closed_promise.future
140
+ end
141
+
142
+ def connection_closed(cause)
143
+ @closed_promise.fulfill
144
+ end
145
+
146
+ def inspect
147
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
148
+ end
149
+
150
+ private
151
+
152
+ SELECT_LOCAL = Protocol::QueryRequest.new(
153
+ 'SELECT * ' \
154
+ 'FROM system.local',
155
+ EMPTY_LIST,
156
+ EMPTY_LIST,
157
+ :quorum
158
+ )
159
+ SELECT_PEERS = Protocol::QueryRequest.new(
160
+ 'SELECT * ' \
161
+ 'FROM system.peers',
162
+ EMPTY_LIST,
163
+ EMPTY_LIST,
164
+ :quorum
165
+ )
166
+
167
+ SELECT_PEER_QUERY =
168
+ 'SELECT * ' \
169
+ 'FROM system.peers ' \
170
+ "WHERE peer = '%s'".freeze
171
+
172
+ def reconnect_async(schedule)
173
+ timeout = schedule.next
174
+
175
+ @logger.debug("Reestablishing control connection in #{timeout} seconds")
176
+
177
+ f = @io_reactor.schedule_timer(timeout)
178
+ f = f.flat_map do
179
+ if synchronize { @status == :reconnecting }
180
+ plan = @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)
181
+ connect_to_first_available(plan)
182
+ else
183
+ Ione::Future.resolved
184
+ end
185
+ end
186
+ f.fallback do |e|
187
+ @logger.error("Control connection failed (#{e.class.name}: #{e.message})")
188
+
189
+ return Ione::Future.resolved unless synchronize { @status == :reconnecting }
190
+
191
+ # We're reconnecting...
192
+ reconnect_async(schedule)
193
+ end
194
+ end
195
+
196
+ def register_async
197
+ connection = @connection
198
+
199
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
200
+
201
+ request = Protocol::RegisterRequest.new(
202
+ Protocol::TopologyChangeEventResponse::TYPE,
203
+ Protocol::StatusChangeEventResponse::TYPE
204
+ )
205
+
206
+ request.events << Protocol::SchemaChangeEventResponse::TYPE if @connection_options.synchronize_schema?
207
+
208
+ f = connection.send_request(request)
209
+ f = f.map do |r|
210
+ case r
211
+ when Protocol::ReadyResponse
212
+ nil
213
+ when Protocol::ErrorResponse
214
+ raise r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :quorum, 0)
215
+ else
216
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
217
+ end
218
+ end
219
+ f.map do
220
+ connection.on_event do |event|
221
+ @logger.debug("Event received #{event}")
222
+
223
+ if event.type == 'SCHEMA_CHANGE'
224
+ handle_schema_change(event)
225
+ else
226
+ case event.change
227
+ when 'UP'
228
+ address = @address_resolver.resolve(event.address)
229
+ if @registry.has_host?(address)
230
+ @registry.host_up(address)
231
+ else
232
+ refresh_host_async_maybe_retry(event.address)
233
+ refresh_schema_async_wrapper
234
+ end
235
+ when 'DOWN'
236
+ # RUBY-164: Don't mark host down if there are active connections. We have
237
+ # logic in connector.rb to call host_down when all connections to a node are lost,
238
+ # so that covers the requirement.
239
+ when 'NEW_NODE'
240
+ address = @address_resolver.resolve(event.address)
241
+
242
+ unless @registry.has_host?(address)
243
+ refresh_host_async_maybe_retry(event.address)
244
+ refresh_schema_async_wrapper
245
+ end
246
+ when 'REMOVED_NODE'
247
+ @registry.host_lost(@address_resolver.resolve(event.address))
248
+ refresh_schema_async_wrapper
249
+ end
250
+ end
251
+ end
252
+
253
+ self
254
+ end
255
+ end
256
+
257
+ def refresh_schema_async
258
+ connection = @connection
259
+
260
+ @logger.info('Refreshing schema')
261
+
262
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
263
+
264
+ @schema_fetcher.fetch(connection).map do |keyspaces|
265
+ @schema.replace(keyspaces)
266
+ @metadata.rebuild_token_map
267
+ @logger.info('Schema refreshed')
268
+ end
269
+ end
270
+
271
+ def refresh_keyspace_async(keyspace_name)
272
+ connection = @connection
273
+
274
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
275
+
276
+ @logger.info("Refreshing keyspace \"#{keyspace_name}\"")
277
+
278
+ @schema_fetcher.fetch_keyspace(connection, keyspace_name).map do |keyspace|
279
+ if keyspace
280
+ @schema.replace_keyspace(keyspace)
281
+ else
282
+ @schema.delete_keyspace(keyspace_name)
283
+ end
284
+
285
+ @logger.info("Completed refreshing keyspace \"#{keyspace_name}\"")
286
+ end
287
+ end
288
+
289
+ def refresh_table_async(keyspace_name, table_name)
290
+ connection = @connection
291
+
292
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
293
+
294
+ @logger.info("Refreshing table \"#{keyspace_name}.#{table_name}\"")
295
+
296
+ @schema_fetcher.fetch_table(connection, keyspace_name, table_name).map do |table|
297
+ if table
298
+ @schema.replace_table(table)
299
+ else
300
+ @schema.delete_table(keyspace_name, table_name)
301
+ end
302
+
303
+ @logger.info("Completed refreshing table \"#{keyspace_name}.#{table_name}\"")
304
+ end
305
+ end
306
+
307
+ def refresh_materialized_view_async(keyspace_name, view_name)
308
+ connection = @connection
309
+
310
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
311
+
312
+ @logger.info("Refreshing materialized view \"#{keyspace_name}.#{view_name}\"")
313
+
314
+ @schema_fetcher.fetch_materialized_view(connection, keyspace_name, view_name).map do |view|
315
+ if view
316
+ @schema.replace_materialized_view(view)
317
+ else
318
+ @schema.delete_materialized_view(keyspace_name, view_name)
319
+ end
320
+
321
+ @logger.info("Completed refreshing materialized view \"#{keyspace_name}.#{view_name}\"")
322
+ end
323
+ end
324
+
325
+ def refresh_type_async(keyspace_name, type_name)
326
+ connection = @connection
327
+
328
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
329
+
330
+ @logger.info("Refreshing user-defined type \"#{keyspace_name}.#{type_name}\"")
331
+
332
+ @schema_fetcher.fetch_type(connection, keyspace_name, type_name).map do |type|
333
+ if type
334
+ @schema.replace_type(type)
335
+ else
336
+ @schema.delete_type(keyspace_name, type_name)
337
+ end
338
+
339
+ @logger.info("Completed refreshing user-defined type \"#{keyspace_name}.#{type_name}\"")
340
+ end
341
+ end
342
+
343
+ def refresh_function_async(keyspace_name, function_name, function_args)
344
+ connection = @connection
345
+
346
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
347
+
348
+ @logger.info('Refreshing user-defined function ' \
349
+ "\"#{keyspace_name}.#{function_name}\"")
350
+
351
+ # function_args is an array of string, and we need an array of parsed types.
352
+ parsed_function_args =
353
+ @schema_fetcher.parse_argument_types(connection, keyspace_name, function_args)
354
+ @schema_fetcher.fetch_function(connection,
355
+ keyspace_name,
356
+ function_name,
357
+ parsed_function_args).map do |function|
358
+ if function
359
+ @schema.replace_function(function)
360
+ else
361
+ @schema.delete_function(keyspace_name, function_name, parsed_function_args)
362
+ end
363
+
364
+ @logger.info('Completed refreshing user-defined function ' \
365
+ "\"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"")
366
+ end
367
+ end
368
+
369
+ def refresh_aggregate_async(keyspace_name, aggregate_name, aggregate_args)
370
+ connection = @connection
371
+
372
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
373
+
374
+ @logger.info('Refreshing user-defined aggregate ' \
375
+ "\"#{keyspace_name}.#{aggregate_name}\"")
376
+
377
+ # aggregate_args is an array of string, and we need an array of parsed types.
378
+ parsed_aggregate_args = @schema_fetcher.parse_argument_types(connection,
379
+ keyspace_name,
380
+ aggregate_args)
381
+ @schema_fetcher.fetch_aggregate(connection,
382
+ keyspace_name,
383
+ aggregate_name,
384
+ parsed_aggregate_args).map do |aggregate|
385
+ if aggregate
386
+ @schema.replace_aggregate(aggregate)
387
+ else
388
+ @schema.delete_aggregate(keyspace_name, aggregate_name, parsed_aggregate_args)
389
+ end
390
+
391
+ @logger.info('Completed refreshing user-defined aggregate ' \
392
+ "\"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"")
393
+ end
394
+ end
395
+
396
+ def refresh_peers_async_maybe_retry
397
+ synchronize do
398
+ return Ione::Future.resolved if @refreshing_hosts
399
+ @refreshing_hosts = true
400
+ end
401
+
402
+ refresh_peers_async.fallback do |e|
403
+ case e
404
+ when Errors::HostError, Errors::TimeoutError
405
+ refresh_peers_async_retry(e, @reconnection_policy.schedule)
406
+ else
407
+ connection = @connection
408
+ connection && connection.close(e)
409
+
410
+ Ione::Future.failed(e)
411
+ end
412
+ end.map do
413
+ synchronize do
414
+ @refreshing_hosts = false
415
+ end
416
+ end
417
+ end
418
+
419
+ def refresh_peers_async_retry(error, schedule)
420
+ timeout = schedule.next
421
+ @logger.info("Failed to refresh hosts (#{error.class.name}: " \
422
+ "#{error.message}), retrying in #{timeout}")
423
+
424
+ timer = @io_reactor.schedule_timer(timeout)
425
+ timer.flat_map do
426
+ refresh_peers_async.fallback do |e|
427
+ case e
428
+ when Errors::HostError, Errors::TimeoutError
429
+ refresh_peers_async_retry(e, schedule)
430
+ else
431
+ connection = @connection
432
+ connection && connection.close(e)
433
+
434
+ Ione::Future.failed(e)
435
+ end
436
+ end
437
+ end
438
+ end
439
+
440
+ def refresh_peers_async
441
+ connection = @connection
442
+
443
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
444
+
445
+ @logger.info('Refreshing peers metadata')
446
+
447
+ send_select_request(connection, SELECT_PEERS).map do |peers|
448
+ @logger.debug("#{peers.size} peer(s) found")
449
+
450
+ ips = ::Set.new
451
+
452
+ peers.shuffle!
453
+ peers.each do |data|
454
+ ip = peer_ip(data, connection.host)
455
+ next unless ip
456
+ ips << ip
457
+ @registry.host_found(ip, data)
458
+ end
459
+
460
+ @registry.each_host do |host|
461
+ next if host.ip == connection.host
462
+ @registry.host_lost(host.ip) unless ips.include?(host.ip)
463
+ end
464
+
465
+ @logger.info('Completed refreshing peers metadata')
466
+
467
+ nil
468
+ end
469
+ end
470
+
471
+ def refresh_metadata_async
472
+ connection = @connection
473
+
474
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
475
+
476
+ @logger.info("Refreshing connected host's metadata")
477
+
478
+ send_select_request(connection, SELECT_LOCAL).map do |local|
479
+ raise Errors::InternalError, "Unable to fetch connected host's metadata" if local.empty?
480
+
481
+ data = local.first
482
+ @registry.host_found(@address_resolver.resolve(IPAddr.new(connection.host)), data)
483
+ @metadata.update(data)
484
+
485
+ @logger.info("Completed refreshing connected host's metadata")
486
+
487
+ nil
488
+ end
489
+ end
490
+
491
+ def refresh_host_status(host)
492
+ @connector.refresh_status(host)
493
+ end
494
+
495
+ def refresh_host_status_with_retry(original_timer, host, schedule)
496
+ timer = nil
497
+
498
+ synchronize do
499
+ timer = @refreshing_statuses[host]
500
+
501
+ # host must have been lost/up or timer was rescheduled
502
+ return Ione::Future.resolved if timer.nil? || timer != original_timer
503
+
504
+ timeout = schedule.next
505
+
506
+ @logger.debug("Checking host #{host.ip} in #{timeout} seconds")
507
+
508
+ @refreshing_statuses[host] = timer = @io_reactor.schedule_timer(timeout)
509
+ end
510
+
511
+ timer.on_value do
512
+ refresh_host_status(host).fallback do |_e|
513
+ refresh_host_status_with_retry(timer, host, schedule)
514
+ end
515
+ end
516
+ end
517
+
518
+ # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved
519
+ # relative to the client, but rather is the address that other nodes would use to communicate with
520
+ # this node.
521
+ def refresh_host_async_maybe_retry(address)
522
+ synchronize do
523
+ return Ione::Future.resolved if @refreshing_hosts || @refreshing_host[address]
524
+ @refreshing_host[address] = true
525
+ end
526
+
527
+ refresh_host_async(address).fallback do |e|
528
+ case e
529
+ when Errors::HostError, Errors::TimeoutError
530
+ refresh_host_async_retry(address, e, @reconnection_policy.schedule)
531
+ else
532
+ connection = @connection
533
+ connection && connection.close(e)
534
+
535
+ Ione::Future.failed(e)
536
+ end
537
+ end.map do
538
+ synchronize do
539
+ @refreshing_host.delete(address)
540
+ end
541
+ end
542
+ end
543
+
544
+ # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved
545
+ # relative to the client, but rather is the address that other nodes would use to communicate with
546
+ # this node.
547
+ def refresh_host_async_retry(address, error, schedule)
548
+ timeout = schedule.next
549
+ @logger.info("Failed to refresh host #{address} (#{error.class.name}: " \
550
+ "#{error.message}), retrying in #{timeout}")
551
+
552
+ timer = @io_reactor.schedule_timer(timeout)
553
+ timer.flat_map do
554
+ refresh_host_async(address).fallback do |e|
555
+ case e
556
+ when Errors::HostError, Errors::TimeoutError
557
+ refresh_host_async_retry(address, e, schedule)
558
+ else
559
+ connection = @connection
560
+ connection && connection.close(e)
561
+
562
+ Ione::Future.failed(e)
563
+ end
564
+ end
565
+ end
566
+ end
567
+
568
+ # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved
569
+ # relative to the client, but rather is the address that other nodes would use to communicate with
570
+ # this node.
571
+ def refresh_host_async(address)
572
+ connection = @connection
573
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
574
+
575
+ ip = address.to_s
576
+
577
+ @logger.info("Refreshing host metadata: #{ip}")
578
+
579
+ request = if ip == connection.host
580
+ SELECT_LOCAL
581
+ else
582
+ Protocol::QueryRequest.new(SELECT_PEER_QUERY % ip,
583
+ EMPTY_LIST,
584
+ EMPTY_LIST,
585
+ :quorum)
586
+ end
587
+
588
+ send_select_request(connection, request).map do |rows|
589
+ raise Errors::InternalError, "Unable to find host metadata: #{ip}" if rows.empty?
590
+
591
+ @logger.info("Completed refreshing host metadata: #{ip}")
592
+ address = if ip == connection.host
593
+ @address_resolver.resolve(address)
594
+ else
595
+ peer_ip(rows.first, connection.host)
596
+ end
597
+ @registry.host_found(address, rows.first)
598
+
599
+ self
600
+ end
601
+ rescue => e
602
+ @logger.error("Refreshing host metadata failed (#{e.class.name}: #{e.message})")
603
+ end
604
+
605
+ def connect_to_first_available(plan, errors = nil)
606
+ unless plan.has_next?
607
+ if errors.nil? && synchronize { @refreshing_statuses.empty? }
608
+ @logger.fatal(<<-MSG)
609
+ Control connection failed and is unlikely to recover.
610
+
611
+ This usually means that all hosts are ignored by current load
612
+ balancing policy, most likely because they changed datacenters.
613
+ Reconnections attempts will continue getting scheduled to
614
+ repeat this message in the logs.
615
+ MSG
616
+ end
617
+
618
+ return Ione::Future.failed(Errors::NoHostsAvailable.new(errors))
619
+ end
620
+
621
+ host = plan.next
622
+ @logger.debug("Connecting to #{host.ip}")
623
+
624
+ f = connect_to_host(host)
625
+ f = f.flat_map do |connection|
626
+ synchronize do
627
+ @status = :connected
628
+
629
+ @connection = connection
630
+
631
+ connection.on_closed do |cause|
632
+ reconnect = false
633
+
634
+ synchronize do
635
+ if connection == @connection
636
+ if @status == :closing
637
+ @status = :closed
638
+ else
639
+ @status = :reconnecting
640
+ reconnect = true
641
+ end
642
+
643
+ if cause
644
+ @logger.info('Control connection closed ' \
645
+ "(#{cause.class.name}: #{cause.message})")
646
+ else
647
+ @logger.info('Control connection closed')
648
+ end
649
+
650
+ @connection = nil
651
+ end
652
+ end
653
+
654
+ reconnect_async(@reconnection_policy.schedule) if reconnect
655
+ end
656
+ end
657
+
658
+ refresh_maybe_retry(:metadata)
659
+ end
660
+ f = f.flat_map { register_async }
661
+ f = f.flat_map { refresh_peers_async_maybe_retry }
662
+ f = f.flat_map { refresh_maybe_retry(:schema) } if @connection_options.synchronize_schema?
663
+ f = f.fallback do |error|
664
+ @logger.debug("Connection to #{host.ip} failed " \
665
+ "(#{error.class.name}: #{error.message})")
666
+
667
+ case error
668
+ when Errors::HostError, Errors::TimeoutError
669
+ errors ||= {}
670
+ errors[host] = error
671
+ connect_to_first_available(plan, errors)
672
+ else
673
+ Ione::Future.failed(error)
674
+ end
675
+ end
676
+
677
+ f.on_complete do |connection_future|
678
+ @logger.info('Control connection established') if connection_future.resolved?
679
+ end
680
+
681
+ f
682
+ end
683
+
684
+ def connect_to_host(host)
685
+ @connector.connect(host)
686
+ end
687
+
688
+ def peer_ip(data, host_address)
689
+ peer = data['peer']
690
+
691
+ return nil unless peer && data['host_id'] && data['data_center'] && data['rack'] && data['tokens']
692
+
693
+ rpc_address = data['rpc_address']
694
+
695
+ if rpc_address.nil?
696
+ @logger.info("The system.peers row for '#{data['peer']}' has no rpc_address. This is likely " \
697
+ 'a gossip or snitch issue. This host will be ignored.')
698
+ return nil
699
+ end
700
+
701
+ if peer == host_address || rpc_address == host_address
702
+ # Some DSE versions were inserting a line for the local node in peers (with mostly null values).
703
+ # This has been fixed, but if we detect that's the case, ignore it as it's not really a big deal.
704
+
705
+ @logger.debug("System.peers on node #{host_address} has a line for itself. This is not normal but is a " \
706
+ 'known problem of some DSE versions. Ignoring the entry.')
707
+ return nil
708
+ end
709
+
710
+ ip = rpc_address
711
+ ip = peer if ip == '0.0.0.0'
712
+
713
+ @address_resolver.resolve(ip)
714
+ end
715
+
716
+ def process_schema_changes(schema_changes)
717
+ refresh_keyspaces = ::Hash.new
718
+ refresh_tables_and_views = ::Hash.new
719
+ refresh_types = ::Hash.new
720
+
721
+ # This hash is of the form <keyspace, [Change (for function changes)]>
722
+ refresh_functions = ::Hash.new
723
+
724
+ # This hash is of the form <keyspace, [Change (for aggregate changes)]>
725
+ refresh_aggregates = ::Hash.new
726
+
727
+ schema_changes.each do |change|
728
+ keyspace = change.keyspace
729
+
730
+ next if refresh_keyspaces.key?(keyspace)
731
+
732
+ case change.target
733
+ when Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
734
+ refresh_tables_and_views.delete(keyspace)
735
+ refresh_types.delete(keyspace)
736
+ refresh_functions.delete(keyspace)
737
+ refresh_aggregates.delete(keyspace)
738
+ refresh_keyspaces[keyspace] = true
739
+ when Protocol::Constants::SCHEMA_CHANGE_TARGET_TABLE
740
+ # We can't distinguish between table and view change events, so refresh both.
741
+ tables_and_views = refresh_tables_and_views[keyspace] ||= ::Hash.new
742
+ tables_and_views[change.name] = true
743
+ when Protocol::Constants::SCHEMA_CHANGE_TARGET_UDT
744
+ types = refresh_types[keyspace] ||= ::Hash.new
745
+ types[change.name] = true
746
+ when Protocol::Constants::SCHEMA_CHANGE_TARGET_FUNCTION
747
+ functions = refresh_functions[keyspace] ||= []
748
+ functions << change
749
+ when Protocol::Constants::SCHEMA_CHANGE_TARGET_AGGREGATE
750
+ aggregates = refresh_aggregates[keyspace] ||= []
751
+ aggregates << change
752
+ end
753
+ end
754
+
755
+ futures = ::Array.new
756
+
757
+ refresh_keyspaces.each_key do |keyspace|
758
+ futures << refresh_maybe_retry(:keyspace, keyspace)
759
+ end
760
+
761
+ refresh_tables_and_views.each do |(keyspace, tables_and_views)|
762
+ tables_and_views.each_key do |table_or_view|
763
+ futures << refresh_maybe_retry(:table, keyspace, table_or_view)
764
+ futures << refresh_maybe_retry(:materialized_view, keyspace, table_or_view)
765
+ end
766
+ end
767
+
768
+ refresh_types.each do |(keyspace, types)|
769
+ types.each_key do |type|
770
+ futures << refresh_maybe_retry(:type, keyspace, type)
771
+ end
772
+ end
773
+
774
+ refresh_functions.each do |(keyspace, function_changes)|
775
+ function_changes.each do |change|
776
+ futures <<
777
+ refresh_maybe_retry(:function, keyspace, change.name, change.arguments)
778
+ end
779
+ end
780
+
781
+ refresh_aggregates.each do |(keyspace, aggregate_changes)|
782
+ aggregate_changes.each do |change|
783
+ futures <<
784
+ refresh_maybe_retry(:aggregate, keyspace, change.name, change.arguments)
785
+ end
786
+ end
787
+
788
+ Ione::Future.all(*futures)
789
+ end
790
+
791
+ def refresh_maybe_retry(what, *args)
792
+ send(:"refresh_#{what}_async", *args).fallback do |e|
793
+ case e
794
+ when Errors::HostError, Errors::TimeoutError
795
+ refresh_retry(what, e, @reconnection_policy.schedule, *args)
796
+ else
797
+ connection = @connection
798
+ connection && connection.close(e)
799
+
800
+ Ione::Future.failed(e)
801
+ end
802
+ end
803
+ end
804
+
805
+ def refresh_retry(what, error, schedule, *args)
806
+ timeout = schedule.next
807
+ @logger.info("Failed to refresh #{what} #{args.inspect} " \
808
+ "(#{error.class.name}: #{error.message}), retrying in #{timeout}")
809
+
810
+ timer = @io_reactor.schedule_timer(timeout)
811
+ timer.flat_map do
812
+ send(:"refresh_#{what}_async", *args).fallback do |e|
813
+ case e
814
+ when Errors::HostError, Errors::TimeoutError
815
+ refresh_retry(what, e, schedule, *args)
816
+ else
817
+ connection = @connection
818
+ connection && connection.close(e)
819
+
820
+ Ione::Future.failed(e)
821
+ end
822
+ end
823
+ end
824
+ end
825
+
826
+ def refresh_schema_async_wrapper
827
+ # This is kinda tricky. We want to start refreshing the schema asynchronously.
828
+ # However, if we're already in the process of doing so, return the future
829
+ # representing that result rather than starting another schema refresh.
830
+ #
831
+ # A few other nuances while a refresh is in progress:
832
+ # * if a new attempt is made to refresh, keep track of that and schedule another
833
+ # refresh after the current one completes.
834
+ # * we don't want schema_change events to be processed since the full refresh
835
+ # may overwrite the results of handling the schema_change events with older
836
+ # data. That said, we don't want to lose track of schema_change events; just
837
+ # delay processing them until after the full refresh is done.
838
+ #
839
+ # Finally, when a full refresh begins, clear out any pending changes in
840
+ # @schema_changes because the full refresh subsumes them. This has two benefits:
841
+ # 1. avoid round trips to Cassandra to get details related to those schema
842
+ # changes.
843
+ # 2. avoid race conditions where those updates may return older data than our
844
+ # full refresh and might win as last writer with that potentially older data.
845
+ synchronize do
846
+ if @refresh_schema_future
847
+ @pending_schema_refresh = true
848
+ return @refresh_schema_future
849
+ end
850
+
851
+ # Fresh refresh; prep this connection!
852
+
853
+ # Since we're starting a new refresh, there can be no pending refresh request.
854
+ @pending_schema_refresh = false
855
+
856
+ # Clear outstanding schema changes and timers.
857
+ @schema_changes = []
858
+ @io_reactor.cancel_timer(@schema_refresh_timer) if @schema_refresh_timer
859
+ @schema_refresh_timer = nil
860
+ @io_reactor.cancel_timer(@schema_refresh_window) if @schema_refresh_window
861
+ @schema_refresh_window = nil
862
+
863
+ # Start refreshing..
864
+ @refresh_schema_future = refresh_maybe_retry(:schema)
865
+ @refresh_schema_future.on_complete do
866
+ pending = false
867
+ synchronize do
868
+ # We're done refreshing. If we have a pending refresh, launch it now.
869
+ @refresh_schema_future = nil
870
+ pending = @pending_schema_refresh
871
+ @pending_schema_refresh = false
872
+ unless pending
873
+ # Restore timers if there are pending schema changes.
874
+ handle_schema_change(nil)
875
+ end
876
+ end
877
+
878
+ refresh_schema_async_wrapper if pending
879
+ end
880
+
881
+ # Return the (now cached) future
882
+ @refresh_schema_future
883
+ end
884
+ end
885
+
886
+ def handle_schema_change(change)
887
+ timer = nil
888
+ expiration_timer = nil
889
+
890
+ synchronize do
891
+ # If change is nil, it means we want to set up timers (if there are pending
892
+ # changes). Otherwise, we definitely have a change and want to set up timers.
893
+ # Also, we only want to set up timers if we're not in the middle of a full
894
+ # refresh.
895
+ @schema_changes << change if change
896
+
897
+ unless @schema_changes.empty? || @refresh_schema_future
898
+ @io_reactor.cancel_timer(@schema_refresh_timer) if @schema_refresh_timer
899
+ timer = @schema_refresh_timer =
900
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_delay)
901
+
902
+ unless @schema_refresh_window
903
+ @schema_refresh_window =
904
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_timeout)
905
+ expiration_timer = @schema_refresh_window
906
+ end
907
+ end
908
+ end
909
+
910
+ expiration_timer.on_value do
911
+ schema_changes = nil
912
+
913
+ synchronize do
914
+ @io_reactor.cancel_timer(@schema_refresh_timer)
915
+
916
+ @schema_refresh_window = nil
917
+ @schema_refresh_timer = nil
918
+
919
+ schema_changes = @schema_changes
920
+ @schema_changes = ::Array.new
921
+ end
922
+
923
+ process_schema_changes(schema_changes)
924
+ end if expiration_timer
925
+
926
+ timer.on_value do
927
+ schema_changes = nil
928
+
929
+ synchronize do
930
+ @io_reactor.cancel_timer(@schema_refresh_window)
931
+
932
+ @schema_refresh_window = nil
933
+ @schema_refresh_timer = nil
934
+
935
+ schema_changes = @schema_changes
936
+ @schema_changes = ::Array.new
937
+ end
938
+
939
+ process_schema_changes(schema_changes)
940
+ end if timer
941
+
942
+ nil
943
+ end
944
+
945
+ def send_select_request(connection, request)
946
+ backtrace = caller
947
+ connection.send_request(request).map do |r|
948
+ case r
949
+ when Protocol::RowsResultResponse
950
+ r.rows
951
+ when Protocol::ErrorResponse
952
+ e = r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :quorum, 0)
953
+ e.set_backtrace(backtrace)
954
+ raise e
955
+ else
956
+ raise Errors::InternalError, "Unexpected response #{r.inspect}", caller
957
+ end
958
+ end
959
+ end
960
+ end
961
+ end
962
+ end