mongo 2.10.2 → 2.10.3

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 (35) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo/collection/view/readable.rb +2 -2
  5. data/lib/mongo/cursor/builder/get_more_command.rb +4 -1
  6. data/lib/mongo/cursor/builder/kill_cursors_command.rb +16 -5
  7. data/lib/mongo/cursor/builder/op_get_more.rb +2 -2
  8. data/lib/mongo/cursor/builder/op_kill_cursors.rb +17 -5
  9. data/lib/mongo/error/operation_failure.rb +3 -3
  10. data/lib/mongo/protocol/get_more.rb +2 -1
  11. data/lib/mongo/protocol/kill_cursors.rb +6 -13
  12. data/lib/mongo/protocol/serializers.rb +10 -4
  13. data/lib/mongo/retryable.rb +34 -9
  14. data/lib/mongo/version.rb +1 -1
  15. data/mongo.gemspec +1 -1
  16. data/spec/integration/cursor_reaping_spec.rb +1 -1
  17. data/spec/integration/get_more_spec.rb +32 -0
  18. data/spec/integration/retryable_errors_spec.rb +265 -0
  19. data/spec/integration/retryable_writes_spec.rb +36 -36
  20. data/spec/mongo/bulk_write_spec.rb +2 -2
  21. data/spec/mongo/cursor/builder/get_more_command_spec.rb +4 -2
  22. data/spec/mongo/cursor/builder/op_get_more_spec.rb +4 -2
  23. data/spec/mongo/cursor_spec.rb +3 -3
  24. data/spec/mongo/retryable_spec.rb +31 -52
  25. data/spec/runners/sdam/verifier.rb +88 -0
  26. data/spec/spec_tests/retryable_reads_spec.rb +31 -0
  27. data/spec/spec_tests/sdam_monitoring_spec.rb +9 -4
  28. data/spec/support/cluster_tools.rb +4 -3
  29. data/spec/support/command_monitoring.rb +5 -0
  30. data/spec/support/event_subscriber.rb +7 -0
  31. data/spec/support/sdam_monitoring.rb +0 -115
  32. data/spec/support/spec_config.rb +1 -1
  33. data/spec/support/utils.rb +1 -1
  34. metadata +10 -4
  35. metadata.gz.sig +0 -0
@@ -1924,11 +1924,11 @@ describe Mongo::BulkWrite do
1924
1924
  end
1925
1925
 
1926
1926
  let(:first_txn_number) do
1927
- started_events[-2].command['txnNumber'].instance_variable_get(:@integer)
1927
+ started_events[-2].command['txnNumber'].value
1928
1928
  end
1929
1929
 
1930
1930
  let(:second_txn_number) do
1931
- started_events[-1].command['txnNumber'].instance_variable_get(:@integer)
1931
+ started_events[-1].command['txnNumber'].value
1932
1932
  end
1933
1933
 
1934
1934
  it 'inserts the documents' do
@@ -5,7 +5,9 @@ describe Mongo::Cursor::Builder::GetMoreCommand do
5
5
  describe '#specification' do
6
6
 
7
7
  let(:reply) do
8
- Mongo::Protocol::Reply.allocate
8
+ Mongo::Protocol::Reply.allocate.tap do |reply|
9
+ allow(reply).to receive(:cursor_id).and_return(8000)
10
+ end
9
11
  end
10
12
 
11
13
  let(:result) do
@@ -54,7 +56,7 @@ describe Mongo::Cursor::Builder::GetMoreCommand do
54
56
  end
55
57
 
56
58
  it 'includes getMore with cursor id' do
57
- expect(selector[:getMore]).to eq(cursor.id)
59
+ expect(selector[:getMore]).to eq(BSON::Int64.new(8000))
58
60
  end
59
61
 
60
62
  it 'includes the collection name' do
@@ -5,7 +5,9 @@ describe Mongo::Cursor::Builder::OpGetMore do
5
5
  describe '#specification' do
6
6
 
7
7
  let(:reply) do
8
- Mongo::Protocol::Reply.allocate
8
+ Mongo::Protocol::Reply.allocate.tap do |reply|
9
+ allow(reply).to receive(:cursor_id).and_return(8000)
10
+ end
9
11
  end
10
12
 
11
13
  let(:result) do
@@ -38,7 +40,7 @@ describe Mongo::Cursor::Builder::OpGetMore do
38
40
  end
39
41
 
40
42
  it 'includes the cursor id' do
41
- expect(specification[:cursor_id]).to eq(cursor.id)
43
+ expect(specification[:cursor_id]).to eq(BSON::Int64.new(8000))
42
44
  end
