cql-rb 1.1.0.pre3 → 1.1.0.pre6
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.
- data/README.md +2 -2
- data/lib/cql/client.rb +9 -5
- data/lib/cql/client/asynchronous_client.rb +105 -192
- data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
- data/lib/cql/client/connection_helper.rb +155 -0
- data/lib/cql/client/connection_manager.rb +56 -0
- data/lib/cql/client/keyspace_changer.rb +27 -0
- data/lib/cql/client/null_logger.rb +21 -0
- data/lib/cql/client/request_runner.rb +5 -3
- data/lib/cql/client/synchronous_client.rb +5 -5
- data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
- data/lib/cql/future.rb +320 -210
- data/lib/cql/io/connection.rb +5 -5
- data/lib/cql/io/io_reactor.rb +21 -23
- data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
- data/lib/cql/protocol/encoding.rb +5 -1
- data/lib/cql/protocol/requests/register_request.rb +2 -0
- data/lib/cql/protocol/type_converter.rb +1 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +368 -175
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
- data/spec/cql/client/connection_helper_spec.rb +335 -0
- data/spec/cql/client/connection_manager_spec.rb +118 -0
- data/spec/cql/client/keyspace_changer_spec.rb +50 -0
- data/spec/cql/client/request_runner_spec.rb +12 -12
- data/spec/cql/client/synchronous_client_spec.rb +15 -15
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
- data/spec/cql/future_spec.rb +529 -301
- data/spec/cql/io/connection_spec.rb +12 -12
- data/spec/cql/io/io_reactor_spec.rb +61 -61
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/type_converter_spec.rb +1 -1
- data/spec/cql/time_uuid_spec.rb +7 -7
- data/spec/integration/client_spec.rb +2 -2
- data/spec/integration/io_spec.rb +20 -20
- data/spec/integration/protocol_spec.rb +17 -17
- data/spec/integration/regression_spec.rb +6 -0
- data/spec/integration/uuid_spec.rb +4 -0
- data/spec/support/fake_io_reactor.rb +38 -8
- data/spec/support/fake_server.rb +3 -3
- metadata +12 -2
data/lib/cql/io/connection.rb
CHANGED
@@ -23,7 +23,7 @@ module Cql
|
|
23
23
|
@lock = Mutex.new
|
24
24
|
@connected = false
|
25
25
|
@write_buffer = ByteBuffer.new
|
26
|
-
@
|
26
|
+
@connected_promise = Promise.new
|
27
27
|
end
|
28
28
|
|
29
29
|
# @private
|
@@ -41,11 +41,11 @@ module Cql
|
|
41
41
|
unless connected?
|
42
42
|
@io.connect_nonblock(@sockaddr)
|
43
43
|
@connected = true
|
44
|
-
@
|
44
|
+
@connected_promise.fulfill(self)
|
45
45
|
end
|
46
46
|
rescue Errno::EISCONN
|
47
47
|
@connected = true
|
48
|
-
@
|
48
|
+
@connected_promise.fulfill(self)
|
49
49
|
rescue Errno::EINPROGRESS, Errno::EALREADY
|
50
50
|
if @clock.now - @connection_started_at > @connection_timeout
|
51
51
|
close(ConnectionTimeoutError.new("Could not connect to #{@host}:#{@port} within #{@connection_timeout}s"))
|
@@ -62,7 +62,7 @@ module Cql
|
|
62
62
|
rescue SocketError => e
|
63
63
|
close(e) || closed!(e)
|
64
64
|
end
|
65
|
-
@
|
65
|
+
@connected_promise.future
|
66
66
|
end
|
67
67
|
|
68
68
|
# Closes the connection
|
@@ -209,7 +209,7 @@ module Cql
|
|
209
209
|
cause = ConnectionError.new(cause.message)
|
210
210
|
end
|
211
211
|
unless connected?
|
212
|
-
@
|
212
|
+
@connected_promise.fail(cause)
|
213
213
|
end
|
214
214
|
@connected = false
|
215
215
|
if @closed_listener
|
data/lib/cql/io/io_reactor.rb
CHANGED
@@ -95,8 +95,8 @@ module Cql
|
|
95
95
|
@io_loop.add_socket(@unblocker)
|
96
96
|
@running = false
|
97
97
|
@stopped = false
|
98
|
-
@
|
99
|
-
@
|
98
|
+
@started_promise = Promise.new
|
99
|
+
@stopped_promise = Promise.new
|
100
100
|
@lock = Mutex.new
|
101
101
|
end
|
102
102
|
|
@@ -109,7 +109,7 @@ module Cql
|
|
109
109
|
# @yield [error] the error that cause the reactor to stop
|
110
110
|
#
|
111
111
|
def on_error(&listener)
|
112
|
-
@
|
112
|
+
@stopped_promise.future.on_failure(&listener)
|
113
113
|
end
|
114
114
|
|
115
115
|
# Returns true as long as the reactor is running. It will be true even
|
@@ -130,11 +130,11 @@ module Cql
|
|
130
130
|
def start
|
131
131
|
@lock.synchronize do
|
132
132
|
raise ReactorError, 'Cannot start a stopped IO reactor' if @stopped
|
133
|
-
return @
|
133
|
+
return @started_promise.future if @running
|
134
134
|
@running = true
|
135
135
|
end
|
136
136
|
Thread.start do
|
137
|
-
@
|
137
|
+
@started_promise.fulfill(self)
|
138
138
|
begin
|
139
139
|
@io_loop.tick until @stopped
|
140
140
|
ensure
|
@@ -142,13 +142,13 @@ module Cql
|
|
142
142
|
@io_loop.cancel_timers
|
143
143
|
@running = false
|
144
144
|
if $!
|
145
|
-
@
|
145
|
+
@stopped_promise.fail($!)
|
146
146
|
else
|
147
|
-
@
|
147
|
+
@stopped_promise.fulfill(self)
|
148
148
|
end
|
149
149
|
end
|
150
150
|
end
|
151
|
-
@
|
151
|
+
@started_promise.future
|
152
152
|
end
|
153
153
|
|
154
154
|
# Stops the reactor.
|
@@ -161,7 +161,7 @@ module Cql
|
|
161
161
|
#
|
162
162
|
def stop
|
163
163
|
@stopped = true
|
164
|
-
@
|
164
|
+
@stopped_promise.future
|
165
165
|
end
|
166
166
|
|
167
167
|
# Opens a connection to the specified host and port.
|
@@ -197,9 +197,7 @@ module Cql
|
|
197
197
|
# @return [Cql::Future] a future that completes when the timer expires
|
198
198
|
#
|
199
199
|
def schedule_timer(timeout)
|
200
|
-
|
201
|
-
@io_loop.schedule_timer(timeout, f)
|
202
|
-
f
|
200
|
+
@io_loop.schedule_timer(timeout)
|
203
201
|
end
|
204
202
|
|
205
203
|
def to_s
|
@@ -272,19 +270,19 @@ module Cql
|
|
272
270
|
|
273
271
|
def add_socket(socket)
|
274
272
|
@lock.synchronize do
|
275
|
-
sockets = @sockets.
|
273
|
+
sockets = @sockets.reject { |s| s.closed? }
|
276
274
|
sockets << socket
|
277
275
|
@sockets = sockets
|
278
276
|
end
|
279
277
|
end
|
280
278
|
|
281
|
-
def schedule_timer(timeout,
|
279
|
+
def schedule_timer(timeout, promise=Promise.new)
|
282
280
|
@lock.synchronize do
|
283
|
-
timers = @timers.
|
284
|
-
timers << [@clock.now + timeout,
|
285
|
-
timers.sort_by(&:first)
|
281
|
+
timers = @timers.reject { |pair| pair[1].nil? }
|
282
|
+
timers << [@clock.now + timeout, promise]
|
286
283
|
@timers = timers
|
287
284
|
end
|
285
|
+
promise.future
|
288
286
|
end
|
289
287
|
|
290
288
|
def close_sockets
|
@@ -300,7 +298,7 @@ module Cql
|
|
300
298
|
def cancel_timers
|
301
299
|
@timers.each do |pair|
|
302
300
|
if pair[1]
|
303
|
-
pair[1].fail
|
301
|
+
pair[1].fail(CancelledError.new)
|
304
302
|
pair[1] = nil
|
305
303
|
end
|
306
304
|
end
|
@@ -320,8 +318,8 @@ module Cql
|
|
320
318
|
def check_sockets!(timeout)
|
321
319
|
readables, writables, connecting = [], [], []
|
322
320
|
sockets = @sockets
|
323
|
-
sockets.reject! { |s| s.closed? }
|
324
321
|
sockets.each do |s|
|
322
|
+
next if s.closed?
|
325
323
|
readables << s if s.connected?
|
326
324
|
writables << s if s.connecting? || s.writable?
|
327
325
|
connecting << s if s.connecting?
|
@@ -335,11 +333,11 @@ module Cql
|
|
335
333
|
def check_timers!
|
336
334
|
timers = @timers
|
337
335
|
timers.each do |pair|
|
338
|
-
|
339
|
-
|
340
|
-
|
336
|
+
if pair[1] && pair[0] <= @clock.now
|
337
|
+
pair[1].fulfill
|
338
|
+
pair[1] = nil
|
339
|
+
end
|
341
340
|
end
|
342
|
-
timers.reject! { |pair| pair[1].nil? }
|
343
341
|
end
|
344
342
|
end
|
345
343
|
end
|
@@ -23,7 +23,7 @@ module Cql
|
|
23
23
|
@connection = connection
|
24
24
|
@connection.on_data(&method(:receive_data))
|
25
25
|
@connection.on_closed(&method(:socket_closed))
|
26
|
-
@
|
26
|
+
@promises = Array.new(128) { nil }
|
27
27
|
@read_buffer = ByteBuffer.new
|
28
28
|
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
29
29
|
@request_queue_in = []
|
@@ -31,10 +31,24 @@ module Cql
|
|
31
31
|
@event_listeners = []
|
32
32
|
@data = {}
|
33
33
|
@lock = Mutex.new
|
34
|
-
@
|
34
|
+
@closed_promise = Promise.new
|
35
35
|
@keyspace = nil
|
36
36
|
end
|
37
37
|
|
38
|
+
# Returns the hostname of the underlying connection
|
39
|
+
#
|
40
|
+
# @return [String] the hostname
|
41
|
+
def host
|
42
|
+
@connection.host
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the port of the underlying connection
|
46
|
+
#
|
47
|
+
# @return [Integer] the port
|
48
|
+
def port
|
49
|
+
@connection.port
|
50
|
+
end
|
51
|
+
|
38
52
|
# Associate arbitrary data with this protocol handler object. This is
|
39
53
|
# useful in situations where additional metadata can be loaded after the
|
40
54
|
# connection has been set up, or to keep statistics specific to the
|
@@ -66,8 +80,7 @@ module Cql
|
|
66
80
|
# @yieldparam error [nil, Error] the error that caused the connection to
|
67
81
|
# close, if any
|
68
82
|
def on_closed(&listener)
|
69
|
-
@
|
70
|
-
@closed_future.on_failure(&listener)
|
83
|
+
@closed_promise.future.on_complete(&listener)
|
71
84
|
end
|
72
85
|
|
73
86
|
# Register to receive server sent events, like schema changes, nodes going
|
@@ -77,7 +90,7 @@ module Cql
|
|
77
90
|
# @yieldparam event [Cql::Protocol::EventResponse] an event sent by the server
|
78
91
|
def on_event(&listener)
|
79
92
|
@lock.synchronize do
|
80
|
-
@event_listeners
|
93
|
+
@event_listeners += [listener]
|
81
94
|
end
|
82
95
|
end
|
83
96
|
|
@@ -91,11 +104,11 @@ module Cql
|
|
91
104
|
# the response
|
92
105
|
def send_request(request)
|
93
106
|
return Future.failed(NotConnectedError.new) if closed?
|
94
|
-
|
107
|
+
promise = RequestPromise.new(request)
|
95
108
|
id = nil
|
96
109
|
@lock.synchronize do
|
97
110
|
if (id = next_stream_id)
|
98
|
-
@
|
111
|
+
@promises[id] = promise
|
99
112
|
end
|
100
113
|
end
|
101
114
|
if id
|
@@ -104,10 +117,11 @@ module Cql
|
|
104
117
|
end
|
105
118
|
else
|
106
119
|
@lock.synchronize do
|
107
|
-
|
120
|
+
promise.encode_frame!
|
121
|
+
@request_queue_in << promise
|
108
122
|
end
|
109
123
|
end
|
110
|
-
future
|
124
|
+
promise.future
|
111
125
|
end
|
112
126
|
|
113
127
|
# Closes the underlying connection.
|
@@ -115,11 +129,25 @@ module Cql
|
|
115
129
|
# @return [Cql::Future] a future that completes when the connection has closed
|
116
130
|
def close
|
117
131
|
@connection.close
|
118
|
-
@
|
132
|
+
@closed_promise.future
|
119
133
|
end
|
120
134
|
|
121
135
|
private
|
122
136
|
|
137
|
+
# @private
|
138
|
+
class RequestPromise < Promise
|
139
|
+
attr_reader :request, :frame
|
140
|
+
|
141
|
+
def initialize(request)
|
142
|
+
@request = request
|
143
|
+
super()
|
144
|
+
end
|
145
|
+
|
146
|
+
def encode_frame!
|
147
|
+
@frame = @request.encode_frame(0)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
123
151
|
def receive_data(data)
|
124
152
|
@current_frame << data
|
125
153
|
while @current_frame.complete?
|
@@ -135,24 +163,26 @@ module Cql
|
|
135
163
|
end
|
136
164
|
|
137
165
|
def notify_event_listeners(event_response)
|
138
|
-
|
166
|
+
event_listeners = nil
|
139
167
|
@lock.synchronize do
|
140
|
-
|
141
|
-
|
142
|
-
|
168
|
+
event_listeners = @event_listeners
|
169
|
+
return if event_listeners.empty?
|
170
|
+
end
|
171
|
+
event_listeners.each do |listener|
|
172
|
+
listener.call(@current_frame.body) rescue nil
|
143
173
|
end
|
144
174
|
end
|
145
175
|
|
146
176
|
def complete_request(id, response)
|
147
|
-
|
148
|
-
|
149
|
-
@
|
150
|
-
|
177
|
+
promise = @lock.synchronize do
|
178
|
+
promise = @promises[id]
|
179
|
+
@promises[id] = nil
|
180
|
+
promise
|
151
181
|
end
|
152
182
|
if response.is_a?(Protocol::SetKeyspaceResultResponse)
|
153
183
|
@keyspace = response.keyspace
|
154
184
|
end
|
155
|
-
|
185
|
+
promise.fulfill(response)
|
156
186
|
end
|
157
187
|
|
158
188
|
def flush_request_queue
|
@@ -164,16 +194,17 @@ module Cql
|
|
164
194
|
end
|
165
195
|
while true
|
166
196
|
id = nil
|
167
|
-
|
197
|
+
frame = nil
|
168
198
|
@lock.synchronize do
|
169
199
|
if @request_queue_out.any? && (id = next_stream_id)
|
170
|
-
|
171
|
-
|
200
|
+
promise = @request_queue_out.shift
|
201
|
+
frame = promise.frame
|
202
|
+
@promises[id] = promise
|
172
203
|
end
|
173
204
|
end
|
174
205
|
if id
|
175
|
-
Protocol::Request.change_stream_id(id,
|
176
|
-
@connection.write(
|
206
|
+
Protocol::Request.change_stream_id(id, frame)
|
207
|
+
@connection.write(frame)
|
177
208
|
else
|
178
209
|
break
|
179
210
|
end
|
@@ -183,30 +214,30 @@ module Cql
|
|
183
214
|
def socket_closed(cause)
|
184
215
|
request_failure_cause = cause || Io::ConnectionClosedError.new
|
185
216
|
@lock.synchronize do
|
186
|
-
@
|
187
|
-
if
|
188
|
-
@
|
189
|
-
@
|
217
|
+
@promises.each_with_index do |promise, i|
|
218
|
+
if promise
|
219
|
+
@promises[i].fail(request_failure_cause)
|
220
|
+
@promises[i] = nil
|
190
221
|
end
|
191
222
|
end
|
192
|
-
@request_queue_in.each do |
|
193
|
-
|
223
|
+
@request_queue_in.each do |promise|
|
224
|
+
promise.fail(request_failure_cause)
|
194
225
|
end
|
195
226
|
@request_queue_in.clear
|
196
|
-
@request_queue_out.each do |
|
197
|
-
|
227
|
+
@request_queue_out.each do |promise|
|
228
|
+
promise.fail(request_failure_cause)
|
198
229
|
end
|
199
230
|
@request_queue_out.clear
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
231
|
+
end
|
232
|
+
if cause
|
233
|
+
@closed_promise.fail(cause)
|
234
|
+
else
|
235
|
+
@closed_promise.fulfill
|
205
236
|
end
|
206
237
|
end
|
207
238
|
|
208
239
|
def next_stream_id
|
209
|
-
@
|
240
|
+
@promises.each_with_index do |task, index|
|
210
241
|
return index if task.nil?
|
211
242
|
end
|
212
243
|
nil
|
@@ -27,7 +27,11 @@ module Cql
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def write_uuid(buffer, uuid)
|
30
|
-
|
30
|
+
v = uuid.value
|
31
|
+
write_int(buffer, (v >> 96) & 0xffffffff)
|
32
|
+
write_int(buffer, (v >> 64) & 0xffffffff)
|
33
|
+
write_int(buffer, (v >> 32) & 0xffffffff)
|
34
|
+
write_int(buffer, v & 0xffffffff)
|
31
35
|
end
|
32
36
|
|
33
37
|
def write_string_list(buffer, strs)
|
@@ -108,6 +108,7 @@ module Cql
|
|
108
108
|
:bigint => method(:bigint_to_bytes),
|
109
109
|
:blob => method(:blob_to_bytes),
|
110
110
|
:boolean => method(:boolean_to_bytes),
|
111
|
+
:counter => method(:bigint_to_bytes),
|
111
112
|
:decimal => method(:decimal_to_bytes),
|
112
113
|
:double => method(:double_to_bytes),
|
113
114
|
:float => method(:float_to_bytes),
|
data/lib/cql/version.rb
CHANGED
@@ -11,13 +11,17 @@ module Cql
|
|
11
11
|
end
|
12
12
|
|
13
13
|
let :connection_options do
|
14
|
-
{:host => 'example.com', :port => 12321, :io_reactor => io_reactor}
|
14
|
+
{:host => 'example.com', :port => 12321, :io_reactor => io_reactor, :logger => logger}
|
15
15
|
end
|
16
16
|
|
17
17
|
let :io_reactor do
|
18
18
|
FakeIoReactor.new
|
19
19
|
end
|
20
20
|
|
21
|
+
let :logger do
|
22
|
+
NullLogger.new
|
23
|
+
end
|
24
|
+
|
21
25
|
def connections
|
22
26
|
io_reactor.connections
|
23
27
|
end
|
@@ -53,39 +57,116 @@ module Cql
|
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
60
|
+
shared_context 'peer discovery setup' do
|
61
|
+
let :local_info do
|
62
|
+
{
|
63
|
+
'data_center' => 'dc1',
|
64
|
+
'host_id' => nil,
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
let :local_metadata do
|
69
|
+
[
|
70
|
+
['system', 'local', 'data_center', :text],
|
71
|
+
['system', 'local', 'host_id', :uuid],
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
let :peer_metadata do
|
76
|
+
[
|
77
|
+
['system', 'peers', 'peer', :inet],
|
78
|
+
['system', 'peers', 'data_center', :varchar],
|
79
|
+
['system', 'peers', 'host_id', :uuid],
|
80
|
+
['system', 'peers', 'rpc_address', :inet],
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
let :data_centers do
|
85
|
+
Hash.new('dc1')
|
86
|
+
end
|
87
|
+
|
88
|
+
let :additional_nodes do
|
89
|
+
Array.new(5) { IPAddr.new("127.0.#{rand(255)}.#{rand(255)}") }
|
90
|
+
end
|
91
|
+
|
92
|
+
let :bind_all_rpc_addresses do
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
let :min_peers do
|
97
|
+
[2]
|
98
|
+
end
|
99
|
+
|
100
|
+
before do
|
101
|
+
uuid_generator = TimeUuid::Generator.new
|
102
|
+
additional_rpc_addresses = additional_nodes.dup
|
103
|
+
io_reactor.on_connection do |connection|
|
104
|
+
connection[:spec_host_id] = uuid_generator.next
|
105
|
+
connection[:spec_data_center] = data_centers[connection.host]
|
106
|
+
connection.handle_request do |request|
|
107
|
+
case request
|
108
|
+
when Protocol::StartupRequest
|
109
|
+
Protocol::ReadyResponse.new
|
110
|
+
when Protocol::QueryRequest
|
111
|
+
case request.cql
|
112
|
+
when /FROM system\.local/
|
113
|
+
row = {'host_id' => connection[:spec_host_id], 'data_center' => connection[:spec_data_center]}
|
114
|
+
Protocol::RowsResultResponse.new([row], local_metadata)
|
115
|
+
when /FROM system\.peers/
|
116
|
+
other_host_ids = connections.reject { |c| c[:spec_host_id] == connection[:spec_host_id] }.map { |c| c[:spec_host_id] }
|
117
|
+
until other_host_ids.size >= min_peers[0]
|
118
|
+
other_host_ids << uuid_generator.next
|
119
|
+
end
|
120
|
+
rows = other_host_ids.map do |host_id|
|
121
|
+
ip = additional_rpc_addresses.shift
|
122
|
+
{
|
123
|
+
'peer' => ip,
|
124
|
+
'host_id' => host_id,
|
125
|
+
'data_center' => data_centers[ip],
|
126
|
+
'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip
|
127
|
+
}
|
128
|
+
end
|
129
|
+
Protocol::RowsResultResponse.new(rows, peer_metadata)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
56
137
|
describe '#connect' do
|
57
138
|
it 'connects' do
|
58
|
-
client.connect.
|
139
|
+
client.connect.value
|
59
140
|
connections.should have(1).item
|
60
141
|
end
|
61
142
|
|
62
143
|
it 'connects only once' do
|
63
|
-
client.connect.
|
64
|
-
client.connect.
|
144
|
+
client.connect.value
|
145
|
+
client.connect.value
|
65
146
|
connections.should have(1).item
|
66
147
|
end
|
67
148
|
|
68
149
|
context 'when connecting to multiple hosts' do
|
69
150
|
before do
|
70
|
-
client.close.
|
71
|
-
io_reactor.stop.
|
151
|
+
client.close.value
|
152
|
+
io_reactor.stop.value
|
72
153
|
end
|
73
154
|
|
74
155
|
it 'connects to all hosts' do
|
75
156
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h3.example.com]))
|
76
|
-
c.connect.
|
157
|
+
c.connect.value
|
77
158
|
connections.should have(3).items
|
78
159
|
end
|
79
160
|
|
80
161
|
it 'connects to all hosts, when given as a comma-sepatated string' do
|
81
162
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
82
|
-
c.connect.
|
163
|
+
c.connect.value
|
83
164
|
connections.should have(3).items
|
84
165
|
end
|
85
166
|
|
86
167
|
it 'only connects to each host once' do
|
87
168
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h2.example.com]))
|
88
|
-
c.connect.
|
169
|
+
c.connect.value
|
89
170
|
connections.should have(2).items
|
90
171
|
end
|
91
172
|
|
@@ -93,7 +174,7 @@ module Cql
|
|
93
174
|
io_reactor.node_down('h1.example.com')
|
94
175
|
io_reactor.node_down('h3.example.com')
|
95
176
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h2.example.com]))
|
96
|
-
c.connect.
|
177
|
+
c.connect.value
|
97
178
|
connections.should have(1).items
|
98
179
|
end
|
99
180
|
|
@@ -102,134 +183,65 @@ module Cql
|
|
102
183
|
io_reactor.node_down('h2.example.com')
|
103
184
|
io_reactor.node_down('h3.example.com')
|
104
185
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h2.example.com]))
|
105
|
-
expect { c.connect.
|
186
|
+
expect { c.connect.value }.to raise_error(Io::ConnectionError)
|
106
187
|
end
|
107
188
|
end
|
108
189
|
|
109
190
|
it 'returns itself' do
|
110
|
-
client.connect.
|
191
|
+
client.connect.value.should equal(client)
|
111
192
|
end
|
112
193
|
|
113
194
|
it 'connects to the right host and port' do
|
114
|
-
client.connect.
|
195
|
+
client.connect.value
|
115
196
|
last_connection.host.should == 'example.com'
|
116
197
|
last_connection.port.should == 12321
|
117
198
|
end
|
118
199
|
|
119
200
|
it 'connects with the default connection timeout' do
|
120
|
-
client.connect.
|
201
|
+
client.connect.value
|
121
202
|
last_connection.timeout.should == 10
|
122
203
|
end
|
123
204
|
|
124
205
|
it 'sends a startup request' do
|
125
|
-
client.connect.
|
206
|
+
client.connect.value
|
126
207
|
requests.first.should be_a(Protocol::StartupRequest)
|
127
208
|
end
|
128
209
|
|
129
210
|
it 'sends a startup request to each connection' do
|
130
|
-
client.close.
|
131
|
-
io_reactor.stop.
|
132
|
-
io_reactor.start.
|
211
|
+
client.close.value
|
212
|
+
io_reactor.stop.value
|
213
|
+
io_reactor.start.value
|
133
214
|
|
134
215
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h3.example.com]))
|
135
|
-
c.connect.
|
216
|
+
c.connect.value
|
136
217
|
connections.each do |cc|
|
137
218
|
cc.requests.first.should be_a(Protocol::StartupRequest)
|
138
219
|
end
|
139
220
|
end
|
140
221
|
|
141
222
|
it 'is not in a keyspace' do
|
142
|
-
client.connect.
|
223
|
+
client.connect.value
|
143
224
|
client.keyspace.should be_nil
|
144
225
|
end
|
145
226
|
|
146
227
|
it 'changes to the keyspace given as an option' do
|
147
228
|
c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
|
148
|
-
c.connect.
|
229
|
+
c.connect.value
|
149
230
|
request = requests.find { |rq| rq == Protocol::QueryRequest.new('USE hello_world', :one) }
|
150
231
|
request.should_not be_nil, 'expected a USE request to have been sent'
|
151
232
|
end
|
152
233
|
|
153
234
|
it 'validates the keyspace name before sending the USE command' do
|
154
235
|
c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
|
155
|
-
expect { c.connect.
|
236
|
+
expect { c.connect.value }.to raise_error(InvalidKeyspaceNameError)
|
156
237
|
requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
|
157
238
|
end
|
158
239
|
|
159
240
|
context 'with automatic peer discovery' do
|
160
|
-
|
161
|
-
{
|
162
|
-
'data_center' => 'dc1',
|
163
|
-
'host_id' => nil,
|
164
|
-
}
|
165
|
-
end
|
166
|
-
|
167
|
-
let :local_metadata do
|
168
|
-
[
|
169
|
-
['system', 'local', 'data_center', :text],
|
170
|
-
['system', 'local', 'host_id', :uuid],
|
171
|
-
]
|
172
|
-
end
|
173
|
-
|
174
|
-
let :peer_metadata do
|
175
|
-
[
|
176
|
-
['system', 'peers', 'peer', :inet],
|
177
|
-
['system', 'peers', 'data_center', :varchar],
|
178
|
-
['system', 'peers', 'host_id', :uuid],
|
179
|
-
['system', 'peers', 'rpc_address', :inet],
|
180
|
-
]
|
181
|
-
end
|
182
|
-
|
183
|
-
let :data_centers do
|
184
|
-
Hash.new('dc1')
|
185
|
-
end
|
186
|
-
|
187
|
-
let :additional_nodes do
|
188
|
-
Array.new(5) { IPAddr.new("127.0.#{rand(255)}.#{rand(255)}") }
|
189
|
-
end
|
190
|
-
|
191
|
-
let :bind_all_rpc_addresses do
|
192
|
-
false
|
193
|
-
end
|
194
|
-
|
195
|
-
before do
|
196
|
-
uuid_generator = TimeUuid::Generator.new
|
197
|
-
additional_rpc_addresses = additional_nodes.dup
|
198
|
-
io_reactor.on_connection do |connection|
|
199
|
-
connection[:spec_host_id] = uuid_generator.next
|
200
|
-
connection[:spec_data_center] = data_centers[connection.host]
|
201
|
-
connection.handle_request do |request|
|
202
|
-
case request
|
203
|
-
when Protocol::StartupRequest
|
204
|
-
Protocol::ReadyResponse.new
|
205
|
-
when Protocol::QueryRequest
|
206
|
-
case request.cql
|
207
|
-
when /FROM system\.local/
|
208
|
-
row = {'host_id' => connection[:spec_host_id], 'data_center' => connection[:spec_data_center]}
|
209
|
-
Protocol::RowsResultResponse.new([row], local_metadata)
|
210
|
-
when /FROM system\.peers/
|
211
|
-
other_host_ids = connections.reject { |c| c[:spec_host_id] == connection[:spec_host_id] }.map { |c| c[:spec_host_id] }
|
212
|
-
until other_host_ids.size >= 2
|
213
|
-
other_host_ids << uuid_generator.next
|
214
|
-
end
|
215
|
-
rows = other_host_ids.map do |host_id|
|
216
|
-
ip = additional_rpc_addresses.shift
|
217
|
-
{
|
218
|
-
'peer' => ip,
|
219
|
-
'host_id' => host_id,
|
220
|
-
'data_center' => data_centers[ip],
|
221
|
-
'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip
|
222
|
-
}
|
223
|
-
end
|
224
|
-
Protocol::RowsResultResponse.new(rows, peer_metadata)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
241
|
+
include_context 'peer discovery setup'
|
230
242
|
|
231
243
|
it 'connects to the other nodes in the cluster' do
|
232
|
-
client.connect.
|
244
|
+
client.connect.value
|
233
245
|
connections.should have(3).items
|
234
246
|
end
|
235
247
|
|
@@ -239,14 +251,14 @@ module Cql
|
|
239
251
|
end
|
240
252
|
|
241
253
|
it 'falls back on using the peer column' do
|
242
|
-
client.connect.
|
254
|
+
client.connect.value
|
243
255
|
connections.should have(3).items
|
244
256
|
end
|
245
257
|
end
|
246
258
|
|
247
259
|
it 'connects to the other nodes in the same data center' do
|
248
260
|
data_centers[additional_nodes[1]] = 'dc2'
|
249
|
-
client.connect.
|
261
|
+
client.connect.value
|
250
262
|
connections.should have(2).items
|
251
263
|
end
|
252
264
|
|
@@ -254,49 +266,49 @@ module Cql
|
|
254
266
|
data_centers['host2'] = 'dc2'
|
255
267
|
data_centers[additional_nodes[1]] = 'dc2'
|
256
268
|
c = described_class.new(connection_options.merge(hosts: %w[host1 host2]))
|
257
|
-
c.connect.
|
269
|
+
c.connect.value
|
258
270
|
connections.should have(3).items
|
259
271
|
end
|
260
272
|
|
261
273
|
it 'only connects to the other nodes in the cluster it is not already connected do' do
|
262
274
|
c = described_class.new(connection_options.merge(hosts: %w[host1 host2]))
|
263
|
-
c.connect.
|
275
|
+
c.connect.value
|
264
276
|
connections.should have(3).items
|
265
277
|
end
|
266
278
|
|
267
279
|
it 'handles the case when it is already connected to all nodes' do
|
268
280
|
c = described_class.new(connection_options.merge(hosts: %w[host1 host2 host3 host4]))
|
269
|
-
c.connect.
|
281
|
+
c.connect.value
|
270
282
|
connections.should have(4).items
|
271
283
|
end
|
272
284
|
|
273
285
|
it 'accepts that some nodes are down' do
|
274
286
|
io_reactor.node_down(additional_nodes.first.to_s)
|
275
|
-
client.connect.
|
287
|
+
client.connect.value
|
276
288
|
connections.should have(2).items
|
277
289
|
end
|
278
290
|
end
|
279
291
|
|
280
292
|
it 're-raises any errors raised' do
|
281
293
|
io_reactor.stub(:connect).and_raise(ArgumentError)
|
282
|
-
expect { client.connect.
|
294
|
+
expect { client.connect.value }.to raise_error(ArgumentError)
|
283
295
|
end
|
284
296
|
|
285
297
|
it 'is not connected if an error is raised' do
|
286
298
|
io_reactor.stub(:connect).and_raise(ArgumentError)
|
287
|
-
client.connect.
|
299
|
+
client.connect.value rescue nil
|
288
300
|
client.should_not be_connected
|
289
301
|
io_reactor.should_not be_running
|
290
302
|
end
|
291
303
|
|
292
304
|
it 'is connected after #connect returns' do
|
293
|
-
client.connect.
|
305
|
+
client.connect.value
|
294
306
|
client.should be_connected
|
295
307
|
end
|
296
308
|
|
297
309
|
it 'is not connected while connecting' do
|
298
310
|
go = false
|
299
|
-
io_reactor.stop.
|
311
|
+
io_reactor.stop.value
|
300
312
|
io_reactor.before_startup { sleep 0.01 until go }
|
301
313
|
client.connect
|
302
314
|
begin
|
@@ -331,26 +343,26 @@ module Cql
|
|
331
343
|
|
332
344
|
it 'sends credentials' do
|
333
345
|
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
334
|
-
client.connect.
|
346
|
+
client.connect.value
|
335
347
|
request = requests.find { |rq| rq == Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar') }
|
336
348
|
request.should_not be_nil, 'expected a credentials request to have been sent'
|
337
349
|
end
|
338
350
|
|
339
351
|
it 'raises an error when no credentials have been given' do
|
340
352
|
client = described_class.new(connection_options)
|
341
|
-
expect { client.connect.
|
353
|
+
expect { client.connect.value }.to raise_error(AuthenticationError)
|
342
354
|
end
|
343
355
|
|
344
356
|
it 'raises an error when the server responds with an error to the credentials request' do
|
345
357
|
handle_request(&method(:denying_request_handler))
|
346
358
|
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
347
|
-
expect { client.connect.
|
359
|
+
expect { client.connect.value }.to raise_error(AuthenticationError)
|
348
360
|
end
|
349
361
|
|
350
362
|
it 'shuts down the client when there is an authentication error' do
|
351
363
|
handle_request(&method(:denying_request_handler))
|
352
364
|
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
353
|
-
client.connect.
|
365
|
+
client.connect.value rescue nil
|
354
366
|
client.should_not be_connected
|
355
367
|
io_reactor.should_not be_running
|
356
368
|
end
|
@@ -359,28 +371,28 @@ module Cql
|
|
359
371
|
|
360
372
|
describe '#close' do
|
361
373
|
it 'closes the connection' do
|
362
|
-
client.connect.
|
363
|
-
client.close.
|
374
|
+
client.connect.value
|
375
|
+
client.close.value
|
364
376
|
io_reactor.should_not be_running
|
365
377
|
end
|
366
378
|
|
367
379
|
it 'does nothing when called before #connect' do
|
368
|
-
client.close.
|
380
|
+
client.close.value
|
369
381
|
end
|
370
382
|
|
371
383
|
it 'accepts multiple calls to #close' do
|
372
|
-
client.connect.
|
373
|
-
client.close.
|
374
|
-
client.close.
|
384
|
+
client.connect.value
|
385
|
+
client.close.value
|
386
|
+
client.close.value
|
375
387
|
end
|
376
388
|
|
377
389
|
it 'returns itself' do
|
378
|
-
client.connect.
|
390
|
+
client.connect.value.close.value.should equal(client)
|
379
391
|
end
|
380
392
|
|
381
393
|
it 'fails when the IO reactor stop fails' do
|
382
394
|
io_reactor.stub(:stop).and_return(Future.failed(StandardError.new('Bork!')))
|
383
|
-
expect { client.close.
|
395
|
+
expect { client.close.value }.to raise_error('Bork!')
|
384
396
|
end
|
385
397
|
end
|
386
398
|
|
@@ -391,20 +403,20 @@ module Cql
|
|
391
403
|
Protocol::SetKeyspaceResultResponse.new('system')
|
392
404
|
end
|
393
405
|
end
|
394
|
-
client.connect.
|
395
|
-
client.use('system').
|
406
|
+
client.connect.value
|
407
|
+
client.use('system').value
|
396
408
|
last_request.should == Protocol::QueryRequest.new('USE system', :one)
|
397
409
|
end
|
398
410
|
|
399
411
|
it 'executes a USE query for each connection' do
|
400
|
-
client.close.
|
401
|
-
io_reactor.stop.
|
402
|
-
io_reactor.start.
|
412
|
+
client.close.value
|
413
|
+
io_reactor.stop.value
|
414
|
+
io_reactor.start.value
|
403
415
|
|
404
416
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h3.example.com]))
|
405
|
-
c.connect.
|
417
|
+
c.connect.value
|
406
418
|
|
407
|
-
c.use('system').
|
419
|
+
c.use('system').value
|
408
420
|
last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
|
409
421
|
last_requests.should == [
|
410
422
|
Protocol::QueryRequest.new('USE system', :one),
|
@@ -419,14 +431,14 @@ module Cql
|
|
419
431
|
Protocol::SetKeyspaceResultResponse.new('system')
|
420
432
|
end
|
421
433
|
end
|
422
|
-
client.connect.
|
423
|
-
client.use('system').
|
434
|
+
client.connect.value
|
435
|
+
client.use('system').value
|
424
436
|
client.keyspace.should == 'system'
|
425
437
|
end
|
426
438
|
|
427
439
|
it 'raises an error if the keyspace name is not valid' do
|
428
|
-
client.connect.
|
429
|
-
expect { client.use('system; DROP KEYSPACE system').
|
440
|
+
client.connect.value
|
441
|
+
expect { client.use('system; DROP KEYSPACE system').value }.to raise_error(InvalidKeyspaceNameError)
|
430
442
|
end
|
431
443
|
|
432
444
|
it 'allows the keyspace name to be quoted' do
|
@@ -435,24 +447,35 @@ module Cql
|
|
435
447
|
Protocol::SetKeyspaceResultResponse.new('system')
|
436
448
|
end
|
437
449
|
end
|
438
|
-
client.connect.
|
439
|
-
client.use('"system"').
|
450
|
+
client.connect.value
|
451
|
+
client.use('"system"').value
|
440
452
|
client.keyspace.should == "system"
|
441
453
|
end
|
442
454
|
end
|
443
455
|
|
444
456
|
describe '#execute' do
|
457
|
+
let :cql do
|
458
|
+
'UPDATE stuff SET thing = 1 WHERE id = 3'
|
459
|
+
end
|
460
|
+
|
445
461
|
before do
|
446
|
-
client.connect.
|
462
|
+
client.connect.value
|
447
463
|
end
|
448
464
|
|
449
|
-
it 'asks the connection to execute the query' do
|
450
|
-
client.execute(
|
451
|
-
last_request.should == Protocol::QueryRequest.new(
|
465
|
+
it 'asks the connection to execute the query using the default consistency level' do
|
466
|
+
client.execute(cql).value
|
467
|
+
last_request.should == Protocol::QueryRequest.new(cql, :quorum)
|
468
|
+
end
|
469
|
+
|
470
|
+
it 'uses the consistency specified when the client was created' do
|
471
|
+
client = described_class.new(connection_options.merge(default_consistency: :all))
|
472
|
+
client.connect.value
|
473
|
+
client.execute(cql).value
|
474
|
+
last_request.should == Protocol::QueryRequest.new(cql, :all)
|
452
475
|
end
|
453
476
|
|
454
477
|
it 'uses the specified consistency' do
|
455
|
-
client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three).
|
478
|
+
client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three).value
|
456
479
|
last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
|
457
480
|
end
|
458
481
|
|
@@ -463,7 +486,7 @@ module Cql
|
|
463
486
|
Protocol::VoidResultResponse.new
|
464
487
|
end
|
465
488
|
end
|
466
|
-
result = client.execute('UPDATE stuff SET thing = 1 WHERE id = 3').
|
489
|
+
result = client.execute('UPDATE stuff SET thing = 1 WHERE id = 3').value
|
467
490
|
result.should be_nil
|
468
491
|
end
|
469
492
|
end
|
@@ -475,7 +498,7 @@ module Cql
|
|
475
498
|
Protocol::SetKeyspaceResultResponse.new('system')
|
476
499
|
end
|
477
500
|
end
|
478
|
-
result = client.execute('USE system').
|
501
|
+
result = client.execute('USE system').value
|
479
502
|
result.should be_nil
|
480
503
|
end
|
481
504
|
|
@@ -485,14 +508,14 @@ module Cql
|
|
485
508
|
Protocol::SetKeyspaceResultResponse.new('system')
|
486
509
|
end
|
487
510
|
end
|
488
|
-
client.execute('USE system').
|
511
|
+
client.execute('USE system').value
|
489
512
|
client.keyspace.should == 'system'
|
490
513
|
end
|
491
514
|
|
492
515
|
it 'detects that one connection changed to a keyspace and changes the others too' do
|
493
|
-
client.close.
|
494
|
-
io_reactor.stop.
|
495
|
-
io_reactor.start.
|
516
|
+
client.close.value
|
517
|
+
io_reactor.stop.value
|
518
|
+
io_reactor.start.value
|
496
519
|
|
497
520
|
handle_request do |request, connection|
|
498
521
|
if request.is_a?(Protocol::QueryRequest) && request.cql == 'USE system'
|
@@ -501,9 +524,9 @@ module Cql
|
|
501
524
|
end
|
502
525
|
|
503
526
|
c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com h3.example.com]))
|
504
|
-
c.connect.
|
527
|
+
c.connect.value
|
505
528
|
|
506
|
-
c.execute('USE system', :one).
|
529
|
+
c.execute('USE system', :one).value
|
507
530
|
c.keyspace.should == 'system'
|
508
531
|
|
509
532
|
last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
|
@@ -525,7 +548,7 @@ module Cql
|
|
525
548
|
end
|
526
549
|
|
527
550
|
let :result do
|
528
|
-
client.execute('SELECT * FROM things').
|
551
|
+
client.execute('SELECT * FROM things').value
|
529
552
|
end
|
530
553
|
|
531
554
|
before do
|
@@ -569,7 +592,7 @@ module Cql
|
|
569
592
|
context 'when there is an error creating the request' do
|
570
593
|
it 'returns a failed future' do
|
571
594
|
f = client.execute('SELECT * FROM stuff', :foo)
|
572
|
-
expect { f.
|
595
|
+
expect { f.value }.to raise_error(ArgumentError)
|
573
596
|
end
|
574
597
|
end
|
575
598
|
|
@@ -583,12 +606,12 @@ module Cql
|
|
583
606
|
end
|
584
607
|
|
585
608
|
it 'raises an error' do
|
586
|
-
expect { client.execute('SELECT * FROM things').
|
609
|
+
expect { client.execute('SELECT * FROM things').value }.to raise_error(QueryError, 'Blurgh')
|
587
610
|
end
|
588
611
|
|
589
612
|
it 'decorates the error with the CQL that caused it' do
|
590
613
|
begin
|
591
|
-
client.execute('SELECT * FROM things').
|
614
|
+
client.execute('SELECT * FROM things').value
|
592
615
|
rescue QueryError => e
|
593
616
|
e.cql.should == 'SELECT * FROM things'
|
594
617
|
else
|
@@ -607,6 +630,10 @@ module Cql
|
|
607
630
|
[['stuff', 'things', 'item', :varchar]]
|
608
631
|
end
|
609
632
|
|
633
|
+
let :cql do
|
634
|
+
'SELECT * FROM stuff.things WHERE item = ?'
|
635
|
+
end
|
636
|
+
|
610
637
|
before do
|
611
638
|
handle_request do |request|
|
612
639
|
if request.is_a?(Protocol::PrepareRequest)
|
@@ -616,40 +643,48 @@ module Cql
|
|
616
643
|
end
|
617
644
|
|
618
645
|
before do
|
619
|
-
client.connect.
|
646
|
+
client.connect.value
|
620
647
|
end
|
621
648
|
|
622
649
|
it 'sends a prepare request' do
|
623
|
-
client.prepare('SELECT * FROM system.peers').
|
650
|
+
client.prepare('SELECT * FROM system.peers').value
|
624
651
|
last_request.should == Protocol::PrepareRequest.new('SELECT * FROM system.peers')
|
625
652
|
end
|
626
653
|
|
627
654
|
it 'returns a prepared statement' do
|
628
|
-
statement = client.prepare(
|
655
|
+
statement = client.prepare(cql).value
|
629
656
|
statement.should_not be_nil
|
630
657
|
end
|
631
658
|
|
632
|
-
it 'executes a prepared statement' do
|
633
|
-
statement = client.prepare(
|
634
|
-
statement.execute('foo').
|
659
|
+
it 'executes a prepared statement using the default consistency level' do
|
660
|
+
statement = client.prepare(cql).value
|
661
|
+
statement.execute('foo').value
|
635
662
|
last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :quorum)
|
636
663
|
end
|
637
664
|
|
665
|
+
it 'executes a prepared statement using the consistency specified when the client was created' do
|
666
|
+
client = described_class.new(connection_options.merge(default_consistency: :all))
|
667
|
+
client.connect.value
|
668
|
+
statement = client.prepare(cql).value
|
669
|
+
statement.execute('foo').value
|
670
|
+
last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :all)
|
671
|
+
end
|
672
|
+
|
638
673
|
it 'returns a prepared statement that knows the metadata' do
|
639
|
-
statement = client.prepare(
|
674
|
+
statement = client.prepare(cql).value
|
640
675
|
statement.metadata['item'].type == :varchar
|
641
676
|
end
|
642
677
|
|
643
678
|
it 'executes a prepared statement with a specific consistency level' do
|
644
|
-
statement = client.prepare(
|
645
|
-
statement.execute('thing', :local_quorum).
|
679
|
+
statement = client.prepare(cql).value
|
680
|
+
statement.execute('thing', :local_quorum).value
|
646
681
|
last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['thing'], :local_quorum)
|
647
682
|
end
|
648
683
|
|
649
684
|
context 'when there is an error creating the request' do
|
650
685
|
it 'returns a failed future' do
|
651
686
|
f = client.prepare(nil)
|
652
|
-
expect { f.
|
687
|
+
expect { f.value }.to raise_error(ArgumentError)
|
653
688
|
end
|
654
689
|
end
|
655
690
|
|
@@ -660,9 +695,28 @@ module Cql
|
|
660
695
|
Protocol::PreparedResultResponse.new(id, metadata)
|
661
696
|
end
|
662
697
|
end
|
663
|
-
statement = client.prepare(
|
698
|
+
statement = client.prepare(cql).value
|
664
699
|
f = statement.execute
|
665
|
-
expect { f.
|
700
|
+
expect { f.value }.to raise_error(ArgumentError)
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
context 'with multiple connections' do
|
705
|
+
let :connection_options do
|
706
|
+
{:hosts => %w[host1 host2], :port => 12321, :io_reactor => io_reactor, :logger => logger}
|
707
|
+
end
|
708
|
+
|
709
|
+
it 'prepares the statement on all connections' do
|
710
|
+
statement = client.prepare('SELECT * FROM stuff WHERE item = ?').value
|
711
|
+
started_at = Time.now
|
712
|
+
until connections.map { |c| c.requests.last }.all? { |r| r.is_a?(Protocol::ExecuteRequest) }
|
713
|
+
statement.execute('hello').value
|
714
|
+
raise 'Did not receive EXECUTE requests on all connections within 5s' if (Time.now - started_at) > 5
|
715
|
+
end
|
716
|
+
connections.map { |c| c.requests.last }.should == [
|
717
|
+
Protocol::ExecuteRequest.new(id, metadata, ['hello'], :quorum),
|
718
|
+
Protocol::ExecuteRequest.new(id, metadata, ['hello'], :quorum),
|
719
|
+
]
|
666
720
|
end
|
667
721
|
end
|
668
722
|
end
|
@@ -673,39 +727,39 @@ module Cql
|
|
673
727
|
end
|
674
728
|
|
675
729
|
it 'is not connected after #close has been called' do
|
676
|
-
client.connect.
|
677
|
-
client.close.
|
730
|
+
client.connect.value
|
731
|
+
client.close.value
|
678
732
|
client.should_not be_connected
|
679
733
|
end
|
680
734
|
|
681
735
|
it 'complains when #use is called before #connect' do
|
682
|
-
expect { client.use('system').
|
736
|
+
expect { client.use('system').value }.to raise_error(NotConnectedError)
|
683
737
|
end
|
684
738
|
|
685
739
|
it 'complains when #use is called after #close' do
|
686
|
-
client.connect.
|
687
|
-
client.close.
|
688
|
-
expect { client.use('system').
|
740
|
+
client.connect.value
|
741
|
+
client.close.value
|
742
|
+
expect { client.use('system').value }.to raise_error(NotConnectedError)
|
689
743
|
end
|
690
744
|
|
691
745
|
it 'complains when #execute is called before #connect' do
|
692
|
-
expect { client.execute('DELETE FROM stuff WHERE id = 3').
|
746
|
+
expect { client.execute('DELETE FROM stuff WHERE id = 3').value }.to raise_error(NotConnectedError)
|
693
747
|
end
|
694
748
|
|
695
749
|
it 'complains when #execute is called after #close' do
|
696
|
-
client.connect.
|
697
|
-
client.close.
|
698
|
-
expect { client.execute('DELETE FROM stuff WHERE id = 3').
|
750
|
+
client.connect.value
|
751
|
+
client.close.value
|
752
|
+
expect { client.execute('DELETE FROM stuff WHERE id = 3').value }.to raise_error(NotConnectedError)
|
699
753
|
end
|
700
754
|
|
701
755
|
it 'complains when #prepare is called before #connect' do
|
702
|
-
expect { client.prepare('DELETE FROM stuff WHERE id = 3').
|
756
|
+
expect { client.prepare('DELETE FROM stuff WHERE id = 3').value }.to raise_error(NotConnectedError)
|
703
757
|
end
|
704
758
|
|
705
759
|
it 'complains when #prepare is called after #close' do
|
706
|
-
client.connect.
|
707
|
-
client.close.
|
708
|
-
expect { client.prepare('DELETE FROM stuff WHERE id = 3').
|
760
|
+
client.connect.value
|
761
|
+
client.close.value
|
762
|
+
expect { client.prepare('DELETE FROM stuff WHERE id = 3').value }.to raise_error(NotConnectedError)
|
709
763
|
end
|
710
764
|
|
711
765
|
it 'complains when #execute of a prepared statement is called after #close' do
|
@@ -714,10 +768,149 @@ module Cql
|
|
714
768
|
Protocol::PreparedResultResponse.new('A' * 32, [])
|
715
769
|
end
|
716
770
|
end
|
717
|
-
client.connect.
|
718
|
-
statement = client.prepare('DELETE FROM stuff WHERE id = 3').
|
719
|
-
client.close.
|
720
|
-
expect { statement.execute.
|
771
|
+
client.connect.value
|
772
|
+
statement = client.prepare('DELETE FROM stuff WHERE id = 3').value
|
773
|
+
client.close.value
|
774
|
+
expect { statement.execute.value }.to raise_error(NotConnectedError)
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
context 'when nodes go down' do
|
779
|
+
include_context 'peer discovery setup'
|
780
|
+
|
781
|
+
let :connection_options do
|
782
|
+
{:hosts => %w[host1 host2 host3], :port => 12321, :io_reactor => io_reactor}
|
783
|
+
end
|
784
|
+
|
785
|
+
before do
|
786
|
+
client.connect.value
|
787
|
+
end
|
788
|
+
|
789
|
+
it 'clears out old connections and don\'t reuse them for future requests' do
|
790
|
+
connections.first.close
|
791
|
+
expect { 10.times { client.execute('SELECT * FROM something').value } }.to_not raise_error
|
792
|
+
end
|
793
|
+
|
794
|
+
it 'raises NotConnectedError when all nodes are down' do
|
795
|
+
connections.each(&:close)
|
796
|
+
expect { client.execute('SELECT * FROM something').value }.to raise_error(NotConnectedError)
|
797
|
+
end
|
798
|
+
|
799
|
+
it 'reconnects when it receives a status change UP event' do
|
800
|
+
connections.first.close
|
801
|
+
event = Protocol::StatusChangeEventResponse.new('UP', IPAddr.new('1.1.1.1'), 9999)
|
802
|
+
connections.select(&:has_event_listener?).first.trigger_event(event)
|
803
|
+
connections.select(&:connected?).should have(3).items
|
804
|
+
end
|
805
|
+
|
806
|
+
it 'eventually reconnects even when the node doesn\'t respond at first' do
|
807
|
+
timer_promise = Promise.new
|
808
|
+
io_reactor.stub(:schedule_timer).and_return(timer_promise.future)
|
809
|
+
additional_nodes.each { |host| io_reactor.node_down(host.to_s) }
|
810
|
+
connections.first.close
|
811
|
+
event = Protocol::StatusChangeEventResponse.new('UP', IPAddr.new('1.1.1.1'), 9999)
|
812
|
+
connections.select(&:has_event_listener?).first.trigger_event(event)
|
813
|
+
connections.select(&:connected?).should have(2).items
|
814
|
+
additional_nodes.each { |host| io_reactor.node_up(host.to_s) }
|
815
|
+
timer_promise.fulfill
|
816
|
+
connections.select(&:connected?).should have(3).items
|
817
|
+
end
|
818
|
+
|
819
|
+
it 'connects when it receives a topology change UP event' do
|
820
|
+
min_peers[0] = 3
|
821
|
+
event = Protocol::TopologyChangeEventResponse.new('UP', IPAddr.new('1.1.1.1'), 9999)
|
822
|
+
connections.select(&:has_event_listener?).first.trigger_event(event)
|
823
|
+
connections.select(&:connected?).should have(4).items
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'registers a new event listener when the current event listening connection closes' do
|
827
|
+
connections.select(&:has_event_listener?).should have(1).item
|
828
|
+
connections.select(&:has_event_listener?).first.close
|
829
|
+
connections.select(&:connected?).select(&:has_event_listener?).should have(1).item
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
context 'with logging' do
|
834
|
+
include_context 'peer discovery setup'
|
835
|
+
|
836
|
+
it 'logs when connecting to a node' do
|
837
|
+
logger.stub(:debug)
|
838
|
+
client.connect.value
|
839
|
+
logger.should have_received(:debug).with(/Connecting to node at example\.com:12321/)
|
840
|
+
end
|
841
|
+
|
842
|
+
it 'logs when a node is connected' do
|
843
|
+
logger.stub(:info)
|
844
|
+
client.connect.value
|
845
|
+
logger.should have_received(:info).with(/Connected to node .{36} at example\.com:12321 in data center dc1/)
|
846
|
+
end
|
847
|
+
|
848
|
+
it 'logs when all nodes are connected' do
|
849
|
+
logger.stub(:info)
|
850
|
+
client.connect.value
|
851
|
+
logger.should have_received(:info).with(/Cluster connection complete/)
|
852
|
+
end
|
853
|
+
|
854
|
+
it 'logs when the connection fails' do
|
855
|
+
logger.stub(:error)
|
856
|
+
io_reactor.stub(:connect).and_return(Future.failed(StandardError.new('Hurgh blurgh')))
|
857
|
+
client.connect.value rescue nil
|
858
|
+
logger.should have_received(:error).with(/Failed connecting to cluster: Hurgh blurgh/)
|
859
|
+
end
|
860
|
+
|
861
|
+
it 'logs when a single connection fails' do
|
862
|
+
logger.stub(:warn)
|
863
|
+
io_reactor.stub(:connect).and_return(Future.failed(StandardError.new('Hurgh blurgh')))
|
864
|
+
client.connect.value rescue nil
|
865
|
+
logger.should have_received(:warn).with(/Failed connecting to node at example\.com:12321: Hurgh blurgh/)
|
866
|
+
end
|
867
|
+
|
868
|
+
it 'logs when a connection fails' do
|
869
|
+
logger.stub(:warn)
|
870
|
+
client.connect.value
|
871
|
+
connections.sample.close
|
872
|
+
logger.should have_received(:warn).with(/Connection to node .{36} at .+:\d+ in data center .+ unexpectedly closed/)
|
873
|
+
end
|
874
|
+
|
875
|
+
it 'logs when it does a peer discovery' do
|
876
|
+
logger.stub(:debug)
|
877
|
+
client.connect.value
|
878
|
+
logger.should have_received(:debug).with(/Looking for additional nodes/)
|
879
|
+
logger.should have_received(:debug).with(/\d+ additional nodes found/)
|
880
|
+
end
|
881
|
+
|
882
|
+
it 'logs when it receives an UP event' do
|
883
|
+
logger.stub(:debug)
|
884
|
+
client.connect.value
|
885
|
+
event = Protocol::StatusChangeEventResponse.new('UP', IPAddr.new('1.1.1.1'), 9999)
|
886
|
+
connections.select(&:has_event_listener?).first.trigger_event(event)
|
887
|
+
logger.should have_received(:debug).with(/Received UP event/)
|
888
|
+
end
|
889
|
+
|
890
|
+
it 'logs when it fails with a connect after an UP event' do
|
891
|
+
logger.stub(:debug)
|
892
|
+
logger.stub(:warn)
|
893
|
+
additional_nodes.each { |host| io_reactor.node_down(host.to_s) }
|
894
|
+
client.connect.value
|
895
|
+
event = Protocol::StatusChangeEventResponse.new('UP', IPAddr.new('1.1.1.1'), 9999)
|
896
|
+
connections.select(&:has_event_listener?).first.trigger_event(event)
|
897
|
+
logger.should have_received(:warn).with(/Failed connecting to node/).at_least(1).times
|
898
|
+
logger.should have_received(:debug).with(/Scheduling new peer discovery in \d+s/)
|
899
|
+
end
|
900
|
+
|
901
|
+
it 'logs when it disconnects' do
|
902
|
+
logger.stub(:info)
|
903
|
+
client.connect.value
|
904
|
+
client.close.value
|
905
|
+
logger.should have_received(:info).with(/Cluster disconnect complete/)
|
906
|
+
end
|
907
|
+
|
908
|
+
it 'logs when it fails to disconnect' do
|
909
|
+
logger.stub(:error)
|
910
|
+
client.connect.value
|
911
|
+
io_reactor.stub(:stop).and_return(Future.failed(StandardError.new('Hurgh blurgh')))
|
912
|
+
client.close.value rescue nil
|
913
|
+
logger.should have_received(:error).with(/Cluster disconnect failed: Hurgh blurgh/)
|
721
914
|
end
|
722
915
|
end
|
723
916
|
end
|