mongo 2.19.2 → 2.20.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Rakefile +27 -154
- data/lib/mongo/cluster/topology/base.rb +16 -0
- data/lib/mongo/cluster.rb +27 -1
- data/lib/mongo/collection/view/iterable.rb +1 -0
- data/lib/mongo/collection.rb +27 -3
- data/lib/mongo/error/transactions_not_supported.rb +34 -0
- data/lib/mongo/error.rb +1 -0
- data/lib/mongo/grid/fs_bucket.rb +6 -0
- data/lib/mongo/monitoring/event/secure.rb +1 -1
- data/lib/mongo/operation/create_search_indexes/op_msg.rb +31 -0
- data/lib/mongo/operation/create_search_indexes.rb +15 -0
- data/lib/mongo/operation/drop_search_index/op_msg.rb +33 -0
- data/lib/mongo/operation/drop_search_index.rb +15 -0
- data/lib/mongo/operation/shared/executable.rb +43 -27
- data/lib/mongo/operation/shared/response_handling.rb +23 -25
- data/lib/mongo/operation/shared/specifiable.rb +7 -0
- data/lib/mongo/operation/update_search_index/op_msg.rb +34 -0
- data/lib/mongo/operation/update_search_index.rb +15 -0
- data/lib/mongo/operation.rb +3 -0
- data/lib/mongo/retryable/read_worker.rb +7 -6
- data/lib/mongo/retryable/write_worker.rb +7 -4
- data/lib/mongo/retryable.rb +2 -2
- data/lib/mongo/search_index/view.rb +232 -0
- data/lib/mongo/server/app_metadata/environment.rb +64 -9
- data/lib/mongo/server/app_metadata.rb +5 -4
- data/lib/mongo/server/description/features.rb +1 -0
- data/lib/mongo/server_selector/base.rb +32 -6
- data/lib/mongo/session/server_session/dirtyable.rb +52 -0
- data/lib/mongo/session/server_session.rb +3 -0
- data/lib/mongo/session/session_pool.rb +12 -18
- data/lib/mongo/session.rb +32 -0
- data/lib/mongo/uri.rb +0 -4
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- data/mongo.gemspec +1 -7
- data/spec/atlas/atlas_connectivity_spec.rb +5 -9
- data/spec/atlas/operations_spec.rb +1 -5
- data/spec/faas/ruby-sam-app/Gemfile +9 -0
- data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
- data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
- data/spec/faas/ruby-sam-app/template.yaml +48 -0
- data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
- data/spec/integration/retryable_reads_errors_spec.rb +161 -8
- data/spec/integration/retryable_writes_errors_spec.rb +156 -0
- data/spec/integration/search_indexes_prose_spec.rb +168 -0
- data/spec/lite_spec_helper.rb +32 -10
- data/spec/mongo/cluster_spec.rb +36 -0
- data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
- data/spec/mongo/collection/view/explainable_spec.rb +2 -0
- data/spec/mongo/collection_crud_spec.rb +1 -1
- data/spec/mongo/operation/insert_spec.rb +1 -1
- data/spec/mongo/retryable/write_worker_spec.rb +39 -0
- data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
- data/spec/mongo/server/app_metadata_spec.rb +12 -2
- data/spec/mongo/server/connection_spec.rb +4 -0
- data/spec/mongo/session/session_pool_spec.rb +1 -16
- data/spec/mongo/session_transaction_spec.rb +15 -0
- data/spec/mongo/uri_spec.rb +0 -9
- data/spec/runners/crud/test.rb +0 -8
- data/spec/runners/crud.rb +1 -1
- data/spec/runners/transactions/test.rb +12 -3
- data/spec/runners/unified/assertions.rb +16 -3
- data/spec/runners/unified/crud_operations.rb +12 -0
- data/spec/runners/unified/search_index_operations.rb +63 -0
- data/spec/runners/unified/support_operations.rb +3 -5
- data/spec/runners/unified/test.rb +11 -2
- data/spec/shared/lib/mrss/docker_runner.rb +3 -0
- data/spec/shared/share/Dockerfile.erb +20 -69
- data/spec/shared/shlib/server.sh +1 -0
- data/spec/shared/shlib/set_env.sh +5 -28
- data/spec/spec_helper.rb +1 -1
- data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
- data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
- data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
- data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
- data/spec/spec_tests/data/index_management/createSearchIndex.yml +64 -0
- data/spec/spec_tests/data/index_management/createSearchIndexes.yml +86 -0
- data/spec/spec_tests/data/index_management/dropSearchIndex.yml +43 -0
- data/spec/spec_tests/data/index_management/listSearchIndexes.yml +91 -0
- data/spec/spec_tests/data/index_management/updateSearchIndex.yml +46 -0
- data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
- data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
- data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
- data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
- data/spec/spec_tests/index_management_unified_spec.rb +13 -0
- data/spec/spec_tests/run_command_unified_spec.rb +13 -0
- data/spec/spec_tests/sdam_unified_spec.rb +2 -0
- data/spec/support/constraints.rb +6 -0
- data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +19 -0
- data/spec/support/ocsp +1 -1
- data/spec/support/recording_logger.rb +27 -0
- data/spec/support/spec_config.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +1329 -1285
- metadata.gz.sig +3 -2
- data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
|
@@ -189,4 +189,160 @@ describe 'Retryable writes errors tests' do
|
|
|
189
189
|
})
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
|
+
|
|
193
|
+
context 'Retries in a sharded cluster' do
|
|
194
|
+
require_topology :sharded
|
|
195
|
+
min_server_version '4.2'
|
|
196
|
+
require_no_auth
|
|
197
|
+
|
|
198
|
+
let(:subscriber) { Mrss::EventSubscriber.new }
|
|
199
|
+
|
|
200
|
+
let(:insert_started_events) do
|
|
201
|
+
subscriber.started_events.select { |e| e.command_name == "insert" }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
let(:insert_failed_events) do
|
|
205
|
+
subscriber.failed_events.select { |e| e.command_name == "insert" }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
let(:insert_succeeded_events) do
|
|
209
|
+
subscriber.succeeded_events.select { |e| e.command_name == "insert" }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context 'when another mongos is available' do
|
|
213
|
+
|
|
214
|
+
let(:first_mongos) do
|
|
215
|
+
Mongo::Client.new(
|
|
216
|
+
[SpecConfig.instance.addresses.first],
|
|
217
|
+
direct_connection: true,
|
|
218
|
+
database: 'admin'
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
let(:second_mongos) do
|
|
223
|
+
Mongo::Client.new(
|
|
224
|
+
[SpecConfig.instance.addresses.last],
|
|
225
|
+
direct_connection: false,
|
|
226
|
+
database: 'admin'
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
let(:client) do
|
|
231
|
+
new_local_client(
|
|
232
|
+
[
|
|
233
|
+
SpecConfig.instance.addresses.first,
|
|
234
|
+
SpecConfig.instance.addresses.last,
|
|
235
|
+
],
|
|
236
|
+
SpecConfig.instance.test_options.merge(retry_writes: true)
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
let(:expected_servers) do
|
|
241
|
+
[
|
|
242
|
+
SpecConfig.instance.addresses.first.to_s,
|
|
243
|
+
SpecConfig.instance.addresses.last.to_s
|
|
244
|
+
].sort
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
before do
|
|
248
|
+
skip 'This test requires at least two mongos' if SpecConfig.instance.addresses.length < 2
|
|
249
|
+
|
|
250
|
+
first_mongos.database.command(
|
|
251
|
+
configureFailPoint: 'failCommand',
|
|
252
|
+
mode: { times: 1 },
|
|
253
|
+
data: {
|
|
254
|
+
failCommands: %w(insert),
|
|
255
|
+
closeConnection: false,
|
|
256
|
+
errorCode: 6,
|
|
257
|
+
errorLabels: ['RetryableWriteError']
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
second_mongos.database.command(
|
|
262
|
+
configureFailPoint: 'failCommand',
|
|
263
|
+
mode: { times: 1 },
|
|
264
|
+
data: {
|
|
265
|
+
failCommands: %w(insert),
|
|
266
|
+
closeConnection: false,
|
|
267
|
+
errorCode: 6,
|
|
268
|
+
errorLabels: ['RetryableWriteError']
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
after do
|
|
274
|
+
[first_mongos, second_mongos].each do |admin_client|
|
|
275
|
+
admin_client.database.command(
|
|
276
|
+
configureFailPoint: 'failCommand',
|
|
277
|
+
mode: 'off'
|
|
278
|
+
)
|
|
279
|
+
admin_client.close
|
|
280
|
+
end
|
|
281
|
+
client.close
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it 'retries on different mongos' do
|
|
285
|
+
client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
286
|
+
expect { collection.insert_one(x: 1) }.to raise_error(Mongo::Error::OperationFailure)
|
|
287
|
+
expect(insert_started_events.map { |e| e.address.to_s }.sort).to eq(expected_servers)
|
|
288
|
+
expect(insert_failed_events.map { |e| e.address.to_s }.sort).to eq(expected_servers)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
context 'when no other mongos is available' do
|
|
293
|
+
let(:mongos) do
|
|
294
|
+
Mongo::Client.new(
|
|
295
|
+
[SpecConfig.instance.addresses.first],
|
|
296
|
+
direct_connection: true,
|
|
297
|
+
database: 'admin'
|
|
298
|
+
)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
let(:client) do
|
|
302
|
+
new_local_client(
|
|
303
|
+
[
|
|
304
|
+
SpecConfig.instance.addresses.first
|
|
305
|
+
],
|
|
306
|
+
SpecConfig.instance.test_options.merge(retry_writes: true)
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
before do
|
|
311
|
+
mongos.database.command(
|
|
312
|
+
configureFailPoint: 'failCommand',
|
|
313
|
+
mode: { times: 1 },
|
|
314
|
+
data: {
|
|
315
|
+
failCommands: %w(insert),
|
|
316
|
+
closeConnection: false,
|
|
317
|
+
errorCode: 6,
|
|
318
|
+
errorLabels: ['RetryableWriteError']
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
after do
|
|
324
|
+
mongos.database.command(
|
|
325
|
+
configureFailPoint: 'failCommand',
|
|
326
|
+
mode: 'off'
|
|
327
|
+
)
|
|
328
|
+
mongos.close
|
|
329
|
+
client.close
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it 'retries on the same mongos' do
|
|
333
|
+
client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
334
|
+
expect { collection.insert_one(x: 1) }.not_to raise_error
|
|
335
|
+
expect(insert_started_events.map { |e| e.address.to_s }.sort).to eq([
|
|
336
|
+
SpecConfig.instance.addresses.first.to_s,
|
|
337
|
+
SpecConfig.instance.addresses.first.to_s
|
|
338
|
+
])
|
|
339
|
+
expect(insert_failed_events.map { |e| e.address.to_s }.sort).to eq([
|
|
340
|
+
SpecConfig.instance.addresses.first.to_s
|
|
341
|
+
])
|
|
342
|
+
expect(insert_succeeded_events.map { |e| e.address.to_s }.sort).to eq([
|
|
343
|
+
SpecConfig.instance.addresses.first.to_s
|
|
344
|
+
])
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
192
348
|
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
class SearchIndexHelper
|
|
6
|
+
attr_reader :client, :collection_name
|
|
7
|
+
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
|
|
11
|
+
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id4
|
|
12
|
+
# "...each test uses a randomly generated collection name. Drivers may
|
|
13
|
+
# generate this collection name however they like, but a suggested
|
|
14
|
+
# implementation is a hex representation of an ObjectId..."
|
|
15
|
+
@collection_name = BSON::ObjectId.new.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# `soft_create` means to create the collection object without forcing it to
|
|
19
|
+
# be created in the database.
|
|
20
|
+
def collection(soft_create: false)
|
|
21
|
+
@collection ||= client.database[collection_name].tap do |collection|
|
|
22
|
+
collection.create unless soft_create
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Wait for all of the indexes with the given names to be ready; then return
|
|
27
|
+
# the list of index definitions corresponding to those names.
|
|
28
|
+
def wait_for(*names, &condition)
|
|
29
|
+
timeboxed_wait do
|
|
30
|
+
result = collection.search_indexes
|
|
31
|
+
return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Wait until all of the indexes with the given names are absent from the
|
|
36
|
+
# search index list.
|
|
37
|
+
def wait_for_absense_of(*names)
|
|
38
|
+
names.each do |name|
|
|
39
|
+
timeboxed_wait do
|
|
40
|
+
break if collection.search_indexes(name: name).empty?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def timeboxed_wait(step: 5, max: 300)
|
|
48
|
+
start = Mongo::Utils.monotonic_time
|
|
49
|
+
|
|
50
|
+
loop do
|
|
51
|
+
yield
|
|
52
|
+
|
|
53
|
+
sleep step
|
|
54
|
+
raise Timeout::Error, 'wait took too long' if Mongo::Utils.monotonic_time - start > max
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns true if the list of search indexes includes one with the given name,
|
|
59
|
+
# which is ready to be queried.
|
|
60
|
+
def ready?(list, name, &condition)
|
|
61
|
+
condition ||= ->(index) { index['queryable'] }
|
|
62
|
+
list.any? { |index| index['name'] == name && condition[index] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def filter_results(result, names)
|
|
66
|
+
result.select { |index| names.include?(index['name']) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe 'Mongo::Collection#search_indexes prose tests' do
|
|
71
|
+
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id5
|
|
72
|
+
# "These tests must run against an Atlas cluster with a 7.0+ server."
|
|
73
|
+
require_atlas
|
|
74
|
+
|
|
75
|
+
let(:client) do
|
|
76
|
+
Mongo::Client.new(
|
|
77
|
+
ENV['ATLAS_URI'],
|
|
78
|
+
database: SpecConfig.instance.test_db,
|
|
79
|
+
ssl: true,
|
|
80
|
+
ssl_verify: true
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
let(:helper) { SearchIndexHelper.new(client) }
|
|
85
|
+
|
|
86
|
+
let(:name) { 'test-search-index' }
|
|
87
|
+
let(:definition) { { 'mappings' => { 'dynamic' => false } } }
|
|
88
|
+
let(:create_index) { helper.collection.search_indexes.create_one(definition, name: name) }
|
|
89
|
+
|
|
90
|
+
# Case 1: Driver can successfully create and list search indexes
|
|
91
|
+
context 'when creating and listing search indexes' do
|
|
92
|
+
let(:index) { helper.wait_for(name).first }
|
|
93
|
+
|
|
94
|
+
it 'succeeds' do
|
|
95
|
+
expect(create_index).to be == name
|
|
96
|
+
expect(index['latestDefinition']).to be == definition
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Case 2: Driver can successfully create multiple indexes in batch
|
|
101
|
+
context 'when creating multiple indexes in batch' do
|
|
102
|
+
let(:specs) do
|
|
103
|
+
[
|
|
104
|
+
{ 'name' => 'test-search-index-1', 'definition' => definition },
|
|
105
|
+
{ 'name' => 'test-search-index-2', 'definition' => definition }
|
|
106
|
+
]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
let(:names) { specs.map { |spec| spec['name'] } }
|
|
110
|
+
let(:create_indexes) { helper.collection.search_indexes.create_many(specs) }
|
|
111
|
+
|
|
112
|
+
let(:indexes) { helper.wait_for(*names) }
|
|
113
|
+
|
|
114
|
+
let(:index1) { indexes[0] }
|
|
115
|
+
let(:index2) { indexes[1] }
|
|
116
|
+
|
|
117
|
+
it 'succeeds' do
|
|
118
|
+
expect(create_indexes).to be == names
|
|
119
|
+
expect(index1['latestDefinition']).to be == specs[0]['definition']
|
|
120
|
+
expect(index2['latestDefinition']).to be == specs[1]['definition']
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Case 3: Driver can successfully drop search indexes
|
|
125
|
+
context 'when dropping search indexes' do
|
|
126
|
+
it 'succeeds' do
|
|
127
|
+
expect(create_index).to be == name
|
|
128
|
+
helper.wait_for(name)
|
|
129
|
+
|
|
130
|
+
helper.collection.search_indexes.drop_one(name: name)
|
|
131
|
+
|
|
132
|
+
expect { helper.wait_for_absense_of(name) }.not_to raise_error
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Case 4: Driver can update a search index
|
|
137
|
+
context 'when updating search indexes' do
|
|
138
|
+
let(:new_definition) { { 'mappings' => { 'dynamic' => true } } }
|
|
139
|
+
|
|
140
|
+
let(:index) do
|
|
141
|
+
helper
|
|
142
|
+
.wait_for(name) { |idx| idx['queryable'] && idx['status'] == 'READY' }
|
|
143
|
+
.first
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# rubocop:disable RSpec/ExampleLength
|
|
147
|
+
it 'succeeds' do
|
|
148
|
+
expect(create_index).to be == name
|
|
149
|
+
helper.wait_for(name)
|
|
150
|
+
|
|
151
|
+
expect do
|
|
152
|
+
helper.collection.search_indexes.update_one(new_definition, name: name)
|
|
153
|
+
end.not_to raise_error
|
|
154
|
+
|
|
155
|
+
expect(index['latestDefinition']).to be == new_definition
|
|
156
|
+
end
|
|
157
|
+
# rubocop:enable RSpec/ExampleLength
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Case 5: dropSearchIndex suppresses namespace not found errors
|
|
161
|
+
context 'when dropping a non-existent search index' do
|
|
162
|
+
it 'ignores `namespace not found` errors' do
|
|
163
|
+
collection = helper.collection(soft_create: true)
|
|
164
|
+
expect { collection.search_indexes.drop_one(name: name) }
|
|
165
|
+
.not_to raise_error
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
data/spec/lite_spec_helper.rb
CHANGED
|
@@ -106,6 +106,31 @@ Mrss.patch_mongo_for_session_registry
|
|
|
106
106
|
|
|
107
107
|
class ExampleTimeout < StandardError; end
|
|
108
108
|
|
|
109
|
+
STANDARD_TIMEOUTS = {
|
|
110
|
+
stress: 210,
|
|
111
|
+
jruby: 90,
|
|
112
|
+
default: 45,
|
|
113
|
+
}.freeze
|
|
114
|
+
|
|
115
|
+
def timeout_type
|
|
116
|
+
if ENV['EXAMPLE_TIMEOUT'].to_i > 0
|
|
117
|
+
:custom
|
|
118
|
+
elsif %w(1 true yes).include?(ENV['STRESS']&.downcase)
|
|
119
|
+
:stress
|
|
120
|
+
elsif BSON::Environment.jruby?
|
|
121
|
+
:jruby
|
|
122
|
+
else
|
|
123
|
+
:default
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def example_timeout_seconds
|
|
128
|
+
STANDARD_TIMEOUTS.fetch(
|
|
129
|
+
timeout_type,
|
|
130
|
+
(ENV['EXAMPLE_TIMEOUT'] || STANDARD_TIMEOUTS[:default]).to_i
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
109
134
|
RSpec.configure do |config|
|
|
110
135
|
config.extend(CommonShortcuts::ClassMethods)
|
|
111
136
|
config.include(CommonShortcuts::InstanceMethods)
|
|
@@ -123,6 +148,12 @@ RSpec.configure do |config|
|
|
|
123
148
|
end
|
|
124
149
|
end
|
|
125
150
|
|
|
151
|
+
def require_atlas
|
|
152
|
+
before do
|
|
153
|
+
skip 'Set ATLAS_URI in environment to run atlas tests' if ENV['ATLAS_URI'].nil?
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
126
157
|
if SpecConfig.instance.ci?
|
|
127
158
|
SdamFormatterIntegration.subscribe
|
|
128
159
|
config.add_formatter(JsonExtFormatter, File.join(File.dirname(__FILE__), '../tmp/rspec.json'))
|
|
@@ -141,16 +172,7 @@ RSpec.configure do |config|
|
|
|
141
172
|
# Tests should take under 10 seconds ideally but it seems
|
|
142
173
|
# we have some that run for more than 10 seconds in CI.
|
|
143
174
|
config.around(:each) do |example|
|
|
144
|
-
timeout
|
|
145
|
-
210
|
|
146
|
-
else
|
|
147
|
-
if BSON::Environment.jruby?
|
|
148
|
-
90
|
|
149
|
-
else
|
|
150
|
-
45
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
TimeoutInterrupt.timeout(timeout, ExampleTimeout) do
|
|
175
|
+
TimeoutInterrupt.timeout(example_timeout_seconds, ExampleTimeout) do
|
|
154
176
|
example.run
|
|
155
177
|
end
|
|
156
178
|
end
|
data/spec/mongo/cluster_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'support/recording_logger'
|
|
4
5
|
|
|
5
6
|
# let these existing styles stand, rather than going in for a deep refactoring
|
|
6
7
|
# of these specs.
|
|
@@ -84,6 +85,41 @@ describe Mongo::Cluster do
|
|
|
84
85
|
)
|
|
85
86
|
end
|
|
86
87
|
end
|
|
88
|
+
|
|
89
|
+
context 'when a non-genuine host is detected' do
|
|
90
|
+
before { described_class.new(host_names, monitoring, logger: logger, monitoring_io: false) }
|
|
91
|
+
|
|
92
|
+
let(:logger) { RecordingLogger.new }
|
|
93
|
+
|
|
94
|
+
shared_examples 'an action that logs' do
|
|
95
|
+
it 'writes a warning to the log' do
|
|
96
|
+
expect(logger.lines).to include(a_string_matching(expected_log_output))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'when CosmosDB is detected' do
|
|
101
|
+
let(:host_names) { %w[ xyz.cosmos.azure.com ] }
|
|
102
|
+
let(:expected_log_output) { %r{https://www.mongodb.com/supportability/cosmosdb} }
|
|
103
|
+
|
|
104
|
+
it_behaves_like 'an action that logs'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'when DocumentDB is detected' do
|
|
108
|
+
let(:expected_log_output) { %r{https://www.mongodb.com/supportability/documentdb} }
|
|
109
|
+
|
|
110
|
+
context 'with docdb uri' do
|
|
111
|
+
let(:host_names) { [ 'xyz.docdb.amazonaws.com' ] }
|
|
112
|
+
|
|
113
|
+
it_behaves_like 'an action that logs'
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context 'with docdb-elastic uri' do
|
|
117
|
+
let(:host_names) { [ 'xyz.docdb-elastic.amazonaws.com' ] }
|
|
118
|
+
|
|
119
|
+
it_behaves_like 'an action that logs'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
87
123
|
end
|
|
88
124
|
|
|
89
125
|
describe '#==' do
|
|
@@ -321,7 +321,12 @@ describe Mongo::Collection::View::Aggregation do
|
|
|
321
321
|
min_server_fcv '4.2'
|
|
322
322
|
|
|
323
323
|
let(:result) do
|
|
324
|
-
aggregation.explain
|
|
324
|
+
if aggregation.explain.key?('queryPlanner')
|
|
325
|
+
aggregation.explain['queryPlanner']['collation']['locale']
|
|
326
|
+
else
|
|
327
|
+
# 7.2+ sharded cluster
|
|
328
|
+
aggregation.explain['shards'].first.last['queryPlanner']['collation']['locale']
|
|
329
|
+
end
|
|
325
330
|
end
|
|
326
331
|
|
|
327
332
|
it_behaves_like 'applies the collation'
|
|
@@ -42,6 +42,7 @@ describe Mongo::Collection::View::Explainable do
|
|
|
42
42
|
max_server_version '3.0'
|
|
43
43
|
|
|
44
44
|
it 'executes the explain' do
|
|
45
|
+
skip 'https://jira.mongodb.org/browse/RUBY-3399'
|
|
45
46
|
explain[:queryPlanner][:parsedQuery].should be_a(Hash)
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -50,6 +51,7 @@ describe Mongo::Collection::View::Explainable do
|
|
|
50
51
|
min_server_fcv '3.2'
|
|
51
52
|
|
|
52
53
|
it 'executes the explain' do
|
|
54
|
+
skip 'https://jira.mongodb.org/browse/RUBY-3399'
|
|
53
55
|
explain[:queryPlanner][:mongosPlannerVersion].should == 1
|
|
54
56
|
end
|
|
55
57
|
end
|
|
@@ -177,7 +177,7 @@ describe Mongo::Operation::Insert do
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
it 'inserts the documents into the collection' do
|
|
180
|
-
expect(authorized_collection.find.to_a). to eq(documents)
|
|
180
|
+
expect(authorized_collection.find.sort(_id: 1).to_a). to eq(documents)
|
|
181
181
|
end
|
|
182
182
|
end
|
|
183
183
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Mongo::Retryable::WriteWorker do
|
|
6
|
+
describe '#nro_write_with_retry' do
|
|
7
|
+
context 'when session is nil' do
|
|
8
|
+
let(:retryable) do
|
|
9
|
+
authorized_client['write_worker_test']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
let(:write_concern) do
|
|
13
|
+
Mongo::WriteConcern.get(w: 0)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:write_worker) do
|
|
17
|
+
described_class.new(retryable)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
let(:context) do
|
|
21
|
+
instance_double(Mongo::Operation::Context).tap do |context|
|
|
22
|
+
allow(context).to receive(:session).and_return(nil)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
# We avoid actual execution of the operation to speed up and simplify
|
|
28
|
+
# the spec.
|
|
29
|
+
allow(write_worker).to receive(:legacy_write_with_retry).and_return(nil)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'does not raise' do
|
|
33
|
+
expect do
|
|
34
|
+
write_worker.nro_write_with_retry(write_concern, context: context)
|
|
35
|
+
end.not_to raise_error
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
# rubocop:todo all
|
|
2
3
|
|
|
3
4
|
require 'spec_helper'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
MOCKED_DOCKERENV_PATH = File.expand_path(File.join(Dir.pwd, '.dockerenv-mocked'))
|
|
8
|
+
|
|
9
|
+
module ContainerChecking
|
|
10
|
+
def mock_dockerenv_path
|
|
11
|
+
before do
|
|
12
|
+
allow_any_instance_of(Mongo::Server::AppMetadata::Environment)
|
|
13
|
+
.to receive(:dockerenv_path)
|
|
14
|
+
.and_return(MOCKED_DOCKERENV_PATH)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def with_docker
|
|
19
|
+
mock_dockerenv_path
|
|
20
|
+
|
|
21
|
+
around do |example|
|
|
22
|
+
File.write(MOCKED_DOCKERENV_PATH, 'placeholder')
|
|
23
|
+
example.run
|
|
24
|
+
ensure
|
|
25
|
+
File.delete(MOCKED_DOCKERENV_PATH)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def without_docker
|
|
30
|
+
mock_dockerenv_path
|
|
31
|
+
|
|
32
|
+
around do |example|
|
|
33
|
+
FileUtils.rm_f(MOCKED_DOCKERENV_PATH)
|
|
34
|
+
example.run
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def with_kubernetes
|
|
39
|
+
local_env 'KUBERNETES_SERVICE_HOST' => 'kubernetes.default.svc.cluster.local'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def without_kubernetes
|
|
43
|
+
local_env 'KUBERNETES_SERVICE_HOST' => nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
4
46
|
|
|
5
47
|
describe Mongo::Server::AppMetadata::Environment do
|
|
48
|
+
extend ContainerChecking
|
|
49
|
+
|
|
6
50
|
let(:env) { described_class.new }
|
|
7
51
|
|
|
8
52
|
shared_examples_for 'running in a FaaS environment' do
|
|
@@ -17,6 +61,36 @@ describe Mongo::Server::AppMetadata::Environment do
|
|
|
17
61
|
end
|
|
18
62
|
end
|
|
19
63
|
|
|
64
|
+
shared_examples_for 'not running in a Docker container' do
|
|
65
|
+
it 'does not detect Docker' do
|
|
66
|
+
expect(env.container || {}).not_to include :runtime
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
shared_examples_for 'not running under Kubernetes' do
|
|
71
|
+
it 'does not detect Kubernetes' do
|
|
72
|
+
expect(env.container || {}).not_to include :orchestrator
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
shared_examples_for 'running under Kubernetes' do
|
|
77
|
+
it 'detects that Kubernetes is present' do
|
|
78
|
+
expect(env.container[:orchestrator]).to be == 'kubernetes'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
shared_examples_for 'running in a Docker container' do
|
|
83
|
+
it 'detects that Docker is present' do
|
|
84
|
+
expect(env.container[:runtime]).to be == 'docker'
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
shared_examples_for 'running under Kerbenetes' do
|
|
89
|
+
it 'detects that kubernetes is present' do
|
|
90
|
+
expect(env.container['orchestrator']).to be == 'kubernetes'
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
20
94
|
context 'when run outside of a FaaS environment' do
|
|
21
95
|
it_behaves_like 'running outside a FaaS environment'
|
|
22
96
|
end
|
|
@@ -204,6 +278,67 @@ describe Mongo::Server::AppMetadata::Environment do
|
|
|
204
278
|
timeout_sec: 60, region: 'us-central1',
|
|
205
279
|
}
|
|
206
280
|
end
|
|
281
|
+
|
|
282
|
+
context 'when a container is present' do
|
|
283
|
+
with_kubernetes
|
|
284
|
+
with_docker
|
|
285
|
+
|
|
286
|
+
it 'includes a container key' do
|
|
287
|
+
expect(env.to_h[:container]).to be == {
|
|
288
|
+
runtime: 'docker',
|
|
289
|
+
orchestrator: 'kubernetes'
|
|
290
|
+
}
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
context 'when no container is present' do
|
|
295
|
+
without_kubernetes
|
|
296
|
+
without_docker
|
|
297
|
+
|
|
298
|
+
it 'does not include a container key' do
|
|
299
|
+
expect(env.to_h).not_to include(:container)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# have a specific test for this, since the tests that check
|
|
306
|
+
# for Docker use a mocked value for the .dockerenv path.
|
|
307
|
+
it 'should look for dockerenv in root directory' do
|
|
308
|
+
expect(described_class::DOCKERENV_PATH).to be == '/.dockerenv'
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
context 'when no container is present' do
|
|
312
|
+
without_kubernetes
|
|
313
|
+
without_docker
|
|
314
|
+
|
|
315
|
+
it_behaves_like 'not running in a Docker container'
|
|
316
|
+
it_behaves_like 'not running under Kubernetes'
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
context 'when container is present' do
|
|
320
|
+
context 'when kubernetes is present' do
|
|
321
|
+
without_docker
|
|
322
|
+
with_kubernetes
|
|
323
|
+
|
|
324
|
+
it_behaves_like 'not running in a Docker container'
|
|
325
|
+
it_behaves_like 'running under Kubernetes'
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
context 'when docker is present' do
|
|
329
|
+
with_docker
|
|
330
|
+
without_kubernetes
|
|
331
|
+
|
|
332
|
+
it_behaves_like 'running in a Docker container'
|
|
333
|
+
it_behaves_like 'not running under Kubernetes'
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
context 'when both kubernetes and docker are present' do
|
|
337
|
+
with_docker
|
|
338
|
+
with_kubernetes
|
|
339
|
+
|
|
340
|
+
it_behaves_like 'running in a Docker container'
|
|
341
|
+
it_behaves_like 'running under Kubernetes'
|
|
207
342
|
end
|
|
208
343
|
end
|
|
209
344
|
end
|