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.
Files changed (42) hide show
  1. data/README.md +2 -2
  2. data/lib/cql/client.rb +9 -5
  3. data/lib/cql/client/asynchronous_client.rb +105 -192
  4. data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
  5. data/lib/cql/client/connection_helper.rb +155 -0
  6. data/lib/cql/client/connection_manager.rb +56 -0
  7. data/lib/cql/client/keyspace_changer.rb +27 -0
  8. data/lib/cql/client/null_logger.rb +21 -0
  9. data/lib/cql/client/request_runner.rb +5 -3
  10. data/lib/cql/client/synchronous_client.rb +5 -5
  11. data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
  12. data/lib/cql/future.rb +320 -210
  13. data/lib/cql/io/connection.rb +5 -5
  14. data/lib/cql/io/io_reactor.rb +21 -23
  15. data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
  16. data/lib/cql/protocol/encoding.rb +5 -1
  17. data/lib/cql/protocol/requests/register_request.rb +2 -0
  18. data/lib/cql/protocol/type_converter.rb +1 -0
  19. data/lib/cql/version.rb +1 -1
  20. data/spec/cql/client/asynchronous_client_spec.rb +368 -175
  21. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
  22. data/spec/cql/client/connection_helper_spec.rb +335 -0
  23. data/spec/cql/client/connection_manager_spec.rb +118 -0
  24. data/spec/cql/client/keyspace_changer_spec.rb +50 -0
  25. data/spec/cql/client/request_runner_spec.rb +12 -12
  26. data/spec/cql/client/synchronous_client_spec.rb +15 -15
  27. data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
  28. data/spec/cql/future_spec.rb +529 -301
  29. data/spec/cql/io/connection_spec.rb +12 -12
  30. data/spec/cql/io/io_reactor_spec.rb +61 -61
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
  32. data/spec/cql/protocol/encoding_spec.rb +5 -0
  33. data/spec/cql/protocol/type_converter_spec.rb +1 -1
  34. data/spec/cql/time_uuid_spec.rb +7 -7
  35. data/spec/integration/client_spec.rb +2 -2
  36. data/spec/integration/io_spec.rb +20 -20
  37. data/spec/integration/protocol_spec.rb +17 -17
  38. data/spec/integration/regression_spec.rb +6 -0
  39. data/spec/integration/uuid_spec.rb +4 -0
  40. data/spec/support/fake_io_reactor.rb +38 -8
  41. data/spec/support/fake_server.rb +3 -3
  42. metadata +12 -2
@@ -23,7 +23,7 @@ module Cql
23
23
  @lock = Mutex.new
24
24
  @connected = false
25
25
  @write_buffer = ByteBuffer.new
26
- @connected_future = Future.new
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
- @connected_future.complete!(self)
44
+ @connected_promise.fulfill(self)
45
45
  end
46
46
  rescue Errno::EISCONN
47
47
  @connected = true
48
- @connected_future.complete!(self)
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
- @connected_future
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
- @connected_future.fail!(cause)
212
+ @connected_promise.fail(cause)
213
213
  end
214
214
  @connected = false
215
215
  if @closed_listener
@@ -95,8 +95,8 @@ module Cql
95
95
  @io_loop.add_socket(@unblocker)
96
96
  @running = false
97
97
  @stopped = false
98
- @started_future = Future.new
99
- @stopped_future = Future.new
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
- @stopped_future.on_failure(&listener)
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 @started_future if @running
133
+ return @started_promise.future if @running
134
134
  @running = true
135
135
  end
136
136
  Thread.start do
137
- @started_future.complete!(self)
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
- @stopped_future.fail!($!)
145
+ @stopped_promise.fail($!)
146
146
  else
147
- @stopped_future.complete!(self)
147
+ @stopped_promise.fulfill(self)
148
148
  end
149
149
  end
150
150
  end
151
- @started_future
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
- @stopped_future
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
- f = Future.new
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.dup
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, future)
279
+ def schedule_timer(timeout, promise=Promise.new)
282
280
  @lock.synchronize do
283
- timers = @timers.dup
284
- timers << [@clock.now + timeout, future]
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!(CancelledError.new)
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
- break unless pair[0] <= @clock.now && pair[1]
339
- pair[1].complete!
340
- pair[1] = nil
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
- @responses = Array.new(128) { nil }
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
- @closed_future = Future.new
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
- @closed_future.on_complete(&listener)
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 << listener
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
- future = Future.new
107
+ promise = RequestPromise.new(request)
95
108
  id = nil
96
109
  @lock.synchronize do
97
110
  if (id = next_stream_id)
98
- @responses[id] = future
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
- @request_queue_in << [request.encode_frame(0), future]
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
- @closed_future
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
- return if @event_listeners.empty?
166
+ event_listeners = nil
139
167
  @lock.synchronize do
140
- @event_listeners.each do |listener|
141
- listener.call(@current_frame.body) rescue nil
142
- end
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
- future = @lock.synchronize do
148
- future = @responses[id]
149
- @responses[id] = nil
150
- future
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
- future.complete!(response)
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
- request_buffer = nil
197
+ frame = nil
168
198
  @lock.synchronize do