43
45
 
44
46
  it 'includes the database name' do
@@ -301,7 +301,7 @@ describe Mongo::Cursor do
301
301
  Mongo::Collection::View.new(
302
302
  authorized_collection,
303
303
  {},
304
- :batch_size => 2
304
+ :batch_size => 2,
305
305
  )
306
306
  end
307
307
 
@@ -312,9 +312,9 @@ describe Mongo::Cursor do
312
312
 
313
313
  it 'schedules a kill cursors op' do
314
314
  cluster.instance_variable_get(:@periodic_executor).flush
315
- expect {
315
+ expect do
316
316
  cursor.to_a
317
- }.to raise_exception(Mongo::Error::OperationFailure, /[cC]ursor.*not found/)
317
+ end.to raise_exception(Mongo::Error::OperationFailure, /[cC]ursor.*not found/)
318
318
  end
319
319
 
320
320
  context 'when the cursor is unregistered before the kill cursors operations are executed' do
@@ -175,83 +175,62 @@ describe Mongo::Retryable do
175
175
 
176
176
  context 'when an operation failure occurs' do
177
177
 
178
- context 'when the cluster is not a mongos' do
178
+ context 'when the operation failure is not retryable' do
179
+
180
+ let(:error) do
181
+ Mongo::Error::OperationFailure.new('not authorized')
182
+ end
179
183
 
180
184
  before do
181
- expect(operation).to receive(:execute).and_raise(Mongo::Error::OperationFailure).ordered
182
- expect(cluster).to receive(:sharded?).and_return(false)
185
+ expect(operation).to receive(:execute).and_raise(error).ordered
183
186
  end
184
187
 
185
- it 'raises an exception' do
188
+ it 'raises the exception' do
186
189
  expect {
187
190
  read_operation
188
191
  }.to raise_error(Mongo::Error::OperationFailure)
189
192
  end
190
193
  end
191
194
 
192
- context 'when the cluster is a mongos' do
195
+ context 'when the operation failure is retryable' do
193
196
 
194
- context 'when the operation failure is not retryable' do
197
+ let(:error) do
198
+ Mongo::Error::OperationFailure.new('not master')
199
+ end
195
200
 
196
- let(:error) do
197
- Mongo::Error::OperationFailure.new('not authorized')
198
- end
201
+ context 'when the retry succeeds' do
199
202
 
200
203
  before do
204
+ expect(retryable).to receive(:select_server).ordered
201
205
  expect(operation).to receive(:execute).and_raise(error).ordered
202
- expect(cluster).to receive(:sharded?).and_return(true)
206
+ expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
207
+ expect(retryable).to receive(:select_server).ordered
208
+ expect(operation).to receive(:execute).and_return(true).ordered
203
209
  end
204
210
 
205
- it 'raises the exception' do
206
- expect {
207
- read_operation
208
- }.to raise_error(Mongo::Error::OperationFailure)
211
+ it 'returns the result' do
212
+ expect(read_operation).to be true
209
213
  end
210
214
  end
211
215
 
212
- context 'when the operation failure is retryable' do
216
+ context 'when the retry fails once and then succeeds' do
217
+ let(:max_read_retries) { 2 }
213
218
 
214
- let(:error) do
215
- Mongo::Error::OperationFailure.new('not master')
216
- end
217
-
218
- context 'when the retry succeeds' do
219
+ before do
220
+ expect(retryable).to receive(:select_server).ordered
221
+ expect(operation).to receive(:execute).and_raise(error).ordered
219
222
 
220
- before do
221
- expect(retryable).to receive(:select_server).ordered
222
- expect(operation).to receive(:execute).and_raise(error).ordered
223
- expect(cluster).to receive(:sharded?).and_return(true)
224
- expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
225
- expect(retryable).to receive(:select_server).ordered
226
- expect(operation).to receive(:execute).and_return(true).ordered
227
- end
223
+ expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
224
+ expect(retryable).to receive(:select_server).ordered
225
+ expect(operation).to receive(:execute).and_raise(error).ordered
228
226
 
229
- it 'returns the result' do
230
- expect(read_operation).to be true
231
- end
227
+ expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
228
+ expect(retryable).to receive(:select_server).ordered
229
+ expect(operation).to receive(:execute).and_return(true).ordered
232
230
  end
233
231
 
234
- context 'when the retry fails once and then succeeds' do
235
- let(:max_read_retries) { 2 }
236
-
237
- before do
238
- expect(retryable).to receive(:select_server).ordered
239
- expect(operation).to receive(:execute).and_raise(error).ordered
240
-
241
- expect(cluster).to receive(:sharded?).and_return(true)
242
- expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
243
- expect(retryable).to receive(:select_server).ordered
244
- expect(operation).to receive(:execute).and_raise(error).ordered
245
-
246
- expect(cluster).to receive(:sharded?).and_return(true)
247
- expect(client).to receive(:read_retry_interval).and_return(0.1).ordered
248
- expect(retryable).to receive(:select_server).ordered
249
- expect(operation).to receive(:execute).and_return(true).ordered
250
- end
251
-
252
- it 'returns the result' do
253
- expect(read_operation).to be true
254
- end
232
+ it 'returns the result' do
233
+ expect(read_operation).to be true
255
234
  end
256
235
  end
257
236
  end
@@ -0,0 +1,88 @@
1
+ module Sdam
2
+ class Verifier
3
+ include RSpec::Matchers
4
+
5
+ def verify_sdam_event(expected_events, actual_events, i)
6
+ expect(expected_events.length).to be > i
7
+ expect(actual_events.length).to be > i
8
+
9
+ expected_event = expected_events[i]
10
+ actual_event = actual_events[i]
11
+
12
+ actual_event_name = Utils.underscore(actual_event.class.name.sub(/.*::/, ''))
13
+ actual_event_name = actual_event_name.to_s.sub('topology_changed', 'topology_description_changed') + '_event'
14
+ expect(actual_event_name).to eq(expected_event.name)
15
+
16
+ send("verify_#{expected_event.name}", expected_event, actual_event)
17
+ end
18
+
19
+ def verify_topology_opening_event(expected, actual)
20
+ expect(actual.topology).not_to be nil
21
+ end
22
+
23
+ def verify_topology_description_changed_event(expected, actual)
24
+ verify_topology_matches(expected.data['previousDescription'], actual.previous_topology)
25
+ verify_topology_matches(expected.data['newDescription'], actual.new_topology)
26
+ end
27
+
28
+ def verify_topology_matches(expected, actual)
29
+ expected_type = ::Mongo::Cluster::Topology.const_get(expected['topologyType'])
30
+ expect(actual).to be_a(expected_type)
31
+
32
+ expect(actual.replica_set_name).to eq(expected['setName'])
33
+
34
+ expected['servers'].each do |server|
35
+ desc = actual.server_descriptions[server['address'].to_s]
36
+ expect(desc).not_to be nil
37
+ verify_description_matches(server, desc)
38
+ end
39
+
40
+ actual.server_descriptions.keys.each do |address_str|
41
+ expect(
42
+ expected['servers'].any? { |server| server['address'] == address_str }
43
+ ).to be true
44
+ end
45
+ end
46
+
47
+ def verify_server_opening_event(expected, actual)
48
+ expect(actual.address.to_s).to eq(expected.data['address'])
49
+ end
50
+
51
+ def verify_server_description_changed_event(expected, actual)
52
+ verify_description_matches(expected.data['previousDescription'], actual.previous_description)
53
+ verify_description_matches(expected.data['newDescription'], actual.new_description)
54
+ end
55
+
56
+ def verify_description_matches(expected, actual)
57
+ case expected['type']
58
+ when 'Standalone'
59
+ expect(actual).to be_standalone
60
+ when 'RSPrimary'
61
+ expect(actual).to be_primary
62
+ when 'RSSecondary'
63
+ expect(actual).to be_secondary
64
+ when 'RSArbiter'
65
+ expect(actual).to be_arbiter
66
+ when 'Mongos'
67
+ expect(actual).to be_mongos
68
+ when 'Unknown', 'PossiblePrimary'
69
+ expect(actual).to be_unknown
70
+ when 'RSGhost'
71
+ expect(actual).to be_ghost
72
+ when 'RSOther'
73
+ expect(actual).to be_other
74
+ end
75
+
76
+ expect(actual.address.to_s).to eq(expected['address'])
77
+ expect(actual.arbiters).to eq(expected['arbiters'])
78
+ expect(actual.hosts).to eq(expected['hosts'])
79
+ expect(actual.passives).to eq(expected['passives'])
80
+ expect(actual.primary_host).to eq(expected['primary'])
81
+ expect(actual.replica_set_name).to eq(expected['setName'])
82
+ end
83
+
84
+ def verify_server_closed_event(expected, actual)
85
+ expect(actual.address.to_s).to eq(expected.data['address'])
86
+ end
87
+ end
88
+ end
@@ -12,3 +12,34 @@ describe 'Retryable reads spec tests' do
12
12
  end