169
199
  if @request_queue_out.any? && (id = next_stream_id)
170
- request_buffer, future = @request_queue_out.shift
171
- @responses[id] = future
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, request_buffer)
176
- @connection.write(request_buffer)
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
- @responses.each_with_index do |future, i|
187
- if future
188
- @responses[i].fail!(request_failure_cause)
189
- @responses[i] = nil
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 |_, future|
193
- future.fail!(request_failure_cause)
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 |_, future|
197
- future.fail!(request_failure_cause)
227
+ @request_queue_out.each do |promise|
228
+ promise.fail(request_failure_cause)
198
229
  end
199
230
  @request_queue_out.clear
200
- if cause
201
- @closed_future.fail!(cause)
202
- else
203
- @closed_future.complete!
204
- end
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
- @responses.each_with_index do |task, index|
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
- write_varint(buffer, uuid.value)
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)
@@ -3,6 +3,8 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class RegisterRequest < Request
6
+ attr_reader :events
7
+
6
8
  def initialize(*events)
7
9
  super(11)
8
10
  @events = events
@@ -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),
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.1.0.pre3'.freeze
4
+ VERSION = '1.1.0.pre6'.freeze
5
5
  end
@@ -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.get
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.get
64
- client.connect.get
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.get
71
- io_reactor.stop.get
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.get
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.get
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.get
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.get
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.get }.to raise_error(Io::ConnectionError)
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.get.should equal(client)
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.get
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.get
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.get
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.get
131
- io_reactor.stop.get
132
- io_reactor.start.get
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.get
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.get
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.get
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.get }.to raise_error(InvalidKeyspaceNameError)
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
- let :local_info do
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.get
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.get
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.get
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.get
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.get
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.get
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.get
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.get }.to raise_error(ArgumentError)
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.get rescue nil
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.get
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.get
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.get
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.get }.to raise_error(AuthenticationError)
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.get }.to raise_error(AuthenticationError)
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.get rescue nil
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.get
363
- client.close.get
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.get
380
+ client.close.value
369
381
  end
370
382
 
371
383
  it 'accepts multiple calls to #close' do
372
- client.connect.get
373
- client.close.get
374
- client.close.get
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.get.close.get.should equal(client)
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.get }.to raise_error('Bork!')
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.get
395
- client.use('system').get
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.get
401
- io_reactor.stop.get
402
- io_reactor.start.get
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.get
417
+ c.connect.value
406
418
 
407
- c.use('system').get
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.get
423
- client.use('system').get
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.get
429
- expect { client.use('system; DROP KEYSPACE system').get }.to raise_error(InvalidKeyspaceNameError)
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.get
439
- client.use('"system"').get
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.get
462
+ client.connect.value
447
463
  end
448
464
 
449
- it 'asks the connection to execute the query' do
450
- client.execute('UPDATE stuff SET thing = 1 WHERE id = 3').get
451
- last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :quorum)
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).get
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').get
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').get
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').get
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.get
494
- io_reactor.stop.get
495
- io_reactor.start.get
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.get
527
+ c.connect.value
505
528
 
506
- c.execute('USE system', :one).get
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').get
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.get }.to raise_error(ArgumentError)
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').get }.to raise_error(QueryError, 'Blurgh')
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').get
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.get
646
+ client.connect.value
620
647
  end
621
648
 
622
649
  it 'sends a prepare request' do
623
- client.prepare('SELECT * FROM system.peers').get
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('SELECT * FROM stuff.things WHERE item = ?').get
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('SELECT * FROM stuff.things WHERE item = ?').get
634
- statement.execute('foo').get
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('SELECT * FROM stuff.things WHERE item = ?').get
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('SELECT * FROM stuff.things WHERE item = ?').get
645
- statement.execute('thing', :local_quorum).get
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.get }.to raise_error(ArgumentError)
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('SELECT * FROM stuff.things WHERE item = ?').get
698
+ statement = client.prepare(cql).value
664
699
  f = statement.execute
665
- expect { f.get }.to raise_error(ArgumentError)
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.get
677
- client.close.get
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').get }.to raise_error(NotConnectedError)
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.get
687
- client.close.get
688
- expect { client.use('system').get }.to raise_error(NotConnectedError)
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').get }.to raise_error(NotConnectedError)
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.get
697
- client.close.get
698
- expect { client.execute('DELETE FROM stuff WHERE id = 3').get }.to raise_error(NotConnectedError)
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').get }.to raise_error(NotConnectedError)
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.get
707
- client.close.get
708
- expect { client.prepare('DELETE FROM stuff WHERE id = 3').get }.to raise_error(NotConnectedError)
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.get
718
- statement = client.prepare('DELETE FROM stuff WHERE id = 3').get
719
- client.close.get
720
- expect { statement.execute.get }.to raise_error(NotConnectedError)
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