13
13
  end
14
14
  end
15
+
16
+ describe 'Retryable reads spec tests - legacy' do
17
+ require_no_multi_shard
18
+
19
+ define_crud_spec_tests(RETRYABLE_READS_TESTS) do |spec, req, test|
20
+ let(:client_options) do
21
+ {
22
+ max_read_retries: 1,
23
+ read_retry_interval: 0,
24
+ retry_reads: false,
25
+ }.update(test.client_options)
26
+ end
27
+
28
+ let(:client) do
29
+ authorized_client.with(client_options).tap do |client|
30
+ client.subscribe(Mongo::Monitoring::COMMAND, event_subscriber)
31
+ end
32
+ end
33
+
34
+ around do |example|
35
+ desc = example.full_description
36
+ # Skip tests that disable modern retryable reads because they expect
37
+ # no retries - and since legacy retryable reads are used, the tests
38
+ # will fail.
39
+ if desc =~/retryReads is false|fails on first attempt/
40
+ skip 'Test not applicable to legacy read retries'
41
+ end
42
+ example.run
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,7 @@
1
1
  require 'lite_spec_helper'
2
2
 
3
+ require_relative '../runners/sdam/verifier'
4
+
3
5
  describe 'SDAM Monitoring' do
4
6
  include Mongo::SDAM
5
7
 
@@ -67,12 +69,15 @@ describe 'SDAM Monitoring' do
67
69
  expect(@subscriber.phase_events(phase_index).length).to eq(phase.outcome.events.length)
68
70
  end
69
71
 
72
+ let(:verifier) do
73
+ Sdam::Verifier.new
74
+ end
75
+
70
76
  phase.outcome.events.each_with_index do |expectation, index|
71
77
 
72
- it "expects a #{expectation.name} to be published" do
73
- published_event = @subscriber.phase_events(phase_index)[index]
74
- expect(published_event).not_to be_nil
75
- expect(published_event).to match_sdam_monitoring_event(expectation)
78
+ it "expects event #{index+1} to be #{expectation.name}" do
79
+ verifier.verify_sdam_event(
80
+ phase.outcome.events, @subscriber.phase_events(phase_index), index)
76
81
  end
77
82
  end
78
83
  end
@@ -142,6 +142,7 @@ class ClusterTools
142
142
  # - call step down on the existing primary
143
143
  # - call step up on the target in a loop until it becomes the primary
144
144
  def change_primary
145
+ start = Time.now
145
146
  existing_primary = admin_client.cluster.next_primary
146
147
  existing_primary_address = existing_primary.address
147
148
 
@@ -171,7 +172,7 @@ class ClusterTools
171
172
  persistently_step_up(target.address)
172
173
 
173
174
  new_primary = admin_client.cluster.next_primary
174
- puts "#{Time.now} [CT] Primary changed to #{new_primary.address}"
175
+ puts "#{Time.now} [CT] Primary changed to #{new_primary.address}. Time to change primaries: #{Time.now - start}"
175
176
  end
176
177
 
177
178
  def persistently_step_up(address)
@@ -348,8 +349,6 @@ class ClusterTools
348
349
  end
349
350
  end
350
351
 
351
- private
352
-
353
352
  def each_server(&block)
354
353
  admin_client.cluster.servers_list.each(&block)
355
354
  end
@@ -360,6 +359,8 @@ class ClusterTools
360
359
  end
361
360
  end
362
361
 
362
+ private
363
+
363
364
  def reset_server_states
364
365
  each_server do |server|
365
366
  server.unknown!
@@ -134,6 +134,11 @@ module Mongo
134
134
  end
135
135
  if expected.keys.first == '$numberLong'
136
136
  converted = expected.values.first.to_i
137
+ if actual.is_a?(BSON::Int64)
138
+ actual = actual.value
139
+ elsif actual.is_a?(BSON::Int32)
140
+ return false
141
+ end
137
142
  (actual == converted) || actual >= 0
138
143
  else
139
144
  expected.each do |key, value|
@@ -44,6 +44,13 @@ class EventSubscriber
44
44
  end
45
45
  end
46
46
 
47
+ # Filters command started events for the specified command name.
48
+ def command_started_events(command_name)
49
+ started_events.select do |event|
50
+ event.command[command_name]
51
+ end
52
+ end
53
+
47
54
  # Locates command stated events for the specified command name,
48
55
  # asserts that there is exactly one such event, and returns it.
49
56
  def single_command_started_event(command_name)
@@ -13,123 +13,8 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- RSpec::Matchers.define :match_topology_opening_event do |expectation|
17
-
18
- match do |event|
19
- event.is_a?(Mongo::Monitoring::Event::TopologyOpening) &&
20
- event.topology != nil
21
- end
22
- end
23
-
24
- RSpec::Matchers.define :match_topology_description_changed_event do |expectation|
25
- include Mongo::SDAMMonitoring::Matchable
26
-
27
- match do |event|
28
- event.is_a?(Mongo::Monitoring::Event::TopologyChanged) &&
29
- topologies_match?(event, expectation)
30
- end
31
- end
32
-
33
- RSpec::Matchers.define :match_server_opening_event do |expectation|
34
-
35
- match do |event|
36
- event.is_a?(Mongo::Monitoring::Event::ServerOpening) &&
37
- event.address.to_s == expectation.data['address']
38
- end
39
- end
40
-
41
- RSpec::Matchers.define :match_server_description_changed_event do |expectation|
42
- include Mongo::SDAMMonitoring::Matchable
43
-
44
- match do |event|
45
- event.is_a?(Mongo::Monitoring::Event::ServerDescriptionChanged) &&
46
- descriptions_match?(event, expectation)
47
- end
48
- end
49
-
50
- RSpec::Matchers.define :match_server_closed_event do |expectation|
51
-
52
- match do |event|
53
- event.is_a?(Mongo::Monitoring::Event::ServerClosed) &&
54
- event.address.to_s == expectation.data['address']
55
- end
56
- end
57
-
58
- RSpec::Matchers.define :match_sdam_monitoring_event do |expectation|
59
-
60
- match do |event|
61
- expect(event).to send("match_#{expectation.name}", expectation)
62
- end
63
- end
64
-
65
16
  module Mongo
66
17
  module SDAMMonitoring
67
- module Matchable
68
-
69
- def descriptions_match?(event, expectation)
70
- description_matches?(event.previous_description, expectation.data['previousDescription']) &&
71
- description_matches?(event.new_description, expectation.data['newDescription'])
72
- end
73
-
74
- def topologies_match?(event, expectation)
75
- unless topology_matches?(event.previous_topology, expectation.data['previousDescription'])
76
- if ENV['VERBOSE_MATCHERS']
77
- $stderr.puts "Previous topology mismatch"
78
- end
79
- return false
80
- end
81
- unless topology_matches?(event.new_topology, expectation.data['newDescription'])
82
- if ENV['VERBOSE_MATCHERS']
83
- $stderr.puts "New topology mismatch:\nHave: #{event.new_topology}\nWant: #{expectation.data['newDescription']}"
84
- end
85
- return false
86
- end
87
- true
88
- end
89
-
90
- def description_matches?(actual, expected)
91
- type_ok = case expected['type']
92
- when 'Standalone' then actual.standalone?
93
- when 'RSPrimary' then actual.primary?
94
- when 'RSSecondary' then actual.secondary?
95
- when 'RSArbiter' then actual.arbiter?
96
- when 'Mongos' then actual.mongos?
97
- when 'Unknown' then actual.unknown?
98
- when 'PossiblePrimary' then actual.unknown?
99
- when 'RSGhost' then actual.ghost?
100
- when 'RSOther' then actual.other?
101
- end
102
- return false unless type_ok
103
-
104
- return false if actual.address.to_s != expected['address']
105
- return false if actual.arbiters != expected['arbiters']
106
- return false if actual.hosts != expected['hosts']
107
- return false if actual.passives != expected['passives']
108
- return false if actual.primary_host != expected['primary']
109
- return false if actual.replica_set_name != expected['setName']
110
- true
111
- end
112
-
113
- def topology_matches?(actual, expected)
114
- expected_type = ::Mongo::Cluster::Topology.const_get(expected['topologyType'])
115
- return false unless actual.is_a?(expected_type)
116
-
117
- return false unless actual.replica_set_name == expected['setName']
118
-
119
- expected['servers'].each do |server|
120
- desc = actual.server_descriptions[server['address'].to_s]
121
- return false unless description_matches?(desc, server)
122
- end
123
-
124
- actual.server_descriptions.keys.each do |address_str|
125
- unless expected['servers'].any? { |server| server['address'] == address_str }
126
- return false
127
- end
128
- end
129
-
130
- true
131
- end
132
- end
133
18
 
134
19
  # Test subscriber for SDAM monitoring.
135
20
  #